diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000000..178bcbce24 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 7fa7496cedd22a33b10cd91d84b7920f +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.circleci/config.yml b/.circleci/config.yml index f6eff674de..075a613be0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: jobs: build_doc: docker: - - image: cimg/python:3.13 + - image: cimg/python:3.10 steps: - checkout - run: diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0d943696cc..24f0926299 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,4 @@ # These are supported funding model platforms github: AtsushiSakai patreon: myenigma -custom: https://www.paypal.com/paypalme/myenigmapay/ +custom: https://www.paypal.me/myenigmapay/ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6ffedf6f3b..07f1bc80e0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,8 +6,3 @@ updates: interval: weekly time: "20:00" open-pull-requests-limit: 10 - -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c0d9f7eab2..c541063156 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ #### CheckList -- [ ] Did you add an unittest for your new example or defect fix? -- [ ] Did you add documents for your new example? -- [ ] All CIs are green? (You can check it after submitting) +-[] Did you add an unittest for your new example or defect fix? +-[] Did you add documents for your new example? +-[] All CIs are green? (You can check it after submitting) diff --git a/.github/workflows/Linux_CI.yml b/.github/workflows/Linux_CI.yml index 22a8baa4e7..73f8aa5c0d 100644 --- a/.github/workflows/Linux_CI.yml +++ b/.github/workflows/Linux_CI.yml @@ -12,16 +12,16 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.13' ] + python-version: ['3.10'] name: Python ${{ matrix.python-version }} CI steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v2 - run: git fetch --prune --unshallow - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/MacOS_CI.yml b/.github/workflows/MacOS_CI.yml index aa162cde2e..5c796120a5 100644 --- a/.github/workflows/MacOS_CI.yml +++ b/.github/workflows/MacOS_CI.yml @@ -16,17 +16,17 @@ jobs: runs-on: macos-latest strategy: matrix: - python-version: [ '3.13' ] + python-version: [ '3.10' ] name: Python ${{ matrix.python-version }} CI steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v2 - run: git fetch --prune --unshallow - name: Update bash run: brew install bash - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -36,4 +36,4 @@ jobs: python -m pip install --upgrade pip pip install -r requirements/requirements.txt - name: do all unit tests - run: bash runtests.sh + run: bash runtests.sh \ No newline at end of file diff --git a/.github/workflows/Windows_CI.yml b/.github/workflows/Windows_CI.yml deleted file mode 100644 index 4d83a3a696..0000000000 --- a/.github/workflows/Windows_CI.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Windows_CI - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - push: - branches: - - master - pull_request: - - -jobs: - build: - runs-on: windows-latest - strategy: - matrix: - python-version: [ '3.13' ] - name: Python ${{ matrix.python-version }} CI - steps: - - uses: actions/checkout@v5 - - run: git fetch --prune --unshallow - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python --version - python -m pip install --upgrade pip - pip install -r requirements/requirements.txt - - name: do all unit tests - run: bash runtests.sh diff --git a/.github/workflows/circle_artifacts.yml b/.github/workflows/circle_artifacts.yml new file mode 100644 index 0000000000..56ac78ed15 --- /dev/null +++ b/.github/workflows/circle_artifacts.yml @@ -0,0 +1,13 @@ +on: [status] +jobs: + circleci_artifacts_redirector_job: + runs-on: ubuntu-20.04 + name: Run CircleCI artifacts redirector + steps: + - name: GitHub Action step + uses: larsoner/circleci-artifacts-redirector-action@master + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + artifact-path: 0/html-scipyorg/index.html + circleci-jobs: build_docs + job-title: build_doc artifact \ No newline at end of file diff --git a/.github/workflows/circleci-artifacts-redirector.yml b/.github/workflows/circleci-artifacts-redirector.yml index f6dd3f2e7c..d988d2c38e 100644 --- a/.github/workflows/circleci-artifacts-redirector.yml +++ b/.github/workflows/circleci-artifacts-redirector.yml @@ -3,12 +3,11 @@ on: [status] jobs: circleci_artifacts_redirector_job: runs-on: ubuntu-latest - name: Run CircleCI artifacts redirector!! + name: Run CircleCI artifacts redirector steps: - name: run-circleci-artifacts-redirector - uses: larsoner/circleci-artifacts-redirector-action@v1.2.0 + uses: larsoner/circleci-artifacts-redirector-action@0.3.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - api-token: ${{ secrets.CIRCLECI_TOKEN }} artifact-path: 0/html/index.html circleci-jobs: build_doc diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0ed1d7e90d..148260a835 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -24,7 +24,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v1 with: config-file: ./.github/codeql/codeql-config.yml # Override language selection by uncommenting this and choosing your languages @@ -34,7 +34,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -48,4 +48,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index e08c6106c0..e51197cb37 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,4 +1,4 @@ -name: GitHub Pages site update +name: Pages on: push: branches: @@ -6,15 +6,9 @@ on: jobs: build: runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - permissions: - id-token: write - pages: write steps: - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v2 - name: Checkout uses: actions/checkout@master with: @@ -24,7 +18,12 @@ jobs: python --version python -m pip install --upgrade pip python -m pip install -r requirements/requirements.txt - - name: Build and Deploy - uses: sphinx-notes/pages@v3 + - name: Build and Commit + uses: sphinx-notes/pages@v2 with: - requirements_path: ./docs/doc_requirements.txt + requirements_path: ./docs/doc_requirements.txt + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages \ No newline at end of file diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 0000000000..b06edf3510 --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,4 @@ +extraction: + python: + python_setup: + version: 3 diff --git a/AerialNavigation/__init__.py b/.nojekyll similarity index 100% rename from AerialNavigation/__init__.py rename to .nojekyll diff --git a/AerialNavigation/drone_3d_trajectory_following/Quadrotor.py b/AerialNavigation/drone_3d_trajectory_following/Quadrotor.py deleted file mode 100644 index c379e5eda0..0000000000 --- a/AerialNavigation/drone_3d_trajectory_following/Quadrotor.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Class for plotting a quadrotor - -Author: Daniel Ingram (daniel-s-ingram) -""" - -from math import cos, sin -import numpy as np -import matplotlib.pyplot as plt - -class Quadrotor(): - def __init__(self, x=0, y=0, z=0, roll=0, pitch=0, yaw=0, size=0.25, show_animation=True): - self.p1 = np.array([size / 2, 0, 0, 1]).T - self.p2 = np.array([-size / 2, 0, 0, 1]).T - self.p3 = np.array([0, size / 2, 0, 1]).T - self.p4 = np.array([0, -size / 2, 0, 1]).T - - self.x_data = [] - self.y_data = [] - self.z_data = [] - self.show_animation = show_animation - - if self.show_animation: - plt.ion() - fig = plt.figure() - # for stopping simulation with the esc key. - fig.canvas.mpl_connect('key_release_event', - lambda event: [exit(0) if event.key == 'escape' else None]) - - self.ax = fig.add_subplot(111, projection='3d') - - self.update_pose(x, y, z, roll, pitch, yaw) - - def update_pose(self, x, y, z, roll, pitch, yaw): - self.x = x - self.y = y - self.z = z - self.roll = roll - self.pitch = pitch - self.yaw = yaw - self.x_data.append(x) - self.y_data.append(y) - self.z_data.append(z) - - if self.show_animation: - self.plot() - - def transformation_matrix(self): - x = self.x - y = self.y - z = self.z - roll = self.roll - pitch = self.pitch - yaw = self.yaw - return np.array( - [[cos(yaw) * cos(pitch), -sin(yaw) * cos(roll) + cos(yaw) * sin(pitch) * sin(roll), sin(yaw) * sin(roll) + cos(yaw) * sin(pitch) * cos(roll), x], - [sin(yaw) * cos(pitch), cos(yaw) * cos(roll) + sin(yaw) * sin(pitch) - * sin(roll), -cos(yaw) * sin(roll) + sin(yaw) * sin(pitch) * cos(roll), y], - [-sin(pitch), cos(pitch) * sin(roll), cos(pitch) * cos(roll), z] - ]) - - def plot(self): # pragma: no cover - T = self.transformation_matrix() - - p1_t = np.matmul(T, self.p1) - p2_t = np.matmul(T, self.p2) - p3_t = np.matmul(T, self.p3) - p4_t = np.matmul(T, self.p4) - - plt.cla() - - self.ax.plot([p1_t[0], p2_t[0], p3_t[0], p4_t[0]], - [p1_t[1], p2_t[1], p3_t[1], p4_t[1]], - [p1_t[2], p2_t[2], p3_t[2], p4_t[2]], 'k.') - - self.ax.plot([p1_t[0], p2_t[0]], [p1_t[1], p2_t[1]], - [p1_t[2], p2_t[2]], 'r-') - self.ax.plot([p3_t[0], p4_t[0]], [p3_t[1], p4_t[1]], - [p3_t[2], p4_t[2]], 'r-') - - self.ax.plot(self.x_data, self.y_data, self.z_data, 'b:') - - plt.xlim(-5, 5) - plt.ylim(-5, 5) - self.ax.set_zlim(0, 10) - - plt.pause(0.001) diff --git a/AerialNavigation/drone_3d_trajectory_following/TrajectoryGenerator.py b/AerialNavigation/drone_3d_trajectory_following/TrajectoryGenerator.py deleted file mode 100644 index 0b99af885c..0000000000 --- a/AerialNavigation/drone_3d_trajectory_following/TrajectoryGenerator.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -Generates a quintic polynomial trajectory. - -Author: Daniel Ingram (daniel-s-ingram) -""" - -import numpy as np - -class TrajectoryGenerator(): - def __init__(self, start_pos, des_pos, T, start_vel=[0,0,0], des_vel=[0,0,0], start_acc=[0,0,0], des_acc=[0,0,0]): - self.start_x = start_pos[0] - self.start_y = start_pos[1] - self.start_z = start_pos[2] - - self.des_x = des_pos[0] - self.des_y = des_pos[1] - self.des_z = des_pos[2] - - self.start_x_vel = start_vel[0] - self.start_y_vel = start_vel[1] - self.start_z_vel = start_vel[2] - - self.des_x_vel = des_vel[0] - self.des_y_vel = des_vel[1] - self.des_z_vel = des_vel[2] - - self.start_x_acc = start_acc[0] - self.start_y_acc = start_acc[1] - self.start_z_acc = start_acc[2] - - self.des_x_acc = des_acc[0] - self.des_y_acc = des_acc[1] - self.des_z_acc = des_acc[2] - - self.T = T - - def solve(self): - A = np.array( - [[0, 0, 0, 0, 0, 1], - [self.T**5, self.T**4, self.T**3, self.T**2, self.T, 1], - [0, 0, 0, 0, 1, 0], - [5*self.T**4, 4*self.T**3, 3*self.T**2, 2*self.T, 1, 0], - [0, 0, 0, 2, 0, 0], - [20*self.T**3, 12*self.T**2, 6*self.T, 2, 0, 0] - ]) - - b_x = np.array( - [[self.start_x], - [self.des_x], - [self.start_x_vel], - [self.des_x_vel], - [self.start_x_acc], - [self.des_x_acc] - ]) - - b_y = np.array( - [[self.start_y], - [self.des_y], - [self.start_y_vel], - [self.des_y_vel], - [self.start_y_acc], - [self.des_y_acc] - ]) - - b_z = np.array( - [[self.start_z], - [self.des_z], - [self.start_z_vel], - [self.des_z_vel], - [self.start_z_acc], - [self.des_z_acc] - ]) - - self.x_c = np.linalg.solve(A, b_x) - self.y_c = np.linalg.solve(A, b_y) - self.z_c = np.linalg.solve(A, b_z) \ No newline at end of file diff --git a/AerialNavigation/drone_3d_trajectory_following/__init__.py b/AerialNavigation/drone_3d_trajectory_following/__init__.py deleted file mode 100644 index 2194d4c303..0000000000 --- a/AerialNavigation/drone_3d_trajectory_following/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent)) diff --git a/AerialNavigation/drone_3d_trajectory_following/drone_3d_trajectory_following.py b/AerialNavigation/drone_3d_trajectory_following/drone_3d_trajectory_following.py deleted file mode 100644 index 029e82be62..0000000000 --- a/AerialNavigation/drone_3d_trajectory_following/drone_3d_trajectory_following.py +++ /dev/null @@ -1,213 +0,0 @@ -""" -Simulate a quadrotor following a 3D trajectory - -Author: Daniel Ingram (daniel-s-ingram) -""" - -from math import cos, sin -import numpy as np -from Quadrotor import Quadrotor -from TrajectoryGenerator import TrajectoryGenerator - -show_animation = True - -# Simulation parameters -g = 9.81 -m = 0.2 -Ixx = 1 -Iyy = 1 -Izz = 1 -T = 5 - -# Proportional coefficients -Kp_x = 1 -Kp_y = 1 -Kp_z = 1 -Kp_roll = 25 -Kp_pitch = 25 -Kp_yaw = 25 - -# Derivative coefficients -Kd_x = 10 -Kd_y = 10 -Kd_z = 1 - - -def quad_sim(x_c, y_c, z_c): - """ - Calculates the necessary thrust and torques for the quadrotor to - follow the trajectory described by the sets of coefficients - x_c, y_c, and z_c. - """ - x_pos = -5 - y_pos = -5 - z_pos = 5 - x_vel = 0 - y_vel = 0 - z_vel = 0 - x_acc = 0 - y_acc = 0 - z_acc = 0 - roll = 0 - pitch = 0 - yaw = 0 - roll_vel = 0 - pitch_vel = 0 - yaw_vel = 0 - - des_yaw = 0 - - dt = 0.1 - t = 0 - - q = Quadrotor(x=x_pos, y=y_pos, z=z_pos, roll=roll, - pitch=pitch, yaw=yaw, size=1, show_animation=show_animation) - - i = 0 - n_run = 8 - irun = 0 - - while True: - while t <= T: - # des_x_pos = calculate_position(x_c[i], t) - # des_y_pos = calculate_position(y_c[i], t) - des_z_pos = calculate_position(z_c[i], t) - # des_x_vel = calculate_velocity(x_c[i], t) - # des_y_vel = calculate_velocity(y_c[i], t) - des_z_vel = calculate_velocity(z_c[i], t) - des_x_acc = calculate_acceleration(x_c[i], t) - des_y_acc = calculate_acceleration(y_c[i], t) - des_z_acc = calculate_acceleration(z_c[i], t) - - thrust = m * (g + des_z_acc + Kp_z * (des_z_pos - - z_pos) + Kd_z * (des_z_vel - z_vel)) - - roll_torque = Kp_roll * \ - (((des_x_acc * sin(des_yaw) - des_y_acc * cos(des_yaw)) / g) - roll) - pitch_torque = Kp_pitch * \ - (((des_x_acc * cos(des_yaw) - des_y_acc * sin(des_yaw)) / g) - pitch) - yaw_torque = Kp_yaw * (des_yaw - yaw) - - roll_vel += roll_torque * dt / Ixx - pitch_vel += pitch_torque * dt / Iyy - yaw_vel += yaw_torque * dt / Izz - - roll += roll_vel * dt - pitch += pitch_vel * dt - yaw += yaw_vel * dt - - R = rotation_matrix(roll, pitch, yaw) - acc = (np.matmul(R, np.array( - [0, 0, thrust.item()]).T) - np.array([0, 0, m * g]).T) / m - x_acc = acc[0] - y_acc = acc[1] - z_acc = acc[2] - x_vel += x_acc * dt - y_vel += y_acc * dt - z_vel += z_acc * dt - x_pos += x_vel * dt - y_pos += y_vel * dt - z_pos += z_vel * dt - - q.update_pose(x_pos, y_pos, z_pos, roll, pitch, yaw) - - t += dt - - t = 0 - i = (i + 1) % 4 - irun += 1 - if irun >= n_run: - break - - print("Done") - - -def calculate_position(c, t): - """ - Calculates a position given a set of quintic coefficients and a time. - - Args - c: List of coefficients generated by a quintic polynomial - trajectory generator. - t: Time at which to calculate the position - - Returns - Position - """ - return c[0] * t**5 + c[1] * t**4 + c[2] * t**3 + c[3] * t**2 + c[4] * t + c[5] - - -def calculate_velocity(c, t): - """ - Calculates a velocity given a set of quintic coefficients and a time. - - Args - c: List of coefficients generated by a quintic polynomial - trajectory generator. - t: Time at which to calculate the velocity - - Returns - Velocity - """ - return 5 * c[0] * t**4 + 4 * c[1] * t**3 + 3 * c[2] * t**2 + 2 * c[3] * t + c[4] - - -def calculate_acceleration(c, t): - """ - Calculates an acceleration given a set of quintic coefficients and a time. - - Args - c: List of coefficients generated by a quintic polynomial - trajectory generator. - t: Time at which to calculate the acceleration - - Returns - Acceleration - """ - return 20 * c[0] * t**3 + 12 * c[1] * t**2 + 6 * c[2] * t + 2 * c[3] - - -def rotation_matrix(roll_array, pitch_array, yaw): - """ - Calculates the ZYX rotation matrix. - - Args - Roll: Angular position about the x-axis in radians. - Pitch: Angular position about the y-axis in radians. - Yaw: Angular position about the z-axis in radians. - - Returns - 3x3 rotation matrix as NumPy array - """ - roll = roll_array[0] - pitch = pitch_array[0] - return np.array( - [[cos(yaw) * cos(pitch), -sin(yaw) * cos(roll) + cos(yaw) * sin(pitch) * sin(roll), sin(yaw) * sin(roll) + cos(yaw) * sin(pitch) * cos(roll)], - [sin(yaw) * cos(pitch), cos(yaw) * cos(roll) + sin(yaw) * sin(pitch) * - sin(roll), -cos(yaw) * sin(roll) + sin(yaw) * sin(pitch) * cos(roll)], - [-sin(pitch), cos(pitch) * sin(roll), cos(pitch) * cos(yaw)] - ]) - - -def main(): - """ - Calculates the x, y, z coefficients for the four segments - of the trajectory - """ - x_coeffs = [[], [], [], []] - y_coeffs = [[], [], [], []] - z_coeffs = [[], [], [], []] - waypoints = [[-5, -5, 5], [5, -5, 5], [5, 5, 5], [-5, 5, 5]] - - for i in range(4): - traj = TrajectoryGenerator(waypoints[i], waypoints[(i + 1) % 4], T) - traj.solve() - x_coeffs[i] = traj.x_c - y_coeffs[i] = traj.y_c - z_coeffs[i] = traj.z_c - - quad_sim(x_coeffs, y_coeffs, z_coeffs) - - -if __name__ == "__main__": - main() diff --git a/AerialNavigation/rocket_powered_landing/rocket_powered_landing.py b/AerialNavigation/rocket_powered_landing/rocket_powered_landing.py deleted file mode 100644 index e8ba8fa220..0000000000 --- a/AerialNavigation/rocket_powered_landing/rocket_powered_landing.py +++ /dev/null @@ -1,674 +0,0 @@ -""" - -A rocket powered landing with successive convexification - -author: Sven Niederberger - Atsushi Sakai - -Reference: -- Python implementation of 'Successive Convexification for 6-DoF Mars Rocket Powered Landing with Free-Final-Time' paper -by Michael Szmuk and Behcet Acıkmese. - -- EmbersArc/SuccessiveConvexificationFreeFinalTime: Implementation of "Successive Convexification for 6-DoF Mars Rocket Powered Landing with Free-Final-Time" https://github.com/EmbersArc/SuccessiveConvexificationFreeFinalTime - -""" -import warnings -from time import time -import numpy as np -from scipy.integrate import odeint -import cvxpy -import matplotlib.pyplot as plt - -# Trajectory points -K = 50 - -# Max solver iterations -iterations = 30 - -# Weight constants -W_SIGMA = 1 # flight time -W_DELTA = 1e-3 # difference in state/input -W_DELTA_SIGMA = 1e-1 # difference in flight time -W_NU = 1e5 # virtual control - -print(cvxpy.installed_solvers()) -solver = 'ECOS' -verbose_solver = False - -show_animation = True - - -class Rocket_Model_6DoF: - """ - A 6 degree of freedom rocket landing problem. - """ - - def __init__(self, rng): - """ - A large r_scale for a small scale problem will - ead to numerical problems as parameters become excessively small - and (it seems) precision is lost in the dynamics. - """ - self.n_x = 14 - self.n_u = 3 - - # Mass - self.m_wet = 3.0 # 30000 kg - self.m_dry = 2.2 # 22000 kg - - # Flight time guess - self.t_f_guess = 10.0 # 10 s - - # State constraints - self.r_I_final = np.array((0., 0., 0.)) - self.v_I_final = np.array((-1e-1, 0., 0.)) - self.q_B_I_final = self.euler_to_quat((0, 0, 0)) - self.w_B_final = np.deg2rad(np.array((0., 0., 0.))) - - self.w_B_max = np.deg2rad(60) - - # Angles - max_gimbal = 20 - max_angle = 90 - glidelslope_angle = 20 - - self.tan_delta_max = np.tan(np.deg2rad(max_gimbal)) - self.cos_theta_max = np.cos(np.deg2rad(max_angle)) - self.tan_gamma_gs = np.tan(np.deg2rad(glidelslope_angle)) - - # Thrust limits - self.T_max = 5.0 - self.T_min = 0.3 - - # Angular moment of inertia - self.J_B = 1e-2 * np.diag([1., 1., 1.]) - - # Gravity - self.g_I = np.array((-1, 0., 0.)) - - # Fuel consumption - self.alpha_m = 0.01 - - # Vector from thrust point to CoM - self.r_T_B = np.array([-1e-2, 0., 0.]) - - self.set_random_initial_state(rng) - - self.x_init = np.concatenate( - ((self.m_wet,), self.r_I_init, self.v_I_init, self.q_B_I_init, self.w_B_init)) - self.x_final = np.concatenate( - ((self.m_dry,), self.r_I_final, self.v_I_final, self.q_B_I_final, self.w_B_final)) - - self.r_scale = np.linalg.norm(self.r_I_init) - self.m_scale = self.m_wet - - def set_random_initial_state(self, rng): - if rng is None: - rng = np.random.default_rng() - - self.r_I_init = np.array((0., 0., 0.)) - self.r_I_init[0] = rng.uniform(3, 4) - self.r_I_init[1:3] = rng.uniform(-2, 2, size=2) - - self.v_I_init = np.array((0., 0., 0.)) - self.v_I_init[0] = rng.uniform(-1, -0.5) - self.v_I_init[1:3] = rng.uniform(-0.5, -0.2, - size=2) * self.r_I_init[1:3] - - self.q_B_I_init = self.euler_to_quat((0, - rng.uniform(-30, 30), - rng.uniform(-30, 30))) - self.w_B_init = np.deg2rad((0, - rng.uniform(-20, 20), - rng.uniform(-20, 20))) - - def f_func(self, x, u): - m, _, _, _, vx, vy, vz, q0, q1, q2, q3, wx, wy, wz = x[0], x[1], x[ - 2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13] - ux, uy, uz = u[0], u[1], u[2] - - return np.array([ - [-0.01 * np.sqrt(ux**2 + uy**2 + uz**2)], - [vx], - [vy], - [vz], - [(-1.0 * m - ux * (2 * q2**2 + 2 * q3**2 - 1) - 2 * uy - * (q0 * q3 - q1 * q2) + 2 * uz * (q0 * q2 + q1 * q3)) / m], - [(2 * ux * (q0 * q3 + q1 * q2) - uy * (2 * q1**2 - + 2 * q3**2 - 1) - 2 * uz * (q0 * q1 - q2 * q3)) / m], - [(-2 * ux * (q0 * q2 - q1 * q3) + 2 * uy - * (q0 * q1 + q2 * q3) - uz * (2 * q1**2 + 2 * q2**2 - 1)) / m], - [-0.5 * q1 * wx - 0.5 * q2 * wy - 0.5 * q3 * wz], - [0.5 * q0 * wx + 0.5 * q2 * wz - 0.5 * q3 * wy], - [0.5 * q0 * wy - 0.5 * q1 * wz + 0.5 * q3 * wx], - [0.5 * q0 * wz + 0.5 * q1 * wy - 0.5 * q2 * wx], - [0], - [1.0 * uz], - [-1.0 * uy] - ]) - - def A_func(self, x, u): - m, _, _, _, _, _, _, q0, q1, q2, q3, wx, wy, wz = x[0], x[1], x[ - 2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13] - ux, uy, uz = u[0], u[1], u[2] - - return np.array([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], - [(ux * (2 * q2**2 + 2 * q3**2 - 1) + 2 * uy * (q0 * q3 - q1 * q2) - 2 * uz * (q0 * q2 + q1 * q3)) / m**2, 0, 0, 0, 0, 0, 0, 2 * (q2 * uz - - q3 * uy) / m, 2 * (q2 * uy + q3 * uz) / m, 2 * (q0 * uz + q1 * uy - 2 * q2 * ux) / m, 2 * (-q0 * uy + q1 * uz - 2 * q3 * ux) / m, 0, 0, 0], - [(-2 * ux * (q0 * q3 + q1 * q2) + uy * (2 * q1**2 + 2 * q3**2 - 1) + 2 * uz * (q0 * q1 - q2 * q3)) / m**2, 0, 0, 0, 0, 0, 0, 2 * (-q1 * uz - + q3 * ux) / m, 2 * (-q0 * uz - 2 * q1 * uy + q2 * ux) / m, 2 * (q1 * ux + q3 * uz) / m, 2 * (q0 * ux + q2 * uz - 2 * q3 * uy) / m, 0, 0, 0], - [(2 * ux * (q0 * q2 - q1 * q3) - 2 * uy * (q0 * q1 + q2 * q3) + uz * (2 * q1**2 + 2 * q2**2 - 1)) / m**2, 0, 0, 0, 0, 0, 0, 2 * (q1 * uy - - q2 * ux) / m, 2 * (q0 * uy - 2 * q1 * uz + q3 * ux) / m, 2 * (-q0 * ux - 2 * q2 * uz + q3 * uy) / m, 2 * (q1 * ux + q2 * uy) / m, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, -0.5 * wx, -0.5 * wy, - - 0.5 * wz, -0.5 * q1, -0.5 * q2, -0.5 * q3], - [0, 0, 0, 0, 0, 0, 0, 0.5 * wx, 0, 0.5 * wz, - - 0.5 * wy, 0.5 * q0, -0.5 * q3, 0.5 * q2], - [0, 0, 0, 0, 0, 0, 0, 0.5 * wy, -0.5 * wz, 0, - 0.5 * wx, 0.5 * q3, 0.5 * q0, -0.5 * q1], - [0, 0, 0, 0, 0, 0, 0, 0.5 * wz, 0.5 * wy, - - 0.5 * wx, 0, -0.5 * q2, 0.5 * q1, 0.5 * q0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) - - def B_func(self, x, u): - m, _, _, _, _, _, _, q0, q1, q2, q3, _, _, _ = x[0], x[1], x[ - 2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13] - ux, uy, uz = u[0], u[1], u[2] - - return np.array([ - [-0.01 * ux / np.sqrt(ux**2 + uy**2 + uz**2), - -0.01 * uy / np.sqrt(ux ** 2 + uy**2 + uz**2), - -0.01 * uz / np.sqrt(ux**2 + uy**2 + uz**2)], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [(-2 * q2**2 - 2 * q3**2 + 1) / m, 2 - * (-q0 * q3 + q1 * q2) / m, 2 * (q0 * q2 + q1 * q3) / m], - [2 * (q0 * q3 + q1 * q2) / m, (-2 * q1**2 - 2 - * q3**2 + 1) / m, 2 * (-q0 * q1 + q2 * q3) / m], - [2 * (-q0 * q2 + q1 * q3) / m, 2 * (q0 * q1 + q2 * q3) - / m, (-2 * q1**2 - 2 * q2**2 + 1) / m], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 1.0], - [0, -1.0, 0] - ]) - - def euler_to_quat(self, a): - a = np.deg2rad(a) - - cy = np.cos(a[1] * 0.5) - sy = np.sin(a[1] * 0.5) - cr = np.cos(a[0] * 0.5) - sr = np.sin(a[0] * 0.5) - cp = np.cos(a[2] * 0.5) - sp = np.sin(a[2] * 0.5) - - q = np.zeros(4) - - q[0] = cy * cr * cp + sy * sr * sp - q[1] = cy * sr * cp - sy * cr * sp - q[3] = cy * cr * sp + sy * sr * cp - q[2] = sy * cr * cp - cy * sr * sp - - return q - - def skew(self, v): - return np.array([ - [0, -v[2], v[1]], - [v[2], 0, -v[0]], - [-v[1], v[0], 0] - ]) - - def dir_cosine(self, q): - return np.array([ - [1 - 2 * (q[2] ** 2 + q[3] ** 2), 2 * (q[1] * q[2] - + q[0] * q[3]), 2 * (q[1] * q[3] - q[0] * q[2])], - [2 * (q[1] * q[2] - q[0] * q[3]), 1 - 2 - * (q[1] ** 2 + q[3] ** 2), 2 * (q[2] * q[3] + q[0] * q[1])], - [2 * (q[1] * q[3] + q[0] * q[2]), 2 * (q[2] * q[3] - - q[0] * q[1]), 1 - 2 * (q[1] ** 2 + q[2] ** 2)] - ]) - - def omega(self, w): - return np.array([ - [0, -w[0], -w[1], -w[2]], - [w[0], 0, w[2], -w[1]], - [w[1], -w[2], 0, w[0]], - [w[2], w[1], -w[0], 0], - ]) - - def initialize_trajectory(self, X, U): - """ - Initialize the trajectory with linear approximation. - """ - K = X.shape[1] - - for k in range(K): - alpha1 = (K - k) / K - alpha2 = k / K - - m_k = (alpha1 * self.x_init[0] + alpha2 * self.x_final[0],) - r_I_k = alpha1 * self.x_init[1:4] + alpha2 * self.x_final[1:4] - v_I_k = alpha1 * self.x_init[4:7] + alpha2 * self.x_final[4:7] - q_B_I_k = np.array([1, 0, 0, 0]) - w_B_k = alpha1 * self.x_init[11:14] + alpha2 * self.x_final[11:14] - - X[:, k] = np.concatenate((m_k, r_I_k, v_I_k, q_B_I_k, w_B_k)) - U[:, k] = m_k * -self.g_I - - return X, U - - def get_constraints(self, X_v, U_v, X_last_p, U_last_p): - """ - Get model specific constraints. - - :param X_v: cvx variable for current states - :param U_v: cvx variable for current inputs - :param X_last_p: cvx parameter for last states - :param U_last_p: cvx parameter for last inputs - :return: A list of cvx constraints - """ - # Boundary conditions: - constraints = [ - X_v[0, 0] == self.x_init[0], - X_v[1:4, 0] == self.x_init[1:4], - X_v[4:7, 0] == self.x_init[4:7], - # X_v[7:11, 0] == self.x_init[7:11], # initial orientation is free - X_v[11:14, 0] == self.x_init[11:14], - - # X_[0, -1] final mass is free - X_v[1:, -1] == self.x_final[1:], - U_v[1:3, -1] == 0, - ] - - constraints += [ - # State constraints: - X_v[0, :] >= self.m_dry, # minimum mass - cvxpy.norm(X_v[2: 4, :], axis=0) <= X_v[1, :] / \ - self.tan_gamma_gs, # glideslope - cvxpy.norm(X_v[9:11, :], axis=0) <= np.sqrt( - (1 - self.cos_theta_max) / 2), # maximum angle - # maximum angular velocity - cvxpy.norm(X_v[11: 14, :], axis=0) <= self.w_B_max, - - # Control constraints: - cvxpy.norm(U_v[1:3, :], axis=0) <= self.tan_delta_max * \ - U_v[0, :], # gimbal angle constraint - cvxpy.norm(U_v, axis=0) <= self.T_max, # upper thrust constraint - ] - - # linearized lower thrust constraint - rhs = [U_last_p[:, k] / cvxpy.norm(U_last_p[:, k]) @ U_v[:, k] - for k in range(X_v.shape[1])] - constraints += [ - self.T_min <= cvxpy.vstack(rhs) - ] - - return constraints - - -class Integrator: - def __init__(self, m, K): - self.K = K - self.m = m - self.n_x = m.n_x - self.n_u = m.n_u - - self.A_bar = np.zeros([m.n_x * m.n_x, K - 1]) - self.B_bar = np.zeros([m.n_x * m.n_u, K - 1]) - self.C_bar = np.zeros([m.n_x * m.n_u, K - 1]) - self.S_bar = np.zeros([m.n_x, K - 1]) - self.z_bar = np.zeros([m.n_x, K - 1]) - - # vector indices for flat matrices - x_end = m.n_x - A_bar_end = m.n_x * (1 + m.n_x) - B_bar_end = m.n_x * (1 + m.n_x + m.n_u) - C_bar_end = m.n_x * (1 + m.n_x + m.n_u + m.n_u) - S_bar_end = m.n_x * (1 + m.n_x + m.n_u + m.n_u + 1) - z_bar_end = m.n_x * (1 + m.n_x + m.n_u + m.n_u + 2) - self.x_ind = slice(0, x_end) - self.A_bar_ind = slice(x_end, A_bar_end) - self.B_bar_ind = slice(A_bar_end, B_bar_end) - self.C_bar_ind = slice(B_bar_end, C_bar_end) - self.S_bar_ind = slice(C_bar_end, S_bar_end) - self.z_bar_ind = slice(S_bar_end, z_bar_end) - - self.f, self.A, self.B = m.f_func, m.A_func, m.B_func - - # integration initial condition - self.V0 = np.zeros((m.n_x * (1 + m.n_x + m.n_u + m.n_u + 2),)) - self.V0[self.A_bar_ind] = np.eye(m.n_x).reshape(-1) - - self.dt = 1. / (K - 1) - - def calculate_discretization(self, X, U, sigma): - """ - Calculate discretization for given states, inputs and total time. - - :param X: Matrix of states for all time points - :param U: Matrix of inputs for all time points - :param sigma: Total time - :return: The discretization matrices - """ - for k in range(self.K - 1): - self.V0[self.x_ind] = X[:, k] - V = np.array(odeint(self._ode_dVdt, self.V0, (0, self.dt), - args=(U[:, k], U[:, k + 1], sigma))[1, :]) - - # using \Phi_A(\tau_{k+1},\xi) = \Phi_A(\tau_{k+1},\tau_k)\Phi_A(\xi,\tau_k)^{-1} - # flatten matrices in column-major (Fortran) order for CVXPY - Phi = V[self.A_bar_ind].reshape((self.n_x, self.n_x)) - self.A_bar[:, k] = Phi.flatten(order='F') - self.B_bar[:, k] = np.matmul(Phi, V[self.B_bar_ind].reshape( - (self.n_x, self.n_u))).flatten(order='F') - self.C_bar[:, k] = np.matmul(Phi, V[self.C_bar_ind].reshape( - (self.n_x, self.n_u))).flatten(order='F') - self.S_bar[:, k] = np.matmul(Phi, V[self.S_bar_ind]) - self.z_bar[:, k] = np.matmul(Phi, V[self.z_bar_ind]) - - return self.A_bar, self.B_bar, self.C_bar, self.S_bar, self.z_bar - - def _ode_dVdt(self, V, t, u_t0, u_t1, sigma): - """ - ODE function to compute dVdt. - - :param V: Evaluation state V = [x, Phi_A, B_bar, C_bar, S_bar, z_bar] - :param t: Evaluation time - :param u_t0: Input at start of interval - :param u_t1: Input at end of interval - :param sigma: Total time - :return: Derivative at current time and state dVdt - """ - alpha = (self.dt - t) / self.dt - beta = t / self.dt - x = V[self.x_ind] - u = u_t0 + beta * (u_t1 - u_t0) - - # using \Phi_A(\tau_{k+1},\xi) = \Phi_A(\tau_{k+1},\tau_k)\Phi_A(\xi,\tau_k)^{-1} - # and pre-multiplying with \Phi_A(\tau_{k+1},\tau_k) after integration - Phi_A_xi = np.linalg.inv( - V[self.A_bar_ind].reshape((self.n_x, self.n_x))) - - A_subs = sigma * self.A(x, u) - B_subs = sigma * self.B(x, u) - f_subs = self.f(x, u) - - dVdt = np.zeros_like(V) - dVdt[self.x_ind] = sigma * f_subs.transpose() - dVdt[self.A_bar_ind] = np.matmul( - A_subs, V[self.A_bar_ind].reshape((self.n_x, self.n_x))).reshape(-1) - dVdt[self.B_bar_ind] = np.matmul(Phi_A_xi, B_subs).reshape(-1) * alpha - dVdt[self.C_bar_ind] = np.matmul(Phi_A_xi, B_subs).reshape(-1) * beta - dVdt[self.S_bar_ind] = np.matmul(Phi_A_xi, f_subs).transpose() - z_t = -np.matmul(A_subs, x) - np.matmul(B_subs, u) - dVdt[self.z_bar_ind] = np.dot(Phi_A_xi, z_t.T).flatten() - - return dVdt - - -class SCProblem: - """ - Defines a standard Successive Convexification problem and - adds the model specific constraints and objectives. - - :param m: The model object - :param K: Number of discretization points - """ - - def __init__(self, m, K): - # Variables: - self.var = dict() - self.var['X'] = cvxpy.Variable((m.n_x, K)) - self.var['U'] = cvxpy.Variable((m.n_u, K)) - self.var['sigma'] = cvxpy.Variable(nonneg=True) - self.var['nu'] = cvxpy.Variable((m.n_x, K - 1)) - self.var['delta_norm'] = cvxpy.Variable(nonneg=True) - self.var['sigma_norm'] = cvxpy.Variable(nonneg=True) - - # Parameters: - self.par = dict() - self.par['A_bar'] = cvxpy.Parameter((m.n_x * m.n_x, K - 1)) - self.par['B_bar'] = cvxpy.Parameter((m.n_x * m.n_u, K - 1)) - self.par['C_bar'] = cvxpy.Parameter((m.n_x * m.n_u, K - 1)) - self.par['S_bar'] = cvxpy.Parameter((m.n_x, K - 1)) - self.par['z_bar'] = cvxpy.Parameter((m.n_x, K - 1)) - - self.par['X_last'] = cvxpy.Parameter((m.n_x, K)) - self.par['U_last'] = cvxpy.Parameter((m.n_u, K)) - self.par['sigma_last'] = cvxpy.Parameter(nonneg=True) - - self.par['weight_sigma'] = cvxpy.Parameter(nonneg=True) - self.par['weight_delta'] = cvxpy.Parameter(nonneg=True) - self.par['weight_delta_sigma'] = cvxpy.Parameter(nonneg=True) - self.par['weight_nu'] = cvxpy.Parameter(nonneg=True) - - # Constraints: - constraints = [] - - # Model: - constraints += m.get_constraints( - self.var['X'], self.var['U'], self.par['X_last'], self.par['U_last']) - - # Dynamics: - # x_t+1 = A_*x_t+B_*U_t+C_*U_T+1*S_*sigma+zbar+nu - constraints += [ - self.var['X'][:, k + 1] == - cvxpy.reshape(self.par['A_bar'][:, k], (m.n_x, m.n_x), order='F') @ - self.var['X'][:, k] + - cvxpy.reshape(self.par['B_bar'][:, k], (m.n_x, m.n_u), order='F') @ - self.var['U'][:, k] + - cvxpy.reshape(self.par['C_bar'][:, k], (m.n_x, m.n_u), order='F') @ - self.var['U'][:, k + 1] + - self.par['S_bar'][:, k] * self.var['sigma'] + - self.par['z_bar'][:, k] + - self.var['nu'][:, k] - for k in range(K - 1) - ] - - # Trust regions: - dx = cvxpy.sum(cvxpy.square( - self.var['X'] - self.par['X_last']), axis=0) - du = cvxpy.sum(cvxpy.square( - self.var['U'] - self.par['U_last']), axis=0) - ds = self.var['sigma'] - self.par['sigma_last'] - constraints += [cvxpy.norm(dx + du, 1) <= self.var['delta_norm']] - constraints += [cvxpy.norm(ds, 'inf') <= self.var['sigma_norm']] - - # Flight time positive: - constraints += [self.var['sigma'] >= 0.1] - - # Objective: - sc_objective = cvxpy.Minimize( - self.par['weight_sigma'] * self.var['sigma'] + - self.par['weight_nu'] * cvxpy.norm(self.var['nu'], 'inf') + - self.par['weight_delta'] * self.var['delta_norm'] + - self.par['weight_delta_sigma'] * self.var['sigma_norm'] - ) - - objective = sc_objective - - self.prob = cvxpy.Problem(objective, constraints) - - def set_parameters(self, **kwargs): - """ - All parameters have to be filled before calling solve(). - Takes the following arguments as keywords: - - A_bar - B_bar - C_bar - S_bar - z_bar - X_last - U_last - sigma_last - E - weight_sigma - weight_nu - radius_trust_region - """ - - for key in kwargs: - if key in self.par: - self.par[key].value = kwargs[key] - else: - print(f'Parameter \'{key}\' does not exist.') - - def get_variable(self, name): - if name in self.var: - return self.var[name].value - else: - print(f'Variable \'{name}\' does not exist.') - return None - - def solve(self, **kwargs): - error = False - try: - with warnings.catch_warnings(): # For User warning from solver - warnings.simplefilter('ignore') - self.prob.solve(verbose=verbose_solver, - solver=solver) - except cvxpy.SolverError: - error = True - - stats = self.prob.solver_stats - - info = { - 'setup_time': stats.setup_time, - 'solver_time': stats.solve_time, - 'iterations': stats.num_iters, - 'solver_error': error - } - - return info - - -def axis3d_equal(X, Y, Z, ax): - - max_range = np.array([X.max() - X.min(), Y.max() - - Y.min(), Z.max() - Z.min()]).max() - Xb = 0.5 * max_range * np.mgrid[-1:2:2, -1:2:2, - - 1:2:2][0].flatten() + 0.5 * (X.max() + X.min()) - Yb = 0.5 * max_range * np.mgrid[-1:2:2, -1:2:2, - - 1:2:2][1].flatten() + 0.5 * (Y.max() + Y.min()) - Zb = 0.5 * max_range * np.mgrid[-1:2:2, -1:2:2, - - 1:2:2][2].flatten() + 0.5 * (Z.max() + Z.min()) - # Comment or uncomment following both lines to test the fake bounding box: - for xb, yb, zb in zip(Xb, Yb, Zb): - ax.plot([xb], [yb], [zb], 'w') - - -def plot_animation(X, U): # pragma: no cover - - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - # for stopping simulation with the esc key. - fig.canvas.mpl_connect('key_release_event', - lambda event: [exit(0) if event.key == 'escape' else None]) - - for k in range(K): - plt.cla() - ax.plot(X[2, :], X[3, :], X[1, :]) # trajectory - ax.scatter3D([0.0], [0.0], [0.0], c="r", - marker="x") # target landing point - axis3d_equal(X[2, :], X[3, :], X[1, :], ax) - - rx, ry, rz = X[1:4, k] - # vx, vy, vz = X[4:7, k] - qw, qx, qy, qz = X[7:11, k] - - CBI = np.array([ - [1 - 2 * (qy ** 2 + qz ** 2), 2 * (qx * qy + qw * qz), - 2 * (qx * qz - qw * qy)], - [2 * (qx * qy - qw * qz), 1 - 2 - * (qx ** 2 + qz ** 2), 2 * (qy * qz + qw * qx)], - [2 * (qx * qz + qw * qy), 2 * (qy * qz - qw * qx), - 1 - 2 * (qx ** 2 + qy ** 2)] - ]) - - Fx, Fy, Fz = np.dot(np.transpose(CBI), U[:, k]) - dx, dy, dz = np.dot(np.transpose(CBI), np.array([1., 0., 0.])) - - # attitude vector - ax.quiver(ry, rz, rx, dy, dz, dx, length=0.5, linewidth=3.0, - arrow_length_ratio=0.0, color='black') - - # thrust vector - ax.quiver(ry, rz, rx, -Fy, -Fz, -Fx, length=0.1, - arrow_length_ratio=0.0, color='red') - - ax.set_title("Rocket powered landing") - plt.pause(0.5) - - -def main(rng=None): - print("start!!") - m = Rocket_Model_6DoF(rng) - - # state and input list - X = np.empty(shape=[m.n_x, K]) - U = np.empty(shape=[m.n_u, K]) - - # INITIALIZATION - sigma = m.t_f_guess - X, U = m.initialize_trajectory(X, U) - - integrator = Integrator(m, K) - problem = SCProblem(m, K) - - converged = False - w_delta = W_DELTA - for it in range(iterations): - t0_it = time() - print('-' * 18 + f' Iteration {str(it + 1).zfill(2)} ' + '-' * 18) - - A_bar, B_bar, C_bar, S_bar, z_bar = integrator.calculate_discretization( - X, U, sigma) - - problem.set_parameters(A_bar=A_bar, B_bar=B_bar, C_bar=C_bar, S_bar=S_bar, z_bar=z_bar, - X_last=X, U_last=U, sigma_last=sigma, - weight_sigma=W_SIGMA, weight_nu=W_NU, - weight_delta=w_delta, weight_delta_sigma=W_DELTA_SIGMA) - problem.solve() - - X = problem.get_variable('X') - U = problem.get_variable('U') - sigma = problem.get_variable('sigma') - - delta_norm = problem.get_variable('delta_norm') - sigma_norm = problem.get_variable('sigma_norm') - nu_norm = np.linalg.norm(problem.get_variable('nu'), np.inf) - - print('delta_norm', delta_norm) - print('sigma_norm', sigma_norm) - print('nu_norm', nu_norm) - - if delta_norm < 1e-3 and sigma_norm < 1e-3 and nu_norm < 1e-7: - converged = True - - w_delta *= 1.5 - - print('Time for iteration', time() - t0_it, 's') - - if converged: - print(f'Converged after {it + 1} iterations.') - break - - if show_animation: # pragma: no cover - plot_animation(X, U) - - print("done!!") - - -if __name__ == '__main__': - main() diff --git a/ArmNavigation/__init__.py b/ArmNavigation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation.py b/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation.py deleted file mode 100644 index 9047c13851..0000000000 --- a/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -Obstacle navigation using A* on a toroidal grid - -Author: Daniel Ingram (daniel-s-ingram) -""" -from math import pi -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.colors import from_levels_and_colors - -plt.ion() - -# Simulation parameters -M = 100 -obstacles = [[1.75, 0.75, 0.6], [0.55, 1.5, 0.5], [0, -1, 0.25]] - - -def main(): - arm = NLinkArm([1, 1], [0, 0]) - start = (10, 50) - goal = (58, 56) - grid = get_occupancy_grid(arm, obstacles) - plt.imshow(grid) - plt.show() - route = astar_torus(grid, start, goal) - for node in route: - theta1 = 2 * pi * node[0] / M - pi - theta2 = 2 * pi * node[1] / M - pi - arm.update_joints([theta1, theta2]) - arm.plot(obstacles=obstacles) - - -def detect_collision(line_seg, circle): - """ - Determines whether a line segment (arm link) is in contact - with a circle (obstacle). - Credit to: https://web.archive.org/web/20200130224918/http://doswa.com/2009/07/13/circle-segment-intersectioncollision.html - Args: - line_seg: List of coordinates of line segment endpoints e.g. [[1, 1], [2, 2]] - circle: List of circle coordinates and radius e.g. [0, 0, 0.5] is a circle centered - at the origin with radius 0.5 - - Returns: - True if the line segment is in contact with the circle - False otherwise - """ - a_vec = np.array([line_seg[0][0], line_seg[0][1]]) - b_vec = np.array([line_seg[1][0], line_seg[1][1]]) - c_vec = np.array([circle[0], circle[1]]) - radius = circle[2] - line_vec = b_vec - a_vec - line_mag = np.linalg.norm(line_vec) - circle_vec = c_vec - a_vec - proj = circle_vec.dot(line_vec / line_mag) - if proj <= 0: - closest_point = a_vec - elif proj >= line_mag: - closest_point = b_vec - else: - closest_point = a_vec + line_vec * proj / line_mag - if np.linalg.norm(closest_point - c_vec) > radius: - return False - - return True - - -def get_occupancy_grid(arm, obstacles): - """ - Discretizes joint space into M values from -pi to +pi - and determines whether a given coordinate in joint space - would result in a collision between a robot arm and obstacles - in its environment. - - Args: - arm: An instance of NLinkArm - obstacles: A list of obstacles, with each obstacle defined as a list - of xy coordinates and a radius. - - Returns: - Occupancy grid in joint space - """ - grid = [[0 for _ in range(M)] for _ in range(M)] - theta_list = [2 * i * pi / M for i in range(-M // 2, M // 2 + 1)] - for i in range(M): - for j in range(M): - arm.update_joints([theta_list[i], theta_list[j]]) - points = arm.points - collision_detected = False - for k in range(len(points) - 1): - for obstacle in obstacles: - line_seg = [points[k], points[k + 1]] - collision_detected = detect_collision(line_seg, obstacle) - if collision_detected: - break - if collision_detected: - break - grid[i][j] = int(collision_detected) - return np.array(grid) - - -def astar_torus(grid, start_node, goal_node): - """ - Finds a path between an initial and goal joint configuration using - the A* Algorithm on a tororiadal grid. - - Args: - grid: An occupancy grid (ndarray) - start_node: Initial joint configuration (tuple) - goal_node: Goal joint configuration (tuple) - - Returns: - Obstacle-free route in joint space from start_node to goal_node - """ - colors = ['white', 'black', 'red', 'pink', 'yellow', 'green', 'orange'] - levels = [0, 1, 2, 3, 4, 5, 6, 7] - cmap, norm = from_levels_and_colors(levels, colors) - - grid[start_node] = 4 - grid[goal_node] = 5 - - parent_map = [[() for _ in range(M)] for _ in range(M)] - - heuristic_map = calc_heuristic_map(M, goal_node) - - explored_heuristic_map = np.full((M, M), np.inf) - distance_map = np.full((M, M), np.inf) - explored_heuristic_map[start_node] = heuristic_map[start_node] - distance_map[start_node] = 0 - while True: - grid[start_node] = 4 - grid[goal_node] = 5 - - current_node = np.unravel_index( - np.argmin(explored_heuristic_map, axis=None), explored_heuristic_map.shape) - min_distance = np.min(explored_heuristic_map) - if (current_node == goal_node) or np.isinf(min_distance): - break - - grid[current_node] = 2 - explored_heuristic_map[current_node] = np.inf - - i, j = current_node[0], current_node[1] - - neighbors = find_neighbors(i, j) - - for neighbor in neighbors: - if grid[neighbor] == 0 or grid[neighbor] == 5: - distance_map[neighbor] = distance_map[current_node] + 1 - explored_heuristic_map[neighbor] = heuristic_map[neighbor] - parent_map[neighbor[0]][neighbor[1]] = current_node - grid[neighbor] = 3 - - if np.isinf(explored_heuristic_map[goal_node]): - route = [] - print("No route found.") - else: - route = [goal_node] - while parent_map[route[0][0]][route[0][1]] != (): - route.insert(0, parent_map[route[0][0]][route[0][1]]) - - print(f"The route found covers {len(route)} grid cells.") - for i in range(1, len(route)): - grid[route[i]] = 6 - 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]) - plt.imshow(grid, cmap=cmap, norm=norm, interpolation=None) - plt.show() - plt.pause(1e-2) - - return route - - -def find_neighbors(i, j): - neighbors = [] - if i - 1 >= 0: - neighbors.append((i - 1, j)) - else: - neighbors.append((M - 1, j)) - - if i + 1 < M: - neighbors.append((i + 1, j)) - else: - neighbors.append((0, j)) - - if j - 1 >= 0: - neighbors.append((i, j - 1)) - else: - neighbors.append((i, M - 1)) - - if j + 1 < M: - neighbors.append((i, j + 1)) - else: - neighbors.append((i, 0)) - - return neighbors - - -def calc_heuristic_map(M, goal_node): - X, Y = np.meshgrid([i for i in range(M)], [i for i in range(M)]) - heuristic_map = np.abs(X - goal_node[1]) + np.abs(Y - goal_node[0]) - for i in range(heuristic_map.shape[0]): - for j in range(heuristic_map.shape[1]): - heuristic_map[i, j] = min(heuristic_map[i, j], - M - i - 1 + heuristic_map[M - 1, j], - i + heuristic_map[0, j], - M - j - 1 + heuristic_map[i, M - 1], - j + heuristic_map[i, 0] - ) - - return heuristic_map - - -class NLinkArm: - """ - Class for controlling and plotting a planar arm with an arbitrary number of links. - """ - - def __init__(self, link_lengths, joint_angles): - self.n_links = len(link_lengths) - if self.n_links != len(joint_angles): - raise ValueError() - - self.link_lengths = np.array(link_lengths) - self.joint_angles = np.array(joint_angles) - self.points = [[0, 0] for _ in range(self.n_links + 1)] - - self.lim = sum(link_lengths) - self.update_points() - - def update_joints(self, joint_angles): - self.joint_angles = joint_angles - self.update_points() - - def update_points(self): - for i in range(1, self.n_links + 1): - self.points[i][0] = self.points[i - 1][0] + \ - self.link_lengths[i - 1] * \ - np.cos(np.sum(self.joint_angles[:i])) - self.points[i][1] = self.points[i - 1][1] + \ - self.link_lengths[i - 1] * \ - np.sin(np.sum(self.joint_angles[:i])) - - self.end_effector = np.array(self.points[self.n_links]).T - - def plot(self, obstacles=[]): # pragma: no cover - plt.cla() - - for obstacle in obstacles: - circle = plt.Circle( - (obstacle[0], obstacle[1]), radius=0.5 * obstacle[2], fc='k') - plt.gca().add_patch(circle) - - for i in range(self.n_links + 1): - if i is not self.n_links: - plt.plot([self.points[i][0], self.points[i + 1][0]], - [self.points[i][1], self.points[i + 1][1]], 'r-') - plt.plot(self.points[i][0], self.points[i][1], 'k.') - - plt.xlim([-self.lim, self.lim]) - plt.ylim([-self.lim, self.lim]) - plt.draw() - plt.pause(1e-5) - - -if __name__ == '__main__': - main() diff --git a/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation_2.py b/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation_2.py deleted file mode 100644 index f5d435082a..0000000000 --- a/ArmNavigation/arm_obstacle_navigation/arm_obstacle_navigation_2.py +++ /dev/null @@ -1,299 +0,0 @@ -""" -Obstacle navigation using A* on a toroidal grid - -Author: Daniel Ingram (daniel-s-ingram) - Tullio Facchinetti (tullio.facchinetti@unipv.it) -""" -from math import pi -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.colors import from_levels_and_colors -import sys - -plt.ion() - -# Simulation parameters -M = 100 -obstacles = [[1.75, 0.75, 0.6], [0.55, 1.5, 0.5], [0, -1, 0.7]] - - -def press(event): - """Exit from the simulation.""" - if event.key == 'q' or event.key == 'Q': - print('Quitting upon request.') - sys.exit(0) - - -def main(): - # Arm geometry in the working space - link_length = [0.5, 1.5] - initial_link_angle = [0, 0] - arm = NLinkArm(link_length, initial_link_angle) - # (x, y) co-ordinates in the joint space [cell] - start = (10, 50) - goal = (58, 56) - grid = get_occupancy_grid(arm, obstacles) - route = astar_torus(grid, start, goal) - if route: - animate(grid, arm, route) - - -def animate(grid, arm, route): - fig, axs = plt.subplots(1, 2) - fig.canvas.mpl_connect('key_press_event', press) - colors = ['white', 'black', 'red', 'pink', 'yellow', 'green', 'orange'] - levels = [0, 1, 2, 3, 4, 5, 6, 7] - cmap, norm = from_levels_and_colors(levels, colors) - for i, node in enumerate(route): - plt.subplot(1, 2, 1) - grid[node] = 6 - plt.cla() - plt.imshow(grid, cmap=cmap, norm=norm, interpolation=None) - theta1 = 2 * pi * node[0] / M - pi - theta2 = 2 * pi * node[1] / M - pi - arm.update_joints([theta1, theta2]) - plt.subplot(1, 2, 2) - arm.plot_arm(plt, obstacles=obstacles) - plt.xlim(-2.0, 2.0) - plt.ylim(-3.0, 3.0) - plt.show() - # Uncomment here to save the sequence of frames - # plt.savefig('frame{:04d}.png'.format(i)) - plt.pause(0.1) - - -def detect_collision(line_seg, circle): - """ - Determines whether a line segment (arm link) is in contact - with a circle (obstacle). - Credit to: https://web.archive.org/web/20200130224918/http://doswa.com/2009/07/13/circle-segment-intersectioncollision.html - Args: - line_seg: List of coordinates of line segment endpoints e.g. [[1, 1], [2, 2]] - circle: List of circle coordinates and radius e.g. [0, 0, 0.5] is a circle centered - at the origin with radius 0.5 - - Returns: - True if the line segment is in contact with the circle - False otherwise - """ - a_vec = np.array([line_seg[0][0], line_seg[0][1]]) - b_vec = np.array([line_seg[1][0], line_seg[1][1]]) - c_vec = np.array([circle[0], circle[1]]) - radius = circle[2] - line_vec = b_vec - a_vec - line_mag = np.linalg.norm(line_vec) - circle_vec = c_vec - a_vec - proj = circle_vec.dot(line_vec / line_mag) - if proj <= 0: - closest_point = a_vec - elif proj >= line_mag: - closest_point = b_vec - else: - closest_point = a_vec + line_vec * proj / line_mag - if np.linalg.norm(closest_point - c_vec) > radius: - return False - return True - - -def get_occupancy_grid(arm, obstacles): - """ - Discretizes joint space into M values from -pi to +pi - and determines whether a given coordinate in joint space - would result in a collision between a robot arm and obstacles - in its environment. - - Args: - arm: An instance of NLinkArm - obstacles: A list of obstacles, with each obstacle defined as a list - of xy coordinates and a radius. - - Returns: - Occupancy grid in joint space - """ - grid = [[0 for _ in range(M)] for _ in range(M)] - theta_list = [2 * i * pi / M for i in range(-M // 2, M // 2 + 1)] - for i in range(M): - for j in range(M): - arm.update_joints([theta_list[i], theta_list[j]]) - points = arm.points - collision_detected = False - for k in range(len(points) - 1): - for obstacle in obstacles: - line_seg = [points[k], points[k + 1]] - collision_detected = detect_collision(line_seg, obstacle) - if collision_detected: - break - if collision_detected: - break - grid[i][j] = int(collision_detected) - return np.array(grid) - - -def astar_torus(grid, start_node, goal_node): - """ - Finds a path between an initial and goal joint configuration using - the A* Algorithm on a tororiadal grid. - - Args: - grid: An occupancy grid (ndarray) - start_node: Initial joint configuration (tuple) - goal_node: Goal joint configuration (tuple) - - Returns: - Obstacle-free route in joint space from start_node to goal_node - """ - colors = ['white', 'black', 'red', 'pink', 'yellow', 'green', 'orange'] - levels = [0, 1, 2, 3, 4, 5, 6, 7] - cmap, norm = from_levels_and_colors(levels, colors) - - grid[start_node] = 4 - grid[goal_node] = 5 - - parent_map = [[() for _ in range(M)] for _ in range(M)] - - heuristic_map = calc_heuristic_map(M, goal_node) - - explored_heuristic_map = np.full((M, M), np.inf) - distance_map = np.full((M, M), np.inf) - explored_heuristic_map[start_node] = heuristic_map[start_node] - distance_map[start_node] = 0 - while True: - grid[start_node] = 4 - grid[goal_node] = 5 - - current_node = np.unravel_index( - np.argmin(explored_heuristic_map, axis=None), explored_heuristic_map.shape) - min_distance = np.min(explored_heuristic_map) - if (current_node == goal_node) or np.isinf(min_distance): - break - - grid[current_node] = 2 - explored_heuristic_map[current_node] = np.inf - - i, j = current_node[0], current_node[1] - - neighbors = find_neighbors(i, j) - - for neighbor in neighbors: - if grid[neighbor] == 0 or grid[neighbor] == 5: - distance_map[neighbor] = distance_map[current_node] + 1 - explored_heuristic_map[neighbor] = heuristic_map[neighbor] - parent_map[neighbor[0]][neighbor[1]] = current_node - grid[neighbor] = 3 - - if np.isinf(explored_heuristic_map[goal_node]): - route = [] - print("No route found.") - else: - route = [goal_node] - while parent_map[route[0][0]][route[0][1]] != (): - route.insert(0, parent_map[route[0][0]][route[0][1]]) - - print(f"The route found covers {len(route)} grid cells.") - for i in range(1, len(route)): - grid[route[i]] = 6 - 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]) - plt.imshow(grid, cmap=cmap, norm=norm, interpolation=None) - plt.show() - plt.pause(1e-2) - - return route - - -def find_neighbors(i, j): - neighbors = [] - if i - 1 >= 0: - neighbors.append((i - 1, j)) - else: - neighbors.append((M - 1, j)) - - if i + 1 < M: - neighbors.append((i + 1, j)) - else: - neighbors.append((0, j)) - - if j - 1 >= 0: - neighbors.append((i, j - 1)) - else: - neighbors.append((i, M - 1)) - - if j + 1 < M: - neighbors.append((i, j + 1)) - else: - neighbors.append((i, 0)) - - return neighbors - - -def calc_heuristic_map(M, goal_node): - X, Y = np.meshgrid([i for i in range(M)], [i for i in range(M)]) - heuristic_map = np.abs(X - goal_node[1]) + np.abs(Y - goal_node[0]) - for i in range(heuristic_map.shape[0]): - for j in range(heuristic_map.shape[1]): - heuristic_map[i, j] = min(heuristic_map[i, j], - M - i - 1 + heuristic_map[M - 1, j], - i + heuristic_map[0, j], - M - j - 1 + heuristic_map[i, M - 1], - j + heuristic_map[i, 0] - ) - - return heuristic_map - - -class NLinkArm: - """ - Class for controlling and plotting a planar arm with an arbitrary number of links. - """ - - def __init__(self, link_lengths, joint_angles): - self.n_links = len(link_lengths) - if self.n_links != len(joint_angles): - raise ValueError() - - self.link_lengths = np.array(link_lengths) - self.joint_angles = np.array(joint_angles) - self.points = [[0, 0] for _ in range(self.n_links + 1)] - - self.lim = sum(link_lengths) - self.update_points() - - def update_joints(self, joint_angles): - self.joint_angles = joint_angles - self.update_points() - - def update_points(self): - for i in range(1, self.n_links + 1): - self.points[i][0] = self.points[i - 1][0] + \ - self.link_lengths[i - 1] * \ - np.cos(np.sum(self.joint_angles[:i])) - self.points[i][1] = self.points[i - 1][1] + \ - self.link_lengths[i - 1] * \ - np.sin(np.sum(self.joint_angles[:i])) - - self.end_effector = np.array(self.points[self.n_links]).T - - def plot_arm(self, myplt, obstacles=[]): # pragma: no cover - myplt.cla() - - for obstacle in obstacles: - circle = myplt.Circle( - (obstacle[0], obstacle[1]), radius=0.5 * obstacle[2], fc='k') - myplt.gca().add_patch(circle) - - for i in range(self.n_links + 1): - if i is not self.n_links: - myplt.plot([self.points[i][0], self.points[i + 1][0]], - [self.points[i][1], self.points[i + 1][1]], 'r-') - myplt.plot(self.points[i][0], self.points[i][1], 'k.') - - myplt.xlim([-self.lim, self.lim]) - myplt.ylim([-self.lim, self.lim]) - myplt.draw() - # myplt.pause(1e-5) - - -if __name__ == '__main__': - main() diff --git a/ArmNavigation/n_joint_arm_3d/NLinkArm3d.py b/ArmNavigation/n_joint_arm_3d/NLinkArm3d.py deleted file mode 100644 index 0459e234b2..0000000000 --- a/ArmNavigation/n_joint_arm_3d/NLinkArm3d.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -Class of n-link arm in 3D -Author: Takayuki Murooka (takayuki5168) -""" -import numpy as np -import math -from mpl_toolkits.mplot3d import Axes3D -import matplotlib.pyplot as plt - - -class Link: - def __init__(self, dh_params): - self.dh_params_ = dh_params - - def transformation_matrix(self): - theta = self.dh_params_[0] - alpha = self.dh_params_[1] - a = self.dh_params_[2] - d = self.dh_params_[3] - - st = math.sin(theta) - ct = math.cos(theta) - sa = math.sin(alpha) - ca = math.cos(alpha) - trans = np.array([[ct, -st * ca, st * sa, a * ct], - [st, ct * ca, -ct * sa, a * st], - [0, sa, ca, d], - [0, 0, 0, 1]]) - - return trans - - @staticmethod - def basic_jacobian(trans_prev, ee_pos): - pos_prev = np.array( - [trans_prev[0, 3], trans_prev[1, 3], trans_prev[2, 3]]) - z_axis_prev = np.array( - [trans_prev[0, 2], trans_prev[1, 2], trans_prev[2, 2]]) - - basic_jacobian = np.hstack( - (np.cross(z_axis_prev, ee_pos - pos_prev), z_axis_prev)) - return basic_jacobian - - -class NLinkArm: - def __init__(self, dh_params_list): - self.link_list = [] - for i in range(len(dh_params_list)): - self.link_list.append(Link(dh_params_list[i])) - - def transformation_matrix(self): - trans = np.identity(4) - for i in range(len(self.link_list)): - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - return trans - - def forward_kinematics(self, plot=False): - trans = self.transformation_matrix() - - x = trans[0, 3] - y = trans[1, 3] - z = trans[2, 3] - alpha, beta, gamma = self.euler_angle() - - if plot: - self.fig = plt.figure() - self.ax = Axes3D(self.fig, auto_add_to_figure=False) - self.fig.add_axes(self.ax) - - x_list = [] - y_list = [] - z_list = [] - - trans = np.identity(4) - - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - for i in range(len(self.link_list)): - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - - self.ax.plot(x_list, y_list, z_list, "o-", color="#00aa00", ms=4, - mew=0.5) - self.ax.plot([0], [0], [0], "o") - - self.ax.set_xlim(-1, 1) - self.ax.set_ylim(-1, 1) - self.ax.set_zlim(-1, 1) - - plt.show() - - return [x, y, z, alpha, beta, gamma] - - def basic_jacobian(self): - ee_pos = self.forward_kinematics()[0:3] - basic_jacobian_mat = [] - - trans = np.identity(4) - for i in range(len(self.link_list)): - basic_jacobian_mat.append( - self.link_list[i].basic_jacobian(trans, ee_pos)) - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - - return np.array(basic_jacobian_mat).T - - def inverse_kinematics(self, ref_ee_pose, plot=False): - for cnt in range(500): - ee_pose = self.forward_kinematics() - diff_pose = np.array(ref_ee_pose) - ee_pose - - basic_jacobian_mat = self.basic_jacobian() - alpha, beta, gamma = self.euler_angle() - - K_zyz = np.array( - [[0, -math.sin(alpha), math.cos(alpha) * math.sin(beta)], - [0, math.cos(alpha), math.sin(alpha) * math.sin(beta)], - [1, 0, math.cos(beta)]]) - K_alpha = np.identity(6) - K_alpha[3:, 3:] = K_zyz - - theta_dot = np.dot( - np.dot(np.linalg.pinv(basic_jacobian_mat), K_alpha), - np.array(diff_pose)) - self.update_joint_angles(theta_dot / 100.) - - if plot: - self.fig = plt.figure() - self.ax = Axes3D(self.fig, auto_add_to_figure=False) - self.fig.add_axes(self.ax) - - x_list = [] - y_list = [] - z_list = [] - - trans = np.identity(4) - - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - for i in range(len(self.link_list)): - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - - self.ax.plot(x_list, y_list, z_list, "o-", color="#00aa00", ms=4, - mew=0.5) - self.ax.plot([0], [0], [0], "o") - - self.ax.set_xlim(-1, 1) - self.ax.set_ylim(-1, 1) - self.ax.set_zlim(-1, 1) - - self.ax.plot([ref_ee_pose[0]], [ref_ee_pose[1]], [ref_ee_pose[2]], - "o") - plt.show() - - def euler_angle(self): - trans = self.transformation_matrix() - - alpha = math.atan2(trans[1][2], trans[0][2]) - if not (-math.pi / 2 <= alpha <= math.pi / 2): - alpha = math.atan2(trans[1][2], trans[0][2]) + math.pi - if not (-math.pi / 2 <= alpha <= math.pi / 2): - alpha = math.atan2(trans[1][2], trans[0][2]) - math.pi - beta = math.atan2( - trans[0][2] * math.cos(alpha) + trans[1][2] * math.sin(alpha), - trans[2][2]) - gamma = math.atan2( - -trans[0][0] * math.sin(alpha) + trans[1][0] * math.cos(alpha), - -trans[0][1] * math.sin(alpha) + trans[1][1] * math.cos(alpha)) - - return alpha, beta, gamma - - def set_joint_angles(self, joint_angle_list): - for i in range(len(self.link_list)): - self.link_list[i].dh_params_[0] = joint_angle_list[i] - - def update_joint_angles(self, diff_joint_angle_list): - for i in range(len(self.link_list)): - self.link_list[i].dh_params_[0] += diff_joint_angle_list[i] - - def plot(self): - self.fig = plt.figure() - self.ax = Axes3D(self.fig) - - x_list = [] - y_list = [] - z_list = [] - - trans = np.identity(4) - - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - for i in range(len(self.link_list)): - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - - self.ax.plot(x_list, y_list, z_list, "o-", color="#00aa00", ms=4, - mew=0.5) - self.ax.plot([0], [0], [0], "o") - - self.ax.set_xlabel("x") - self.ax.set_ylabel("y") - self.ax.set_zlabel("z") - - self.ax.set_xlim(-1, 1) - self.ax.set_ylim(-1, 1) - self.ax.set_zlim(-1, 1) - plt.show() diff --git a/ArmNavigation/n_joint_arm_3d/__init__.py b/ArmNavigation/n_joint_arm_3d/__init__.py deleted file mode 100644 index 2194d4c303..0000000000 --- a/ArmNavigation/n_joint_arm_3d/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent)) diff --git a/ArmNavigation/n_joint_arm_3d/random_forward_kinematics.py b/ArmNavigation/n_joint_arm_3d/random_forward_kinematics.py deleted file mode 100644 index f9caace300..0000000000 --- a/ArmNavigation/n_joint_arm_3d/random_forward_kinematics.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Forward Kinematics for an n-link arm in 3D -Author: Takayuki Murooka (takayuki5168) -""" -import math -from NLinkArm3d import NLinkArm -import random - - -def random_val(min_val, max_val): - return min_val + random.random() * (max_val - min_val) - - -def main(): - print("Start solving Forward Kinematics 10 times") - # init NLinkArm with Denavit-Hartenberg parameters of PR2 - n_link_arm = NLinkArm([[0., -math.pi / 2, .1, 0.], - [math.pi / 2, math.pi / 2, 0., 0.], - [0., -math.pi / 2, 0., .4], - [0., math.pi / 2, 0., 0.], - [0., -math.pi / 2, 0., .321], - [0., math.pi / 2, 0., 0.], - [0., 0., 0., 0.]]) - # execute FK 10 times - for _ in range(10): - n_link_arm.set_joint_angles( - [random_val(-1, 1) for _ in range(len(n_link_arm.link_list))]) - - n_link_arm.forward_kinematics(plot=True) - - -if __name__ == "__main__": - main() diff --git a/ArmNavigation/n_joint_arm_3d/random_inverse_kinematics.py b/ArmNavigation/n_joint_arm_3d/random_inverse_kinematics.py deleted file mode 100644 index 91f6f1bba0..0000000000 --- a/ArmNavigation/n_joint_arm_3d/random_inverse_kinematics.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Inverse Kinematics for an n-link arm in 3D -Author: Takayuki Murooka (takayuki5168) -""" -import math -from NLinkArm3d import NLinkArm -import random - - -def random_val(min_val, max_val): - return min_val + random.random() * (max_val - min_val) - - -def main(): - print("Start solving Inverse Kinematics 10 times") - # init NLinkArm with Denavit-Hartenberg parameters of PR2 - n_link_arm = NLinkArm([[0., -math.pi / 2, .1, 0.], - [math.pi / 2, math.pi / 2, 0., 0.], - [0., -math.pi / 2, 0., .4], - [0., math.pi / 2, 0., 0.], - [0., -math.pi / 2, 0., .321], - [0., math.pi / 2, 0., 0.], - [0., 0., 0., 0.]]) - # execute IK 10 times - for _ in range(10): - n_link_arm.inverse_kinematics([random_val(-0.5, 0.5), - random_val(-0.5, 0.5), - random_val(-0.5, 0.5), - random_val(-0.5, 0.5), - random_val(-0.5, 0.5), - random_val(-0.5, 0.5)], plot=True) - - -if __name__ == "__main__": - main() diff --git a/ArmNavigation/n_joint_arm_to_point_control/NLinkArm.py b/ArmNavigation/n_joint_arm_to_point_control/NLinkArm.py deleted file mode 100644 index 854ade9038..0000000000 --- a/ArmNavigation/n_joint_arm_to_point_control/NLinkArm.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -Class for controlling and plotting an arm with an arbitrary number of links. - -Author: Daniel Ingram -""" - -import numpy as np -import matplotlib.pyplot as plt - - -class NLinkArm(object): - def __init__(self, link_lengths, joint_angles, goal, show_animation): - self.show_animation = show_animation - self.n_links = len(link_lengths) - if self.n_links != len(joint_angles): - raise ValueError() - - self.link_lengths = np.array(link_lengths) - self.joint_angles = np.array(joint_angles) - self.points = [[0, 0] for _ in range(self.n_links + 1)] - - self.lim = sum(link_lengths) - self.goal = np.array(goal).T - - if show_animation: # pragma: no cover - self.fig = plt.figure() - self.fig.canvas.mpl_connect('button_press_event', self.click) - - plt.ion() - plt.show() - - self.update_points() - - def update_joints(self, joint_angles): - self.joint_angles = joint_angles - - self.update_points() - - def update_points(self): - for i in range(1, self.n_links + 1): - self.points[i][0] = self.points[i - 1][0] + \ - self.link_lengths[i - 1] * \ - np.cos(np.sum(self.joint_angles[:i])) - self.points[i][1] = self.points[i - 1][1] + \ - self.link_lengths[i - 1] * \ - np.sin(np.sum(self.joint_angles[:i])) - - self.end_effector = np.array(self.points[self.n_links]).T - if self.show_animation: # pragma: no cover - self.plot() - - def plot(self): # pragma: no cover - 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]) - - for i in range(self.n_links + 1): - if i is not self.n_links: - plt.plot([self.points[i][0], self.points[i + 1][0]], - [self.points[i][1], self.points[i + 1][1]], 'r-') - plt.plot(self.points[i][0], self.points[i][1], 'ko') - - plt.plot(self.goal[0], self.goal[1], 'gx') - - plt.plot([self.end_effector[0], self.goal[0]], [ - self.end_effector[1], self.goal[1]], 'g--') - - plt.xlim([-self.lim, self.lim]) - plt.ylim([-self.lim, self.lim]) - plt.draw() - plt.pause(0.0001) - - def click(self, event): - self.goal = np.array([event.xdata, event.ydata]).T - self.plot() diff --git a/ArmNavigation/n_joint_arm_to_point_control/__init__.py b/ArmNavigation/n_joint_arm_to_point_control/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ArmNavigation/n_joint_arm_to_point_control/n_joint_arm_to_point_control.py b/ArmNavigation/n_joint_arm_to_point_control/n_joint_arm_to_point_control.py deleted file mode 100644 index a237523336..0000000000 --- a/ArmNavigation/n_joint_arm_to_point_control/n_joint_arm_to_point_control.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Inverse kinematics for an n-link arm using the Jacobian inverse method - -Author: Daniel Ingram (daniel-s-ingram) - Atsushi Sakai (@Atsushi_twi) -""" - -import sys -from pathlib import Path -sys.path.append(str(Path(__file__).parent.parent.parent)) - -import numpy as np -from ArmNavigation.n_joint_arm_to_point_control.NLinkArm import NLinkArm -from utils.angle import angle_mod - -# Simulation parameters -Kp = 2 -dt = 0.1 -N_LINKS = 10 -N_ITERATIONS = 10000 - -# States -WAIT_FOR_NEW_GOAL = 1 -MOVING_TO_GOAL = 2 - -show_animation = True - - -def main(): # pragma: no cover - """ - Creates an arm using the NLinkArm class and uses its inverse kinematics - to move it to the desired position. - """ - link_lengths = [1] * N_LINKS - joint_angles = np.array([0] * N_LINKS) - goal_pos = [N_LINKS, 0] - arm = NLinkArm(link_lengths, joint_angles, goal_pos, show_animation) - state = WAIT_FOR_NEW_GOAL - solution_found = False - while True: - old_goal = np.array(goal_pos) - goal_pos = np.array(arm.goal) - end_effector = arm.end_effector - errors, distance = distance_to_goal(end_effector, goal_pos) - - # State machine to allow changing of goal before current goal has been reached - if state is WAIT_FOR_NEW_GOAL: - if distance > 0.1 and not solution_found: - joint_goal_angles, solution_found = inverse_kinematics( - link_lengths, joint_angles, goal_pos) - if not solution_found: - print("Solution could not be found.") - state = WAIT_FOR_NEW_GOAL - arm.goal = end_effector - elif solution_found: - state = MOVING_TO_GOAL - elif state is MOVING_TO_GOAL: - if distance > 0.1 and all(old_goal == goal_pos): - joint_angles = joint_angles + Kp * \ - ang_diff(joint_goal_angles, joint_angles) * dt - else: - state = WAIT_FOR_NEW_GOAL - solution_found = False - - arm.update_joints(joint_angles) - - -def inverse_kinematics(link_lengths, joint_angles, goal_pos): - """ - Calculates the inverse kinematics using the Jacobian inverse method. - """ - for iteration in range(N_ITERATIONS): - current_pos = forward_kinematics(link_lengths, joint_angles) - errors, distance = distance_to_goal(current_pos, goal_pos) - if distance < 0.1: - print("Solution found in %d iterations." % iteration) - return joint_angles, True - J = jacobian_inverse(link_lengths, joint_angles) - joint_angles = joint_angles + np.matmul(J, errors) - return joint_angles, False - - -def get_random_goal(): - from random import random - SAREA = 15.0 - return [SAREA * random() - SAREA / 2.0, - SAREA * random() - SAREA / 2.0] - - -def animation(): - link_lengths = [1] * N_LINKS - joint_angles = np.array([0] * N_LINKS) - goal_pos = get_random_goal() - arm = NLinkArm(link_lengths, joint_angles, goal_pos, show_animation) - state = WAIT_FOR_NEW_GOAL - solution_found = False - - i_goal = 0 - while True: - old_goal = np.array(goal_pos) - goal_pos = np.array(arm.goal) - end_effector = arm.end_effector - errors, distance = distance_to_goal(end_effector, goal_pos) - - # State machine to allow changing of goal before current goal has been reached - if state is WAIT_FOR_NEW_GOAL: - - if distance > 0.1 and not solution_found: - joint_goal_angles, solution_found = inverse_kinematics( - link_lengths, joint_angles, goal_pos) - if not solution_found: - print("Solution could not be found.") - state = WAIT_FOR_NEW_GOAL - arm.goal = get_random_goal() - elif solution_found: - state = MOVING_TO_GOAL - elif state is MOVING_TO_GOAL: - if distance > 0.1 and all(old_goal == goal_pos): - joint_angles = joint_angles + Kp * \ - ang_diff(joint_goal_angles, joint_angles) * dt - else: - state = WAIT_FOR_NEW_GOAL - solution_found = False - arm.goal = get_random_goal() - i_goal += 1 - - if i_goal >= 5: - break - - arm.update_joints(joint_angles) - - -def forward_kinematics(link_lengths, joint_angles): - x = y = 0 - for i in range(1, N_LINKS + 1): - x += link_lengths[i - 1] * np.cos(np.sum(joint_angles[:i])) - y += link_lengths[i - 1] * np.sin(np.sum(joint_angles[:i])) - return np.array([x, y]).T - - -def jacobian_inverse(link_lengths, joint_angles): - J = np.zeros((2, N_LINKS)) - for i in range(N_LINKS): - J[0, i] = 0 - J[1, i] = 0 - for j in range(i, N_LINKS): - J[0, i] -= link_lengths[j] * np.sin(np.sum(joint_angles[:j])) - J[1, i] += link_lengths[j] * np.cos(np.sum(joint_angles[:j])) - - return np.linalg.pinv(J) - - -def distance_to_goal(current_pos, goal_pos): - x_diff = goal_pos[0] - current_pos[0] - y_diff = goal_pos[1] - current_pos[1] - return np.array([x_diff, y_diff]).T, np.hypot(x_diff, y_diff) - - -def ang_diff(theta1, theta2): - """ - Returns the difference between two angles in the range -pi to +pi - """ - return angle_mod(theta1 - theta2) - - -if __name__ == '__main__': - # main() - animation() diff --git a/ArmNavigation/rrt_star_seven_joint_arm_control/rrt_star_seven_joint_arm_control.py b/ArmNavigation/rrt_star_seven_joint_arm_control/rrt_star_seven_joint_arm_control.py deleted file mode 100755 index 3bc2a5ec1d..0000000000 --- a/ArmNavigation/rrt_star_seven_joint_arm_control/rrt_star_seven_joint_arm_control.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -RRT* path planner for a seven joint arm -Author: Mahyar Abdeetedal (mahyaret) -""" -import math -import random -import numpy as np -import matplotlib.pyplot as plt -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from n_joint_arm_3d.NLinkArm3d import NLinkArm - -show_animation = True -verbose = False - - -class RobotArm(NLinkArm): - def get_points(self, joint_angle_list): - self.set_joint_angles(joint_angle_list) - - x_list = [] - y_list = [] - z_list = [] - - trans = np.identity(4) - - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - for i in range(len(self.link_list)): - trans = np.dot(trans, self.link_list[i].transformation_matrix()) - x_list.append(trans[0, 3]) - y_list.append(trans[1, 3]) - z_list.append(trans[2, 3]) - - return x_list, y_list, z_list - - -class RRTStar: - """ - Class for RRT Star planning - """ - - class Node: - def __init__(self, x): - self.x = x - self.parent = None - self.cost = 0.0 - - def __init__(self, start, goal, robot, obstacle_list, rand_area, - expand_dis=.30, - path_resolution=.1, - goal_sample_rate=20, - max_iter=300, - connect_circle_dist=50.0 - ): - """ - Setting Parameter - - start:Start Position [q1,...,qn] - goal:Goal Position [q1,...,qn] - obstacleList:obstacle Positions [[x,y,z,size],...] - randArea:Random Sampling Area [min,max] - - """ - self.start = self.Node(start) - self.end = self.Node(goal) - self.dimension = len(start) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.expand_dis = expand_dis - self.path_resolution = path_resolution - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.robot = robot - self.obstacle_list = obstacle_list - self.connect_circle_dist = connect_circle_dist - self.goal_node = self.Node(goal) - self.node_list = [] - if show_animation: - self.ax = plt.axes(projection='3d') - - def planning(self, animation=False, search_until_max_iter=False): - """ - rrt star path planning - - animation: flag for animation on or off - search_until_max_iter: search until max iteration for path - improving or not - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - if verbose: - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], - rnd, - self.expand_dis) - - if self.check_collision(new_node, self.robot, self.obstacle_list): - near_inds = self.find_near_nodes(new_node) - new_node = self.choose_parent(new_node, near_inds) - if new_node: - self.node_list.append(new_node) - self.rewire(new_node, near_inds) - - if animation and i % 5 == 0 and self.dimension <= 3: - self.draw_graph(rnd) - - if (not search_until_max_iter) and new_node: - last_index = self.search_best_goal_node() - if last_index is not None: - return self.generate_final_course(last_index) - if verbose: - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index is not None: - return self.generate_final_course(last_index) - - return None - - def choose_parent(self, new_node, near_inds): - if not near_inds: - return None - - # search nearest cost in near_inds - costs = [] - for i in near_inds: - near_node = self.node_list[i] - t_node = self.steer(near_node, new_node) - if t_node and self.check_collision(t_node, - self.robot, - self.obstacle_list): - costs.append(self.calc_new_cost(near_node, new_node)) - else: - costs.append(float("inf")) # the cost of collision node - min_cost = min(costs) - - if min_cost == float("inf"): - print("There is no good path.(min_cost is inf)") - return None - - min_ind = near_inds[costs.index(min_cost)] - new_node = self.steer(self.node_list[min_ind], new_node) - new_node.parent = self.node_list[min_ind] - new_node.cost = min_cost - - return new_node - - def search_best_goal_node(self): - dist_to_goal_list = [self.calc_dist_to_goal(n.x) - for n in self.node_list] - goal_inds = [dist_to_goal_list.index(i) - for i in dist_to_goal_list if i <= self.expand_dis] - - safe_goal_inds = [] - for goal_ind in goal_inds: - t_node = self.steer(self.node_list[goal_ind], self.goal_node) - if self.check_collision(t_node, self.robot, self.obstacle_list): - safe_goal_inds.append(goal_ind) - - if not safe_goal_inds: - return None - - min_cost = min([self.node_list[i].cost for i in safe_goal_inds]) - for i in safe_goal_inds: - if self.node_list[i].cost == min_cost: - return i - - return None - - def find_near_nodes(self, new_node): - nnode = len(self.node_list) + 1 - r = self.connect_circle_dist * math.sqrt(math.log(nnode) / nnode) - # if expand_dist exists, search vertices in - # a range no more than expand_dist - if hasattr(self, 'expand_dis'): - r = min(r, self.expand_dis) - dist_list = [np.sum((np.array(node.x) - np.array(new_node.x)) ** 2) - for node in self.node_list] - near_inds = [dist_list.index(i) for i in dist_list if i <= r ** 2] - return near_inds - - def rewire(self, new_node, near_inds): - for i in near_inds: - near_node = self.node_list[i] - edge_node = self.steer(new_node, near_node) - if not edge_node: - continue - edge_node.cost = self.calc_new_cost(new_node, near_node) - - no_collision = self.check_collision(edge_node, - self.robot, - self.obstacle_list) - improved_cost = near_node.cost > edge_node.cost - - if no_collision and improved_cost: - self.node_list[i] = edge_node - self.propagate_cost_to_leaves(new_node) - - def calc_new_cost(self, from_node, to_node): - d, _, _ = self.calc_distance_and_angle(from_node, to_node) - return from_node.cost + d - - def propagate_cost_to_leaves(self, parent_node): - - for node in self.node_list: - if node.parent == parent_node: - node.cost = self.calc_new_cost(parent_node, node) - self.propagate_cost_to_leaves(node) - - def generate_final_course(self, goal_ind): - path = [self.end.x] - node = self.node_list[goal_ind] - while node.parent is not None: - path.append(node.x) - node = node.parent - path.append(node.x) - reversed(path) - return path - - def calc_dist_to_goal(self, x): - distance = np.linalg.norm(np.array(x) - np.array(self.end.x)) - return distance - - def get_random_node(self): - if random.randint(0, 100) > self.goal_sample_rate: - rnd = self.Node(np.random.uniform(self.min_rand, - self.max_rand, - self.dimension)) - else: # goal point sampling - rnd = self.Node(self.end.x) - return rnd - - def steer(self, from_node, to_node, extend_length=float("inf")): - new_node = self.Node(list(from_node.x)) - d, phi, theta = self.calc_distance_and_angle(new_node, to_node) - - new_node.path_x = [list(new_node.x)] - - if extend_length > d: - extend_length = d - - n_expand = math.floor(extend_length / self.path_resolution) - - start, end = np.array(from_node.x), np.array(to_node.x) - v = end - start - u = v / (np.sqrt(np.sum(v ** 2))) - for _ in range(n_expand): - new_node.x += u * self.path_resolution - new_node.path_x.append(list(new_node.x)) - - d, _, _ = self.calc_distance_and_angle(new_node, to_node) - if d <= self.path_resolution: - new_node.path_x.append(list(to_node.x)) - - new_node.parent = from_node - - return new_node - - def draw_graph(self, rnd=None): - plt.cla() - self.ax.axis([-1, 1, -1, 1, -1, 1]) - self.ax.set_zlim(0, 1) - self.ax.grid(True) - for (ox, oy, oz, size) in self.obstacle_list: - self.plot_sphere(self.ax, ox, oy, oz, size=size) - if self.dimension > 3: - return self.ax - if rnd is not None: - self.ax.plot([rnd.x[0]], [rnd.x[1]], [rnd.x[2]], "^k") - for node in self.node_list: - if node.parent: - path = np.array(node.path_x) - plt.plot(path[:, 0], path[:, 1], path[:, 2], "-g") - self.ax.plot([self.start.x[0]], [self.start.x[1]], - [self.start.x[2]], "xr") - self.ax.plot([self.end.x[0]], [self.end.x[1]], [self.end.x[2]], "xr") - plt.pause(0.01) - return self.ax - - @staticmethod - def get_nearest_node_index(node_list, rnd_node): - dlist = [np.sum((np.array(node.x) - np.array(rnd_node.x))**2) - for node in node_list] - minind = dlist.index(min(dlist)) - - return minind - - @staticmethod - def plot_sphere(ax, x, y, z, size=1, color="k"): - u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j] - xl = x+size*np.cos(u)*np.sin(v) - yl = y+size*np.sin(u)*np.sin(v) - zl = z+size*np.cos(v) - ax.plot_wireframe(xl, yl, zl, color=color) - - @staticmethod - def calc_distance_and_angle(from_node, to_node): - dx = to_node.x[0] - from_node.x[0] - dy = to_node.x[1] - from_node.x[1] - dz = to_node.x[2] - from_node.x[2] - d = np.sqrt(np.sum((np.array(to_node.x) - np.array(from_node.x))**2)) - phi = math.atan2(dy, dx) - theta = math.atan2(math.hypot(dx, dy), dz) - return d, phi, theta - - @staticmethod - def calc_distance_and_angle2(from_node, to_node): - dx = to_node.x[0] - from_node.x[0] - dy = to_node.x[1] - from_node.x[1] - dz = to_node.x[2] - from_node.x[2] - d = math.sqrt(dx**2 + dy**2 + dz**2) - phi = math.atan2(dy, dx) - theta = math.atan2(math.hypot(dx, dy), dz) - return d, phi, theta - - @staticmethod - def check_collision(node, robot, obstacleList): - - if node is None: - return False - - for (ox, oy, oz, size) in obstacleList: - for x in node.path_x: - x_list, y_list, z_list = robot.get_points(x) - dx_list = [ox - x_point for x_point in x_list] - dy_list = [oy - y_point for y_point in y_list] - dz_list = [oz - z_point for z_point in z_list] - d_list = [dx * dx + dy * dy + dz * dz - for (dx, dy, dz) in zip(dx_list, - dy_list, - dz_list)] - - if min(d_list) <= size ** 2: - return False # collision - - return True # safe - - -def main(): - print("Start " + __file__) - - # init NLinkArm with Denavit-Hartenberg parameters of panda - # https://frankaemika.github.io/docs/control_parameters.html - # [theta, alpha, a, d] - seven_joint_arm = RobotArm([[0., math.pi/2., 0., .333], - [0., -math.pi/2., 0., 0.], - [0., math.pi/2., 0.0825, 0.3160], - [0., -math.pi/2., -0.0825, 0.], - [0., math.pi/2., 0., 0.3840], - [0., math.pi/2., 0.088, 0.], - [0., 0., 0., 0.107]]) - # ====Search Path with RRT==== - obstacle_list = [ - (-.3, -.3, .7, .1), - (.0, -.3, .7, .1), - (.2, -.1, .3, .15), - ] # [x,y,size(radius)] - start = [0 for _ in range(len(seven_joint_arm.link_list))] - end = [1.5 for _ in range(len(seven_joint_arm.link_list))] - # Set Initial parameters - rrt_star = RRTStar(start=start, - goal=end, - rand_area=[0, 2], - max_iter=200, - robot=seven_joint_arm, - obstacle_list=obstacle_list) - path = rrt_star.planning(animation=show_animation, - search_until_max_iter=False) - - if path is None: - print("Cannot find path.") - else: - print("Found path!") - - # Draw final path - if show_animation: - ax = rrt_star.draw_graph() - - # Plot final configuration - x_points, y_points, z_points = seven_joint_arm.get_points(path[-1]) - ax.plot([x for x in x_points], - [y for y in y_points], - [z for z in z_points], - "o-", color="red", ms=5, mew=0.5) - - for i, q in enumerate(path): - x_points, y_points, z_points = seven_joint_arm.get_points(q) - ax.plot([x for x in x_points], - [y for y in y_points], - [z for z in z_points], - "o-", color="grey", ms=4, mew=0.5) - plt.pause(0.1) - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/ArmNavigation/two_joint_arm_to_point_control/two_joint_arm_to_point_control.py b/ArmNavigation/two_joint_arm_to_point_control/two_joint_arm_to_point_control.py deleted file mode 100644 index 09969c30be..0000000000 --- a/ArmNavigation/two_joint_arm_to_point_control/two_joint_arm_to_point_control.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Inverse kinematics of a two-joint arm -Left-click the plot to set the goal position of the end effector - -Author: Daniel Ingram (daniel-s-ingram) - Atsushi Sakai (@Atsushi_twi) - -Reference: P. I. Corke, "Robotics, Vision & Control", Springer 2017, - ISBN 978-3-319-54413-7 p102 -- [Robotics, Vision and Control] -(https://link.springer.com/book/10.1007/978-3-642-20144-8) - -""" - -import matplotlib.pyplot as plt -import numpy as np -import math -from utils.angle import angle_mod - - -# Simulation parameters -Kp = 15 -dt = 0.01 - -# Link lengths -l1 = l2 = 1 - -# Set initial goal position to the initial end-effector position -x = 2 -y = 0 - -show_animation = True - -if show_animation: - plt.ion() - - -def two_joint_arm(GOAL_TH=0.0, theta1=0.0, theta2=0.0): - """ - Computes the inverse kinematics for a planar 2DOF arm - When out of bounds, rewrite x and y with last correct values - """ - global x, y - x_prev, y_prev = None, None - while True: - try: - if x is not None and y is not None: - x_prev = x - y_prev = y - if np.hypot(x, y) > (l1 + l2): - theta2_goal = 0 - else: - theta2_goal = np.arccos( - (x**2 + y**2 - l1**2 - l2**2) / (2 * l1 * l2)) - tmp = math.atan2(l2 * np.sin(theta2_goal), - (l1 + l2 * np.cos(theta2_goal))) - theta1_goal = math.atan2(y, x) - tmp - - if theta1_goal < 0: - theta2_goal = -theta2_goal - tmp = math.atan2(l2 * np.sin(theta2_goal), - (l1 + l2 * np.cos(theta2_goal))) - theta1_goal = math.atan2(y, x) - tmp - - theta1 = theta1 + Kp * ang_diff(theta1_goal, theta1) * dt - theta2 = theta2 + Kp * ang_diff(theta2_goal, theta2) * dt - except ValueError as e: - print("Unreachable goal"+e) - except TypeError: - x = x_prev - y = y_prev - - wrist = plot_arm(theta1, theta2, x, y) - - # check goal - d2goal = None - if x is not None and y is not None: - d2goal = np.hypot(wrist[0] - x, wrist[1] - y) - - if abs(d2goal) < GOAL_TH and x is not None: - return theta1, theta2 - - -def plot_arm(theta1, theta2, target_x, target_y): # pragma: no cover - shoulder = np.array([0, 0]) - elbow = shoulder + np.array([l1 * np.cos(theta1), l1 * np.sin(theta1)]) - wrist = elbow + \ - np.array([l2 * np.cos(theta1 + theta2), l2 * np.sin(theta1 + theta2)]) - - if show_animation: - plt.cla() - - plt.plot([shoulder[0], elbow[0]], [shoulder[1], elbow[1]], 'k-') - plt.plot([elbow[0], wrist[0]], [elbow[1], wrist[1]], 'k-') - - plt.plot(shoulder[0], shoulder[1], 'ro') - plt.plot(elbow[0], elbow[1], 'ro') - plt.plot(wrist[0], wrist[1], 'ro') - - plt.plot([wrist[0], target_x], [wrist[1], target_y], 'g--') - plt.plot(target_x, target_y, 'g*') - - plt.xlim(-2, 2) - plt.ylim(-2, 2) - - plt.show() - plt.pause(dt) - - return wrist - - -def ang_diff(theta1, theta2): - # Returns the difference between two angles in the range -pi to +pi - return angle_mod(theta1 - theta2) - - -def click(event): # pragma: no cover - global x, y - x = event.xdata - y = event.ydata - - -def animation(): - from random import random - global x, y - theta1 = theta2 = 0.0 - for i in range(5): - x = 2.0 * random() - 1.0 - y = 2.0 * random() - 1.0 - theta1, theta2 = two_joint_arm( - GOAL_TH=0.01, theta1=theta1, theta2=theta2) - - -def main(): # pragma: no cover - fig = plt.figure() - fig.canvas.mpl_connect("button_press_event", click) - # for stopping simulation with the esc key. - fig.canvas.mpl_connect('key_release_event', lambda event: [ - exit(0) if event.key == 'escape' else None]) - two_joint_arm() - - -if __name__ == "__main__": - # animation() - main() diff --git a/Bipedal/__init__.py b/Bipedal/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Bipedal/bipedal_planner/__init__.py b/Bipedal/bipedal_planner/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Bipedal/bipedal_planner/bipedal_planner.py b/Bipedal/bipedal_planner/bipedal_planner.py deleted file mode 100644 index c34357df67..0000000000 --- a/Bipedal/bipedal_planner/bipedal_planner.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Bipedal Walking with modifying designated footsteps -author: Takayuki Murooka (takayuki5168) -""" -import numpy as np -import math -from matplotlib import pyplot as plt -import matplotlib.patches as pat -from mpl_toolkits.mplot3d import Axes3D -import mpl_toolkits.mplot3d.art3d as art3d - - -class BipedalPlanner(object): - def __init__(self): - self.act_p = [] # actual footstep positions - self.ref_p = [] # reference footstep positions - self.com_trajectory = [] - self.ref_footsteps = None - self.g = 9.8 - - def set_ref_footsteps(self, ref_footsteps): - self.ref_footsteps = ref_footsteps - - def inverted_pendulum(self, x, x_dot, px_star, y, y_dot, py_star, z_c, - time_width): - time_split = 100 - - for i in range(time_split): - delta_time = time_width / time_split - - x_dot2 = self.g / z_c * (x - px_star) - x += x_dot * delta_time - x_dot += x_dot2 * delta_time - - y_dot2 = self.g / z_c * (y - py_star) - y += y_dot * delta_time - y_dot += y_dot2 * delta_time - - if i % 10 == 0: - self.com_trajectory.append([x, y]) - - return x, x_dot, y, y_dot - - def walk(self, t_sup=0.8, z_c=0.8, a=10, b=1, plot=False): - if self.ref_footsteps is None: - print("No footsteps") - return - - # set up plotter - if plot: - fig = plt.figure() - ax = Axes3D(fig) - fig.add_axes(ax) - com_trajectory_for_plot = [] - - px, py = 0.0, 0.0 # reference footstep position - px_star, py_star = px, py # modified footstep position - xi, xi_dot, yi, yi_dot = 0.0, 0.0, 0.01, 0.0 - time = 0.0 - n = 0 - self.ref_p.append([px, py, 0]) - self.act_p.append([px, py, 0]) - for i in range(len(self.ref_footsteps)): - # simulate x, y and those of dot of inverted pendulum - xi, xi_dot, yi, yi_dot = self.inverted_pendulum( - xi, xi_dot, px_star, yi, yi_dot, py_star, z_c, t_sup) - - # update time - time += t_sup - n += 1 - - # calculate px, py, x_, y_, vx_, vy_ - f_x, f_y, f_theta = self.ref_footsteps[n - 1] - rotate_mat = np.array([[math.cos(f_theta), -math.sin(f_theta)], - [math.sin(f_theta), math.cos(f_theta)]]) - - if n == len(self.ref_footsteps): - f_x_next, f_y_next, f_theta_next = 0., 0., 0. - else: - f_x_next, f_y_next, f_theta_next = self.ref_footsteps[n] - rotate_mat_next = np.array( - [[math.cos(f_theta_next), -math.sin(f_theta_next)], - [math.sin(f_theta_next), math.cos(f_theta_next)]]) - - Tc = math.sqrt(z_c / self.g) - C = math.cosh(t_sup / Tc) - S = math.sinh(t_sup / Tc) - - px, py = list(np.array([px, py]) - + np.dot(rotate_mat, - np.array([f_x, -1 * math.pow(-1, n) * f_y]) - )) - x_, y_ = list(np.dot(rotate_mat_next, np.array( - [f_x_next / 2., math.pow(-1, n) * f_y_next / 2.]))) - vx_, vy_ = list(np.dot(rotate_mat_next, np.array( - [(1 + C) / (Tc * S) * x_, (C - 1) / (Tc * S) * y_]))) - self.ref_p.append([px, py, f_theta]) - - # calculate reference COM - xd, xd_dot = px + x_, vx_ - yd, yd_dot = py + y_, vy_ - - # calculate modified footsteps - D = a * math.pow(C - 1, 2) + b * math.pow(S / Tc, 2) - px_star = -a * (C - 1) / D * (xd - C * xi - Tc * S * xi_dot) \ - - b * S / (Tc * D) * (xd_dot - S / Tc * xi - C * xi_dot) - py_star = -a * (C - 1) / D * (yd - C * yi - Tc * S * yi_dot) \ - - b * S / (Tc * D) * (yd_dot - S / Tc * yi - C * yi_dot) - self.act_p.append([px_star, py_star, f_theta]) - - # plot - if plot: - self.plot_animation(ax, com_trajectory_for_plot, px_star, - py_star, z_c) - if plot: - plt.show() - - def plot_animation(self, ax, com_trajectory_for_plot, px_star, py_star, - z_c): # pragma: no cover - # for plot trajectory, plot in for loop - for c in range(len(self.com_trajectory)): - if c > len(com_trajectory_for_plot): - # set up plotter - 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]) - ax.set_zlim(0, z_c * 2) - ax.set_xlim(0, 1) - ax.set_ylim(-0.5, 0.5) - - # update com_trajectory_for_plot - com_trajectory_for_plot.append(self.com_trajectory[c]) - - # plot com - ax.plot([p[0] for p in com_trajectory_for_plot], - [p[1] for p in com_trajectory_for_plot], [ - 0 for _ in com_trajectory_for_plot], - color="red") - - # plot inverted pendulum - ax.plot([px_star, com_trajectory_for_plot[-1][0]], - [py_star, com_trajectory_for_plot[-1][1]], - [0, z_c], color="green", linewidth=3) - ax.scatter([com_trajectory_for_plot[-1][0]], - [com_trajectory_for_plot[-1][1]], - [z_c], color="green", s=300) - - # foot rectangle for self.ref_p - foot_width = 0.06 - foot_height = 0.04 - for j in range(len(self.ref_p)): - angle = self.ref_p[j][2] + \ - math.atan2(foot_height, - foot_width) - math.pi - r = math.sqrt( - math.pow(foot_width / 3., 2) + math.pow( - foot_height / 2., 2)) - rec = pat.Rectangle(xy=( - self.ref_p[j][0] + r * math.cos(angle), - self.ref_p[j][1] + r * math.sin(angle)), - width=foot_width, - height=foot_height, - angle=self.ref_p[j][ - 2] * 180 / math.pi, - color="blue", fill=False, - ls=":") - ax.add_patch(rec) - art3d.pathpatch_2d_to_3d(rec, z=0, zdir="z") - - # foot rectangle for self.act_p - for j in range(len(self.act_p)): - angle = self.act_p[j][2] + \ - math.atan2(foot_height, - foot_width) - math.pi - r = math.sqrt( - math.pow(foot_width / 3., 2) + math.pow( - foot_height / 2., 2)) - rec = pat.Rectangle(xy=( - self.act_p[j][0] + r * math.cos(angle), - self.act_p[j][1] + r * math.sin(angle)), - width=foot_width, - height=foot_height, - angle=self.act_p[j][ - 2] * 180 / math.pi, - color="blue", fill=False) - ax.add_patch(rec) - art3d.pathpatch_2d_to_3d(rec, z=0, zdir="z") - - plt.draw() - plt.pause(0.001) - - -if __name__ == "__main__": - bipedal_planner = BipedalPlanner() - - footsteps = [[0.0, 0.2, 0.0], - [0.3, 0.2, 0.0], - [0.3, 0.2, 0.2], - [0.3, 0.2, 0.2], - [0.0, 0.2, 0.2]] - bipedal_planner.set_ref_footsteps(footsteps) - bipedal_planner.walk(plot=True) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index e8c4702596..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at asakaig@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 3bcc499e6a..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,5 +0,0 @@ -# Contributing - -:+1::tada: First of all, thank you very much for taking the time to contribute! :tada::+1: - -Please check this document for contribution: [How to contribute — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/modules/0_getting_started/3_how_to_contribute.html) diff --git a/InvertedPendulum/inverted_pendulum_lqr_control.py b/InvertedPendulum/inverted_pendulum_lqr_control.py deleted file mode 100644 index c4380530b8..0000000000 --- a/InvertedPendulum/inverted_pendulum_lqr_control.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Inverted Pendulum LQR control -author: Trung Kien - letrungkien.k53.hut@gmail.com -""" - -import math -import time - -import matplotlib.pyplot as plt -import numpy as np -from numpy.linalg import inv, eig - -# Model parameters - -l_bar = 2.0 # length of bar -M = 1.0 # [kg] -m = 0.3 # [kg] -g = 9.8 # [m/s^2] - -nx = 4 # number of state -nu = 1 # number of input -Q = np.diag([0.0, 1.0, 1.0, 0.0]) # state cost matrix -R = np.diag([0.01]) # input cost matrix - -delta_t = 0.1 # time tick [s] -sim_time = 5.0 # simulation time [s] - -show_animation = True - - -def main(): - x0 = np.array([ - [0.0], - [0.0], - [0.3], - [0.0] - ]) - - x = np.copy(x0) - time = 0.0 - - while sim_time > time: - time += delta_t - - # calc control input - u = lqr_control(x) - - # simulate inverted pendulum cart - x = simulation(x, u) - - if show_animation: - plt.clf() - px = float(x[0, 0]) - theta = float(x[2, 0]) - plot_cart(px, theta) - plt.xlim([-5.0, 2.0]) - plt.pause(0.001) - - print("Finish") - print(f"x={float(x[0, 0]):.2f} [m] , theta={math.degrees(x[2, 0]):.2f} [deg]") - if show_animation: - plt.show() - - -def simulation(x, u): - A, B = get_model_matrix() - x = A @ x + B @ u - - return x - - -def solve_DARE(A, B, Q, R, maxiter=150, eps=0.01): - """ - Solve a discrete time_Algebraic Riccati equation (DARE) - """ - P = Q - - for i in range(maxiter): - Pn = A.T @ P @ A - A.T @ P @ B @ \ - inv(R + B.T @ P @ B) @ B.T @ P @ A + Q - if (abs(Pn - P)).max() < eps: - break - P = Pn - - return Pn - - -def dlqr(A, B, Q, R): - """ - Solve the discrete time lqr controller. - x[k+1] = A x[k] + B u[k] - cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k] - # ref Bertsekas, p.151 - """ - - # first, try to solve the ricatti equation - P = solve_DARE(A, B, Q, R) - - # compute the LQR gain - K = inv(B.T @ P @ B + R) @ (B.T @ P @ A) - - eigVals, eigVecs = eig(A - B @ K) - return K, P, eigVals - - -def lqr_control(x): - A, B = get_model_matrix() - start = time.time() - K, _, _ = dlqr(A, B, Q, R) - u = -K @ x - elapsed_time = time.time() - start - print(f"calc time:{elapsed_time:.6f} [sec]") - return u - - -def get_numpy_array_from_matrix(x): - """ - get build-in list from matrix - """ - return np.array(x).flatten() - - -def get_model_matrix(): - A = np.array([ - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, m * g / M, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, g * (M + m) / (l_bar * M), 0.0] - ]) - A = np.eye(nx) + delta_t * A - - B = np.array([ - [0.0], - [1.0 / M], - [0.0], - [1.0 / (l_bar * M)] - ]) - B = delta_t * B - - return A, B - - -def flatten(a): - return np.array(a).flatten() - - -def plot_cart(xt, theta): - cart_w = 1.0 - cart_h = 0.5 - radius = 0.1 - - cx = np.array([-cart_w / 2.0, cart_w / 2.0, cart_w / - 2.0, -cart_w / 2.0, -cart_w / 2.0]) - cy = np.array([0.0, 0.0, cart_h, cart_h, 0.0]) - cy += radius * 2.0 - - cx = cx + xt - - bx = np.array([0.0, l_bar * math.sin(-theta)]) - bx += xt - by = np.array([cart_h, l_bar * math.cos(-theta) + cart_h]) - by += radius * 2.0 - - angles = np.arange(0.0, math.pi * 2.0, math.radians(3.0)) - ox = np.array([radius * math.cos(a) for a in angles]) - oy = np.array([radius * math.sin(a) for a in angles]) - - rwx = np.copy(ox) + cart_w / 4.0 + xt - rwy = np.copy(oy) + radius - lwx = np.copy(ox) - cart_w / 4.0 + xt - lwy = np.copy(oy) + radius - - wx = np.copy(ox) + bx[-1] - wy = np.copy(oy) + by[-1] - - plt.plot(flatten(cx), flatten(cy), "-b") - plt.plot(flatten(bx), flatten(by), "-k") - plt.plot(flatten(rwx), flatten(rwy), "-k") - plt.plot(flatten(lwx), flatten(lwy), "-k") - plt.plot(flatten(wx), flatten(wy), "-k") - plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}") - - # 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]) - - plt.axis("equal") - - -if __name__ == '__main__': - main() diff --git a/InvertedPendulum/inverted_pendulum_mpc_control.py b/InvertedPendulum/inverted_pendulum_mpc_control.py deleted file mode 100644 index c45dde8acc..0000000000 --- a/InvertedPendulum/inverted_pendulum_mpc_control.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Inverted Pendulum MPC control -author: Atsushi Sakai -""" - -import math -import time - -import cvxpy -import matplotlib.pyplot as plt -import numpy as np - -# Model parameters - -l_bar = 2.0 # length of bar -M = 1.0 # [kg] -m = 0.3 # [kg] -g = 9.8 # [m/s^2] - -nx = 4 # number of state -nu = 1 # number of input -Q = np.diag([0.0, 1.0, 1.0, 0.0]) # state cost matrix -R = np.diag([0.01]) # input cost matrix - -T = 30 # Horizon length -delta_t = 0.1 # time tick -sim_time = 5.0 # simulation time [s] - -show_animation = True - - -def main(): - x0 = np.array([ - [0.0], - [0.0], - [0.3], - [0.0] - ]) - - x = np.copy(x0) - time = 0.0 - - while sim_time > time: - time += delta_t - - # calc control input - opt_x, opt_delta_x, opt_theta, opt_delta_theta, opt_input = \ - mpc_control(x) - - # get input - u = opt_input[0] - - # simulate inverted pendulum cart - x = simulation(x, u) - - if show_animation: - plt.clf() - px = float(x[0, 0]) - theta = float(x[2, 0]) - plot_cart(px, theta) - plt.xlim([-5.0, 2.0]) - plt.pause(0.001) - - print("Finish") - print(f"x={float(x[0, 0]):.2f} [m] , theta={math.degrees(x[2, 0]):.2f} [deg]") - if show_animation: - plt.show() - - -def simulation(x, u): - A, B = get_model_matrix() - x = np.dot(A, x) + np.dot(B, u) - - return x - - -def mpc_control(x0): - x = cvxpy.Variable((nx, T + 1)) - u = cvxpy.Variable((nu, T)) - - A, B = get_model_matrix() - - cost = 0.0 - constr = [] - for t in range(T): - cost += cvxpy.quad_form(x[:, t + 1], Q) - cost += cvxpy.quad_form(u[:, t], R) - constr += [x[:, t + 1] == A @ x[:, t] + B @ u[:, t]] - - constr += [x[:, 0] == x0[:, 0]] - prob = cvxpy.Problem(cvxpy.Minimize(cost), constr) - - start = time.time() - prob.solve(verbose=False, solver=cvxpy.CLARABEL) - elapsed_time = time.time() - start - print(f"calc time:{elapsed_time:.6f} [sec]") - - if prob.status == cvxpy.OPTIMAL: - ox = get_numpy_array_from_matrix(x.value[0, :]) - dx = get_numpy_array_from_matrix(x.value[1, :]) - theta = get_numpy_array_from_matrix(x.value[2, :]) - d_theta = get_numpy_array_from_matrix(x.value[3, :]) - - ou = get_numpy_array_from_matrix(u.value[0, :]) - else: - ox, dx, theta, d_theta, ou = None, None, None, None, None - - return ox, dx, theta, d_theta, ou - - -def get_numpy_array_from_matrix(x): - """ - get build-in list from matrix - """ - return np.array(x).flatten() - - -def get_model_matrix(): - A = np.array([ - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, m * g / M, 0.0], - [0.0, 0.0, 0.0, 1.0], - [0.0, 0.0, g * (M + m) / (l_bar * M), 0.0] - ]) - A = np.eye(nx) + delta_t * A - - B = np.array([ - [0.0], - [1.0 / M], - [0.0], - [1.0 / (l_bar * M)] - ]) - B = delta_t * B - - return A, B - - -def flatten(a): - return np.array(a).flatten() - - -def plot_cart(xt, theta): - cart_w = 1.0 - cart_h = 0.5 - radius = 0.1 - - cx = np.array([-cart_w / 2.0, cart_w / 2.0, cart_w / - 2.0, -cart_w / 2.0, -cart_w / 2.0]) - cy = np.array([0.0, 0.0, cart_h, cart_h, 0.0]) - cy += radius * 2.0 - - cx = cx + xt - - bx = np.array([0.0, l_bar * math.sin(-theta)]) - bx += xt - by = np.array([cart_h, l_bar * math.cos(-theta) + cart_h]) - by += radius * 2.0 - - angles = np.arange(0.0, math.pi * 2.0, math.radians(3.0)) - ox = np.array([radius * math.cos(a) for a in angles]) - oy = np.array([radius * math.sin(a) for a in angles]) - - rwx = np.copy(ox) + cart_w / 4.0 + xt - rwy = np.copy(oy) + radius - lwx = np.copy(ox) - cart_w / 4.0 + xt - lwy = np.copy(oy) + radius - - wx = np.copy(ox) + bx[-1] - wy = np.copy(oy) + by[-1] - - plt.plot(flatten(cx), flatten(cy), "-b") - plt.plot(flatten(bx), flatten(by), "-k") - plt.plot(flatten(rwx), flatten(rwy), "-k") - plt.plot(flatten(lwx), flatten(lwy), "-k") - plt.plot(flatten(wx), flatten(wy), "-k") - plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}") - - # 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]) - - plt.axis("equal") - - -if __name__ == '__main__': - main() diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1c9b928b54..0000000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 - now Atsushi Sakai and other contributors: -https://github.com/AtsushiSakai/PythonRobotics/contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Localization/__init__.py b/Localization/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Localization/cubature_kalman_filter/cubature_kalman_filter.py b/Localization/cubature_kalman_filter/cubature_kalman_filter.py deleted file mode 100644 index 0fc4c93760..0000000000 --- a/Localization/cubature_kalman_filter/cubature_kalman_filter.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -Cubature Kalman filter using Constant Turn Rate and Velocity (CTRV) model -Fuse sensor data from IMU and GPS to obtain accurate position - -https://ieeexplore.ieee.org/document/4982682 - -Author: Raghuram Shankar - -state matrix: 2D x-y position, yaw, velocity and yaw rate -measurement matrix: 2D x-y position, velocity and yaw rate - -dt: Duration of time step -N: Number of time steps -show_final: Flag for showing final result -show_animation: Flag for showing each animation frame -show_ellipse: Flag for showing covariance ellipse -z_noise: Measurement noise -x_0: Prior state estimate matrix -P_0: Prior state estimate covariance matrix -q: Process noise covariance -hx: Measurement model matrix -r: Sensor noise covariance -SP: Sigma Points -W: Weights - -x_est: State estimate -P_est: State estimate covariance -x_true: Ground truth value of state -x_true_cat: Concatenate all ground truth states -x_est_cat: Concatenate all state estimates -z_cat: Concatenate all measurements - -""" - -import math -import matplotlib.pyplot as plt -import numpy as np -from scipy.linalg import sqrtm - - -dt = 0.1 -N = 100 - -show_final = 1 -show_animation = 0 -show_ellipse = 0 - - -z_noise = np.array([[0.1, 0.0, 0.0, 0.0], # x position [m] - [0.0, 0.1, 0.0, 0.0], # y position [m] - [0.0, 0.0, 0.1, 0.0], # velocity [m/s] - [0.0, 0.0, 0.0, 0.1]]) # yaw rate [rad/s] - - -x_0 = np.array([[0.0], # x position [m] - [0.0], # y position [m] - [0.0], # yaw [rad] - [1.0], # velocity [m/s] - [0.1]]) # yaw rate [rad/s] - - -p_0 = np.array([[1e-3, 0.0, 0.0, 0.0, 0.0], - [0.0, 1e-3, 0.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0]]) - - -q = np.array([[1e-11, 0.0, 0.0, 0.0, 0.0], - [0.0, 1e-11, 0.0, 0.0, 0.0], - [0.0, 0.0, np.deg2rad(1e-4), 0.0, 0.0], - [0.0, 0.0, 0.0, 1e-4, 0.0], - [0.0, 0.0, 0.0, 0.0, np.deg2rad(1e-4)]]) - - -hx = np.array([[1.0, 0.0, 0.0, 0.0, 0.0], - [0.0, 1.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0]]) - - -r = np.array([[0.015, 0.0, 0.0, 0.0], - [0.0, 0.010, 0.0, 0.0], - [0.0, 0.0, 0.1, 0.0], - [0.0, 0.0, 0.0, 0.01]])**2 - - -def cubature_kalman_filter(x_est, p_est, z): - x_pred, p_pred = cubature_prediction(x_est, p_est) - x_upd, p_upd = cubature_update(x_pred, p_pred, z) - return x_upd, p_upd - - -def f(x): - """ - Motion Model - References: - http://fusion.isif.org/proceedings/fusion08CD/papers/1569107835.pdf - https://github.com/balzer82/Kalman - """ - x[0] = x[0] + (x[3]/x[4]) * (np.sin(x[4] * dt + x[2]) - np.sin(x[2])) - x[1] = x[1] + (x[3]/x[4]) * (- np.cos(x[4] * dt + x[2]) + np.cos(x[2])) - x[2] = x[2] + x[4] * dt - x[3] = x[3] - x[4] = x[4] - return x - - -def h(x): - """Measurement Model""" - x = hx @ x - return x - - -def sigma(x, p): - """ - Unscented Transform with Cubature Rule - Generate 2n Sigma Points to represent the nonlinear motion - Assign Weights to each Sigma Point, Wi = 1/2n - Cubature Rule - Special Case of Unscented Transform - W0 = 0, no extra tuning parameters, no negative weights - """ - n = np.shape(x)[0] - SP = np.zeros((n, 2*n)) - W = np.zeros((1, 2*n)) - for i in range(n): - SD = sqrtm(p) - SP[:, i] = (x + (math.sqrt(n) * SD[:, i]).reshape((n, 1))).flatten() - SP[:, i+n] = (x - (math.sqrt(n) * SD[:, i]).reshape((n, 1))).flatten() - W[:, i] = 1/(2*n) - W[:, i+n] = W[:, i] - return SP, W - - -def cubature_prediction(x_pred, p_pred): - n = np.shape(x_pred)[0] - [SP, W] = sigma(x_pred, p_pred) - x_pred = np.zeros((n, 1)) - p_pred = q - for i in range(2*n): - x_pred = x_pred + (f(SP[:, i]).reshape((n, 1)) * W[0, i]) - for i in range(2*n): - p_step = (f(SP[:, i]).reshape((n, 1)) - x_pred) - p_pred = p_pred + (p_step @ p_step.T * W[0, i]) - return x_pred, p_pred - - -def cubature_update(x_pred, p_pred, z): - n = np.shape(x_pred)[0] - m = np.shape(z)[0] - [SP, W] = sigma(x_pred, p_pred) - y_k = np.zeros((m, 1)) - P_xy = np.zeros((n, m)) - s = r - for i in range(2*n): - y_k = y_k + (h(SP[:, i]).reshape((m, 1)) * W[0, i]) - for i in range(2*n): - p_step = (h(SP[:, i]).reshape((m, 1)) - y_k) - P_xy = P_xy + ((SP[:, i]).reshape((n, 1)) - - x_pred) @ p_step.T * W[0, i] - s = s + p_step @ p_step.T * W[0, i] - x_pred = x_pred + P_xy @ np.linalg.pinv(s) @ (z - y_k) - p_pred = p_pred - P_xy @ np.linalg.pinv(s) @ P_xy.T - return x_pred, p_pred - - -def generate_measurement(x_true): - gz = hx @ x_true - z = gz + z_noise @ np.random.randn(4, 1) - return z - - -def plot_animation(i, x_true_cat, x_est_cat, z): - if i == 0: - plt.plot(x_true_cat[0], x_true_cat[1], '.r') - plt.plot(x_est_cat[0], x_est_cat[1], '.b') - else: - plt.plot(x_true_cat[0:, 0], x_true_cat[0:, 1], 'r') - plt.plot(x_est_cat[0:, 0], x_est_cat[0:, 1], 'b') - plt.plot(z[0], z[1], '+g') - plt.grid(True) - plt.pause(0.001) - - -def plot_ellipse(x_est, p_est): - phi = np.linspace(0, 2 * math.pi, 100) - p_ellipse = np.array( - [[p_est[0, 0], p_est[0, 1]], [p_est[1, 0], p_est[1, 1]]]) - x0 = 3 * sqrtm(p_ellipse) - xy_1 = np.array([]) - xy_2 = np.array([]) - for i in range(100): - arr = np.array([[math.sin(phi[i])], [math.cos(phi[i])]]) - arr = x0 @ arr - xy_1 = np.hstack([xy_1, arr[0]]) - xy_2 = np.hstack([xy_2, arr[1]]) - plt.plot(xy_1 + x_est[0], xy_2 + x_est[1], 'r') - plt.pause(0.00001) - - -def plot_final(x_true_cat, x_est_cat, z_cat): - fig = plt.figure() - subplot = fig.add_subplot(111) - subplot.plot(x_true_cat[0:, 0], x_true_cat[0:, 1], - 'r', label='True Position') - subplot.plot(x_est_cat[0:, 0], x_est_cat[0:, 1], - 'b', label='Estimated Position') - subplot.plot(z_cat[0:, 0], z_cat[0:, 1], '+g', label='Noisy Measurements') - subplot.set_xlabel('x [m]') - subplot.set_ylabel('y [m]') - subplot.set_title('Cubature Kalman Filter - CTRV Model') - subplot.legend(loc='upper left', shadow=True, fontsize='large') - plt.grid(True) - plt.show() - - -def main(): - print(__file__ + " start!!") - x_est = x_0 - p_est = p_0 - x_true = x_0 - x_true_cat = np.array([x_0[0, 0], x_0[1, 0]]) - x_est_cat = np.array([x_0[0, 0], x_0[1, 0]]) - z_cat = np.array([x_0[0, 0], x_0[1, 0]]) - for i in range(N): - x_true = f(x_true) - z = generate_measurement(x_true) - if i == (N - 1) and show_final == 1: - show_final_flag = 1 - else: - show_final_flag = 0 - if show_animation == 1: - plot_animation(i, x_true_cat, x_est_cat, z) - if show_ellipse == 1: - plot_ellipse(x_est[0:2], p_est) - if show_final_flag == 1: - plot_final(x_true_cat, x_est_cat, z_cat) - x_est, p_est = cubature_kalman_filter(x_est, p_est, z) - x_true_cat = np.vstack((x_true_cat, x_true[0:2].T)) - x_est_cat = np.vstack((x_est_cat, x_est[0:2].T)) - z_cat = np.vstack((z_cat, z[0:2].T)) - print('CKF Over') - - -if __name__ == '__main__': - main() diff --git a/Localization/ensemble_kalman_filter/ensemble_kalman_filter.py b/Localization/ensemble_kalman_filter/ensemble_kalman_filter.py deleted file mode 100644 index e8a988a270..0000000000 --- a/Localization/ensemble_kalman_filter/ensemble_kalman_filter.py +++ /dev/null @@ -1,246 +0,0 @@ -""" - -Ensemble Kalman Filter(EnKF) localization sample - -author: Ryohei Sasaki(rsasaki0109) - -Reference: -Ensemble Kalman filtering -(https://rmets.onlinelibrary.wiley.com/doi/10.1256/qj.05.135) - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math -import matplotlib.pyplot as plt -import numpy as np -from utils.angle import angle_mod - -from utils.angle import rot_mat_2d - -# Simulation parameter -Q_sim = np.diag([0.2, np.deg2rad(1.0)]) ** 2 -R_sim = np.diag([1.0, np.deg2rad(30.0)]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] -MAX_RANGE = 20.0 # maximum observation range - -# Ensemble Kalman filter parameter -NP = 20 # Number of Particle - -show_animation = True - - -def calc_input(): - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - u = np.array([[v, yaw_rate]]).T - return u - - -def observation(xTrue, xd, u, RFID): - xTrue = motion_model(xTrue, u) - - z = np.zeros((0, 4)) - - for i in range(len(RFID[:, 0])): - - dx = RFID[i, 0] - xTrue[0, 0] - dy = RFID[i, 1] - xTrue[1, 0] - d = math.hypot(dx, dy) - angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0]) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_sim[0, 0] ** 0.5 # add noise - angle_with_noise = angle + np.random.randn() * Q_sim[1, 1] ** 0.5 - zi = np.array([dn, angle_with_noise, RFID[i, 0], RFID[i, 1]]) - z = np.vstack((z, zi)) - - # add noise to input - ud = np.array([[ - u[0, 0] + np.random.randn() * R_sim[0, 0] ** 0.5, - u[1, 0] + np.random.randn() * R_sim[1, 1] ** 0.5]]).T - - xd = motion_model(xd, ud) - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0], - [0, 1.0, 0, 0], - [0, 0, 1.0, 0], - [0, 0, 0, 0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT], - [1.0, 0.0]]) - x = F.dot(x) + B.dot(u) - - return x - - -def observe_landmark_position(x, landmarks): - landmarks_pos = np.zeros((2 * landmarks.shape[0], 1)) - for (i, lm) in enumerate(landmarks): - index = 2 * i - q = Q_sim[0, 0] ** 0.5 - landmarks_pos[index] = x[0, 0] + lm[0] * math.cos( - x[2, 0] + lm[1]) + np.random.randn() * q / np.sqrt(2) - landmarks_pos[index + 1] = x[1, 0] + lm[0] * math.sin( - x[2, 0] + lm[1]) + np.random.randn() * q / np.sqrt(2) - return landmarks_pos - - -def calc_covariance(xEst, px): - cov = np.zeros((3, 3)) - - for i in range(px.shape[1]): - dx = (px[:, i] - xEst)[0:3] - cov += dx.dot(dx.T) - cov /= NP - - return cov - - -def enkf_localization(px, z, u): - """ - Localization with Ensemble Kalman filter - """ - pz = np.zeros((z.shape[0] * 2, NP)) # Particle store of z - for ip in range(NP): - x = np.array([px[:, ip]]).T - - # Predict with random input sampling - ud1 = u[0, 0] + np.random.randn() * R_sim[0, 0] ** 0.5 - ud2 = u[1, 0] + np.random.randn() * R_sim[1, 1] ** 0.5 - ud = np.array([[ud1, ud2]]).T - x = motion_model(x, ud) - px[:, ip] = x[:, 0] - z_pos = observe_landmark_position(x, z) - pz[:, ip] = z_pos[:, 0] - - x_ave = np.mean(px, axis=1) - x_dif = px - np.tile(x_ave, (NP, 1)).T - - z_ave = np.mean(pz, axis=1) - z_dif = pz - np.tile(z_ave, (NP, 1)).T - - U = 1 / (NP - 1) * x_dif @ z_dif.T - V = 1 / (NP - 1) * z_dif @ z_dif.T - - K = U @ np.linalg.inv(V) # Kalman Gain - - z_lm_pos = z[:, [2, 3]].reshape(-1, ) - - px_hat = px + K @ (np.tile(z_lm_pos, (NP, 1)).T - pz) - - xEst = np.average(px_hat, axis=1).reshape(4, 1) - PEst = calc_covariance(xEst, px_hat) - - return xEst, PEst, px_hat - - -def plot_covariance_ellipse(xEst, PEst): # pragma: no cover - Pxy = PEst[0:2, 0:2] - eig_val, eig_vec = np.linalg.eig(Pxy) - - if eig_val[0] >= eig_val[1]: - big_ind = 0 - small_ind = 1 - else: - big_ind = 1 - small_ind = 0 - - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - - # eig_val[big_ind] or eiq_val[small_ind] were occasionally negative - # numbers extremely close to 0 (~10^-20), catch these cases and set - # the respective variable to 0 - try: - a = math.sqrt(eig_val[big_ind]) - except ValueError: - a = 0 - - try: - b = math.sqrt(eig_val[small_ind]) - except ValueError: - b = 0 - - x = [a * math.cos(it) for it in t] - y = [b * math.sin(it) for it in t] - angle = math.atan2(eig_vec[1, big_ind], eig_vec[0, big_ind]) - fx = np.stack([x, y]).T @ rot_mat_2d(angle) - - px = np.array(fx[:, 0] + xEst[0, 0]).flatten() - py = np.array(fx[:, 1] + xEst[1, 0]).flatten() - plt.plot(px, py, "--r") - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RF_ID positions [x, y] - RF_ID = np.array([[10.0, 0.0], - [10.0, 10.0], - [0.0, 15.0], - [-5.0, 20.0]]) - - # State Vector [x y yaw v]' - xEst = np.zeros((4, 1)) - xTrue = np.zeros((4, 1)) - px = np.zeros((4, NP)) # Particle store of x - - xDR = np.zeros((4, 1)) # Dead reckoning - - # history - hxEst = xEst - hxTrue = xTrue - hxDR = xTrue - - while SIM_TIME >= time: - time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u, RF_ID) - - xEst, PEst, px = enkf_localization(px, z, ud) - - # store data history - hxEst = np.hstack((hxEst, xEst)) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - - if show_animation: - 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]) - - for i in range(len(z[:, 0])): - plt.plot([xTrue[0, 0], z[i, 2]], [xTrue[1, 0], z[i, 3]], "-k") - plt.plot(RF_ID[:, 0], RF_ID[:, 1], "*k") - plt.plot(px[0, :], px[1, :], ".r") - plt.plot(np.array(hxTrue[0, :]).flatten(), - np.array(hxTrue[1, :]).flatten(), "-b") - plt.plot(np.array(hxDR[0, :]).flatten(), - np.array(hxDR[1, :]).flatten(), "-k") - plt.plot(np.array(hxEst[0, :]).flatten(), - np.array(hxEst[1, :]).flatten(), "-r") - plot_covariance_ellipse(xEst, PEst) - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/Localization/extended_kalman_filter/ekf_with_velocity_correction.py b/Localization/extended_kalman_filter/ekf_with_velocity_correction.py deleted file mode 100644 index 5dd97830fc..0000000000 --- a/Localization/extended_kalman_filter/ekf_with_velocity_correction.py +++ /dev/null @@ -1,198 +0,0 @@ -""" - -Extended kalman filter (EKF) localization with velocity correction sample - -author: Atsushi Sakai (@Atsushi_twi) -modified by: Ryohei Sasaki (@rsasaki0109) - -""" -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math -import matplotlib.pyplot as plt -import numpy as np - -from utils.plot import plot_covariance_ellipse - -# Covariance for EKF simulation -Q = np.diag([ - 0.1, # variance of location on x-axis - 0.1, # variance of location on y-axis - np.deg2rad(1.0), # variance of yaw angle - 0.4, # variance of velocity - 0.1 # variance of scale factor -]) ** 2 # predict state covariance -R = np.diag([0.1, 0.1]) ** 2 # Observation x,y position covariance - -# Simulation parameter -INPUT_NOISE = np.diag([0.1, np.deg2rad(5.0)]) ** 2 -GPS_NOISE = np.diag([0.05, 0.05]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] - -show_animation = True - - -def calc_input(): - v = 1.0 # [m/s] - yawrate = 0.1 # [rad/s] - u = np.array([[v], [yawrate]]) - return u - - -def observation(xTrue, xd, u): - xTrue = motion_model(xTrue, u) - - # add noise to gps x-y - z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1) - - # add noise to input - ud = u + INPUT_NOISE @ np.random.randn(2, 1) - - xd = motion_model(xd, ud) - - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0, 0], - [0, 1.0, 0, 0, 0], - [0, 0, 1.0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 1.0]]) - - B = np.array([[DT * math.cos(x[2, 0]) * x[4, 0], 0], - [DT * math.sin(x[2, 0]) * x[4, 0], 0], - [0.0, DT], - [1.0, 0.0], - [0.0, 0.0]]) - - x = F @ x + B @ u - - return x - - -def observation_model(x): - H = np.array([ - [1, 0, 0, 0, 0], - [0, 1, 0, 0, 0] - ]) - z = H @ x - - return z - - -def jacob_f(x, u): - """ - Jacobian of Motion Model - - motion model - x_{t+1} = x_t+v*s*dt*cos(yaw) - y_{t+1} = y_t+v*s*dt*sin(yaw) - yaw_{t+1} = yaw_t+omega*dt - v_{t+1} = v{t} - s_{t+1} = s{t} - so - dx/dyaw = -v*s*dt*sin(yaw) - dx/dv = dt*s*cos(yaw) - dx/ds = dt*v*cos(yaw) - dy/dyaw = v*s*dt*cos(yaw) - dy/dv = dt*s*sin(yaw) - dy/ds = dt*v*sin(yaw) - """ - yaw = x[2, 0] - v = u[0, 0] - s = x[4, 0] - jF = np.array([ - [1.0, 0.0, -DT * v * s * math.sin(yaw), DT * s * math.cos(yaw), DT * v * math.cos(yaw)], - [0.0, 1.0, DT * v * s * math.cos(yaw), DT * s * math.sin(yaw), DT * v * math.sin(yaw)], - [0.0, 0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0]]) - return jF - - -def jacob_h(): - jH = np.array([[1, 0, 0, 0, 0], - [0, 1, 0, 0, 0]]) - return jH - - -def ekf_estimation(xEst, PEst, z, u): - # Predict - xPred = motion_model(xEst, u) - jF = jacob_f(xEst, u) - PPred = jF @ PEst @ jF.T + Q - - # Update - jH = jacob_h() - zPred = observation_model(xPred) - y = z - zPred - S = jH @ PPred @ jH.T + R - K = PPred @ jH.T @ np.linalg.inv(S) - xEst = xPred + K @ y - PEst = (np.eye(len(xEst)) - K @ jH) @ PPred - return xEst, PEst - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # State Vector [x y yaw v s]' - xEst = np.zeros((5, 1)) - xEst[4, 0] = 1.0 # Initial scale factor - xTrue = np.zeros((5, 1)) - true_scale_factor = 0.9 # True scale factor - xTrue[4, 0] = true_scale_factor - PEst = np.eye(5) - - xDR = np.zeros((5, 1)) # Dead reckoning - - # history - hxEst = xEst - hxTrue = xTrue - hxDR = xTrue - hz = np.zeros((2, 1)) - - while SIM_TIME >= time: - time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u) - - xEst, PEst = ekf_estimation(xEst, PEst, z, ud) - - # store data history - hxEst = np.hstack((hxEst, xEst)) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - hz = np.hstack((hz, z)) - estimated_scale_factor = hxEst[4, -1] - if show_animation: - 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]) - plt.plot(hz[0, :], hz[1, :], ".g") - plt.plot(hxTrue[0, :].flatten(), - hxTrue[1, :].flatten(), "-b") - plt.plot(hxDR[0, :].flatten(), - hxDR[1, :].flatten(), "-k") - plt.plot(hxEst[0, :].flatten(), - hxEst[1, :].flatten(), "-r") - plt.text(0.45, 0.85, f"True Velocity Scale Factor: {true_scale_factor:.2f}", ha='left', va='top', transform=plt.gca().transAxes) - plt.text(0.45, 0.95, f"Estimated Velocity Scale Factor: {estimated_scale_factor:.2f}", ha='left', va='top', transform=plt.gca().transAxes) - plot_covariance_ellipse(xEst[0, 0], xEst[1, 0], PEst) - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/Localization/extended_kalman_filter/extended_kalman_filter.py b/Localization/extended_kalman_filter/extended_kalman_filter.py deleted file mode 100644 index d9ece6c6f3..0000000000 --- a/Localization/extended_kalman_filter/extended_kalman_filter.py +++ /dev/null @@ -1,190 +0,0 @@ -""" - -Extended kalman filter (EKF) localization sample - -author: Atsushi Sakai (@Atsushi_twi) - -""" -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math -import matplotlib.pyplot as plt -import numpy as np - -from utils.plot import plot_covariance_ellipse - -# Covariance for EKF simulation -Q = np.diag([ - 0.1, # variance of location on x-axis - 0.1, # variance of location on y-axis - np.deg2rad(1.0), # variance of yaw angle - 1.0 # variance of velocity -]) ** 2 # predict state covariance -R = np.diag([1.0, 1.0]) ** 2 # Observation x,y position covariance - -# Simulation parameter -INPUT_NOISE = np.diag([1.0, np.deg2rad(30.0)]) ** 2 -GPS_NOISE = np.diag([0.5, 0.5]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] - -show_animation = True - - -def calc_input(): - v = 1.0 # [m/s] - yawrate = 0.1 # [rad/s] - u = np.array([[v], [yawrate]]) - return u - - -def observation(xTrue, xd, u): - xTrue = motion_model(xTrue, u) - - # add noise to gps x-y - z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1) - - # add noise to input - ud = u + INPUT_NOISE @ np.random.randn(2, 1) - - xd = motion_model(xd, ud) - - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0], - [0, 1.0, 0, 0], - [0, 0, 1.0, 0], - [0, 0, 0, 0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT], - [1.0, 0.0]]) - - x = F @ x + B @ u - - return x - - -def observation_model(x): - H = np.array([ - [1, 0, 0, 0], - [0, 1, 0, 0] - ]) - - z = H @ x - - return z - - -def jacob_f(x, u): - """ - Jacobian of Motion Model - - motion model - x_{t+1} = x_t+v*dt*cos(yaw) - y_{t+1} = y_t+v*dt*sin(yaw) - yaw_{t+1} = yaw_t+omega*dt - v_{t+1} = v{t} - so - dx/dyaw = -v*dt*sin(yaw) - dx/dv = dt*cos(yaw) - dy/dyaw = v*dt*cos(yaw) - dy/dv = dt*sin(yaw) - """ - yaw = x[2, 0] - v = u[0, 0] - jF = np.array([ - [1.0, 0.0, -DT * v * math.sin(yaw), DT * math.cos(yaw)], - [0.0, 1.0, DT * v * math.cos(yaw), DT * math.sin(yaw)], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]]) - - return jF - - -def jacob_h(): - # Jacobian of Observation Model - jH = np.array([ - [1, 0, 0, 0], - [0, 1, 0, 0] - ]) - - return jH - - -def ekf_estimation(xEst, PEst, z, u): - # Predict - xPred = motion_model(xEst, u) - jF = jacob_f(xEst, u) - PPred = jF @ PEst @ jF.T + Q - - # Update - jH = jacob_h() - zPred = observation_model(xPred) - y = z - zPred - S = jH @ PPred @ jH.T + R - K = PPred @ jH.T @ np.linalg.inv(S) - xEst = xPred + K @ y - PEst = (np.eye(len(xEst)) - K @ jH) @ PPred - return xEst, PEst - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # State Vector [x y yaw v]' - xEst = np.zeros((4, 1)) - xTrue = np.zeros((4, 1)) - PEst = np.eye(4) - - xDR = np.zeros((4, 1)) # Dead reckoning - - # history - hxEst = xEst - hxTrue = xTrue - hxDR = xTrue - hz = np.zeros((2, 1)) - - while SIM_TIME >= time: - time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u) - - xEst, PEst = ekf_estimation(xEst, PEst, z, ud) - - # store data history - hxEst = np.hstack((hxEst, xEst)) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - hz = np.hstack((hz, z)) - - if show_animation: - 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]) - plt.plot(hz[0, :], hz[1, :], ".g") - plt.plot(hxTrue[0, :].flatten(), - hxTrue[1, :].flatten(), "-b") - plt.plot(hxDR[0, :].flatten(), - hxDR[1, :].flatten(), "-k") - plt.plot(hxEst[0, :].flatten(), - hxEst[1, :].flatten(), "-r") - plot_covariance_ellipse(xEst[0, 0], xEst[1, 0], PEst) - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/Localization/histogram_filter/histogram_filter.py b/Localization/histogram_filter/histogram_filter.py deleted file mode 100644 index 17cfc2e14c..0000000000 --- a/Localization/histogram_filter/histogram_filter.py +++ /dev/null @@ -1,265 +0,0 @@ -""" - -Histogram Filter 2D localization example - - -In this simulation, x,y are unknown, yaw is known. - -Initial position is not needed. - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import copy -import math - -import matplotlib.pyplot as plt -import matplotlib as mpl -import numpy as np -from scipy.ndimage import gaussian_filter -from scipy.stats import norm - -# Parameters -EXTEND_AREA = 10.0 # [m] grid map extended length -SIM_TIME = 50.0 # simulation time [s] -DT = 0.1 # time tick [s] -MAX_RANGE = 10.0 # maximum observation range -MOTION_STD = 1.0 # standard deviation for motion gaussian distribution -RANGE_STD = 3.0 # standard deviation for observation gaussian distribution - -# grid map param -XY_RESOLUTION = 0.5 # xy grid resolution -MIN_X = -15.0 -MIN_Y = -5.0 -MAX_X = 15.0 -MAX_Y = 25.0 - -# simulation parameters -NOISE_RANGE = 2.0 # [m] 1σ range noise parameter -NOISE_SPEED = 0.5 # [m/s] 1σ speed noise parameter - -show_animation = True - - -class GridMap: - - def __init__(self): - self.data = None - self.xy_resolution = None - self.min_x = None - self.min_y = None - self.max_x = None - self.max_y = None - self.x_w = None - self.y_w = None - self.dx = 0.0 # movement distance - self.dy = 0.0 # movement distance - - -def histogram_filter_localization(grid_map, u, z, yaw): - grid_map = motion_update(grid_map, u, yaw) - - grid_map = observation_update(grid_map, z, RANGE_STD) - - return grid_map - - -def calc_gaussian_observation_pdf(grid_map, z, iz, ix, iy, std): - # predicted range - x = ix * grid_map.xy_resolution + grid_map.min_x - y = iy * grid_map.xy_resolution + grid_map.min_y - d = math.hypot(x - z[iz, 1], y - z[iz, 2]) - - # likelihood - pdf = norm.pdf(d - z[iz, 0], 0.0, std) - - return pdf - - -def observation_update(grid_map, z, std): - for iz in range(z.shape[0]): - for ix in range(grid_map.x_w): - for iy in range(grid_map.y_w): - grid_map.data[ix][iy] *= calc_gaussian_observation_pdf( - grid_map, z, iz, ix, iy, std) - - grid_map = normalize_probability(grid_map) - - return grid_map - - -def calc_control_input(): - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - u = np.array([v, yaw_rate]).reshape(2, 1) - return u - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0], - [0, 1.0, 0, 0], - [0, 0, 1.0, 0], - [0, 0, 0, 0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT], - [1.0, 0.0]]) - - x = F @ x + B @ u - - return x - - -def draw_heat_map(data, mx, my): - max_value = max([max(i_data) for i_data in data]) - plt.grid(False) - plt.pcolor(mx, my, data, vmax=max_value, cmap=mpl.colormaps["Blues"]) - plt.axis("equal") - - -def observation(xTrue, u, RFID): - xTrue = motion_model(xTrue, u) - - z = np.zeros((0, 3)) - - for i in range(len(RFID[:, 0])): - - dx = xTrue[0, 0] - RFID[i, 0] - dy = xTrue[1, 0] - RFID[i, 1] - d = math.hypot(dx, dy) - if d <= MAX_RANGE: - # add noise to range observation - dn = d + np.random.randn() * NOISE_RANGE - zi = np.array([dn, RFID[i, 0], RFID[i, 1]]) - z = np.vstack((z, zi)) - - # add noise to speed - ud = u[:, :] - ud[0] += np.random.randn() * NOISE_SPEED - - return xTrue, z, ud - - -def normalize_probability(grid_map): - sump = sum([sum(i_data) for i_data in grid_map.data]) - - for ix in range(grid_map.x_w): - for iy in range(grid_map.y_w): - grid_map.data[ix][iy] /= sump - - return grid_map - - -def init_grid_map(xy_resolution, min_x, min_y, max_x, max_y): - grid_map = GridMap() - - grid_map.xy_resolution = xy_resolution - grid_map.min_x = min_x - grid_map.min_y = min_y - grid_map.max_x = max_x - grid_map.max_y = max_y - grid_map.x_w = int(round((grid_map.max_x - grid_map.min_x) - / grid_map.xy_resolution)) - grid_map.y_w = int(round((grid_map.max_y - grid_map.min_y) - / grid_map.xy_resolution)) - - grid_map.data = [[1.0 for _ in range(grid_map.y_w)] - for _ in range(grid_map.x_w)] - grid_map = normalize_probability(grid_map) - - return grid_map - - -def map_shift(grid_map, x_shift, y_shift): - tmp_grid_map = copy.deepcopy(grid_map.data) - - for ix in range(grid_map.x_w): - for iy in range(grid_map.y_w): - nix = ix + x_shift - niy = iy + y_shift - - if 0 <= nix < grid_map.x_w and 0 <= niy < grid_map.y_w: - grid_map.data[ix + x_shift][iy + y_shift] =\ - tmp_grid_map[ix][iy] - - return grid_map - - -def motion_update(grid_map, u, yaw): - grid_map.dx += DT * math.cos(yaw) * u[0] - grid_map.dy += DT * math.sin(yaw) * u[0] - - x_shift = grid_map.dx // grid_map.xy_resolution - y_shift = grid_map.dy // grid_map.xy_resolution - - if abs(x_shift) >= 1.0 or abs(y_shift) >= 1.0: # map should be shifted - grid_map = map_shift(grid_map, int(x_shift[0]), int(y_shift[0])) - grid_map.dx -= x_shift * grid_map.xy_resolution - grid_map.dy -= y_shift * grid_map.xy_resolution - - # Add motion noise - grid_map.data = gaussian_filter(grid_map.data, sigma=MOTION_STD) - - return grid_map - - -def calc_grid_index(grid_map): - mx, my = np.mgrid[slice(grid_map.min_x - grid_map.xy_resolution / 2.0, - grid_map.max_x + grid_map.xy_resolution / 2.0, - grid_map.xy_resolution), - slice(grid_map.min_y - grid_map.xy_resolution / 2.0, - grid_map.max_y + grid_map.xy_resolution / 2.0, - grid_map.xy_resolution)] - - return mx, my - - -def main(): - print(__file__ + " start!!") - - # RF_ID positions [x, y] - RF_ID = np.array([[10.0, 0.0], - [10.0, 10.0], - [0.0, 15.0], - [-5.0, 20.0]]) - - time = 0.0 - - xTrue = np.zeros((4, 1)) - grid_map = init_grid_map(XY_RESOLUTION, MIN_X, MIN_Y, MAX_X, MAX_Y) - mx, my = calc_grid_index(grid_map) # for grid map visualization - - while SIM_TIME >= time: - time += DT - print(f"{time=:.1f}") - - u = calc_control_input() - - yaw = xTrue[2, 0] # Orientation is known - xTrue, z, ud = observation(xTrue, u, RF_ID) - - grid_map = histogram_filter_localization(grid_map, u, z, yaw) - - if show_animation: - 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]) - draw_heat_map(grid_map.data, mx, my) - plt.plot(xTrue[0, :], xTrue[1, :], "xr") - plt.plot(RF_ID[:, 0], RF_ID[:, 1], ".k") - for i in range(z.shape[0]): - plt.plot([xTrue[0, 0], z[i, 1]], - [xTrue[1, 0], z[i, 2]], - "-k") - plt.title("Time[s]:" + str(time)[0: 4]) - plt.pause(0.1) - - print("Done") - - -if __name__ == '__main__': - main() diff --git a/Localization/particle_filter/particle_filter.py b/Localization/particle_filter/particle_filter.py deleted file mode 100644 index ba54a3d12b..0000000000 --- a/Localization/particle_filter/particle_filter.py +++ /dev/null @@ -1,263 +0,0 @@ -""" - -Particle Filter localization sample - -author: Atsushi Sakai (@Atsushi_twi) - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math - -import matplotlib.pyplot as plt -import numpy as np - -from utils.angle import rot_mat_2d - -# Estimation parameter of PF -Q = np.diag([0.2]) ** 2 # range error -R = np.diag([2.0, np.deg2rad(40.0)]) ** 2 # input error - -# Simulation parameter -Q_sim = np.diag([0.2]) ** 2 -R_sim = np.diag([1.0, np.deg2rad(30.0)]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] -MAX_RANGE = 20.0 # maximum observation range - -# Particle filter parameter -NP = 100 # Number of Particle -NTh = NP / 2.0 # Number of particle for re-sampling - -show_animation = True - - -def calc_input(): - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - u = np.array([[v, yaw_rate]]).T - return u - - -def observation(x_true, xd, u, rf_id): - x_true = motion_model(x_true, u) - - # add noise to gps x-y - z = np.zeros((0, 3)) - - for i in range(len(rf_id[:, 0])): - - dx = x_true[0, 0] - rf_id[i, 0] - dy = x_true[1, 0] - rf_id[i, 1] - d = math.hypot(dx, dy) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_sim[0, 0] ** 0.5 # add noise - zi = np.array([[dn, rf_id[i, 0], rf_id[i, 1]]]) - z = np.vstack((z, zi)) - - # add noise to input - ud1 = u[0, 0] + np.random.randn() * R_sim[0, 0] ** 0.5 - ud2 = u[1, 0] + np.random.randn() * R_sim[1, 1] ** 0.5 - ud = np.array([[ud1, ud2]]).T - - xd = motion_model(xd, ud) - - return x_true, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0], - [0, 1.0, 0, 0], - [0, 0, 1.0, 0], - [0, 0, 0, 0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT], - [1.0, 0.0]]) - - x = F.dot(x) + B.dot(u) - - return x - - -def gauss_likelihood(x, sigma): - p = 1.0 / math.sqrt(2.0 * math.pi * sigma ** 2) * \ - math.exp(-x ** 2 / (2 * sigma ** 2)) - - return p - - -def calc_covariance(x_est, px, pw): - """ - calculate covariance matrix - see ipynb doc - """ - cov = np.zeros((4, 4)) - n_particle = px.shape[1] - for i in range(n_particle): - dx = (px[:, i:i + 1] - x_est) - cov += pw[0, i] * dx @ dx.T - cov *= 1.0 / (1.0 - pw @ pw.T) - - return cov - - -def pf_localization(px, pw, z, u): - """ - Localization with Particle filter - """ - - for ip in range(NP): - x = np.array([px[:, ip]]).T - w = pw[0, ip] - - # Predict with random input sampling - ud1 = u[0, 0] + np.random.randn() * R[0, 0] ** 0.5 - ud2 = u[1, 0] + np.random.randn() * R[1, 1] ** 0.5 - ud = np.array([[ud1, ud2]]).T - x = motion_model(x, ud) - - # Calc Importance Weight - for i in range(len(z[:, 0])): - dx = x[0, 0] - z[i, 1] - dy = x[1, 0] - z[i, 2] - pre_z = math.hypot(dx, dy) - dz = pre_z - z[i, 0] - w = w * gauss_likelihood(dz, math.sqrt(Q[0, 0])) - - px[:, ip] = x[:, 0] - pw[0, ip] = w - - pw = pw / pw.sum() # normalize - - x_est = px.dot(pw.T) - p_est = calc_covariance(x_est, px, pw) - - N_eff = 1.0 / (pw.dot(pw.T))[0, 0] # Effective particle number - if N_eff < NTh: - px, pw = re_sampling(px, pw) - return x_est, p_est, px, pw - - -def re_sampling(px, pw): - """ - low variance re-sampling - """ - - w_cum = np.cumsum(pw) - base = np.arange(0.0, 1.0, 1 / NP) - re_sample_id = base + np.random.uniform(0, 1 / NP) - indexes = [] - ind = 0 - for ip in range(NP): - while re_sample_id[ip] > w_cum[ind]: - ind += 1 - indexes.append(ind) - - px = px[:, indexes] - pw = np.zeros((1, NP)) + 1.0 / NP # init weight - - return px, pw - - -def plot_covariance_ellipse(x_est, p_est): # pragma: no cover - p_xy = p_est[0:2, 0:2] - eig_val, eig_vec = np.linalg.eig(p_xy) - - if eig_val[0] >= eig_val[1]: - big_ind = 0 - small_ind = 1 - else: - big_ind = 1 - small_ind = 0 - - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - - # eig_val[big_ind] or eiq_val[small_ind] were occasionally negative - # numbers extremely close to 0 (~10^-20), catch these cases and set the - # respective variable to 0 - try: - a = math.sqrt(eig_val[big_ind]) - except ValueError: - a = 0 - - try: - b = math.sqrt(eig_val[small_ind]) - except ValueError: - b = 0 - - x = [a * math.cos(it) for it in t] - y = [b * math.sin(it) for it in t] - angle = math.atan2(eig_vec[1, big_ind], eig_vec[0, big_ind]) - fx = rot_mat_2d(angle) @ np.array([[x, y]]) - px = np.array(fx[:, 0] + x_est[0, 0]).flatten() - py = np.array(fx[:, 1] + x_est[1, 0]).flatten() - plt.plot(px, py, "--r") - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RF_ID positions [x, y] - rf_id = np.array([[10.0, 0.0], - [10.0, 10.0], - [0.0, 15.0], - [-5.0, 20.0]]) - - # State Vector [x y yaw v]' - x_est = np.zeros((4, 1)) - x_true = np.zeros((4, 1)) - - px = np.zeros((4, NP)) # Particle store - pw = np.zeros((1, NP)) + 1.0 / NP # Particle weight - x_dr = np.zeros((4, 1)) # Dead reckoning - - # history - h_x_est = x_est - h_x_true = x_true - h_x_dr = x_true - - while SIM_TIME >= time: - time += DT - u = calc_input() - - x_true, z, x_dr, ud = observation(x_true, x_dr, u, rf_id) - - x_est, PEst, px, pw = pf_localization(px, pw, z, ud) - - # store data history - h_x_est = np.hstack((h_x_est, x_est)) - h_x_dr = np.hstack((h_x_dr, x_dr)) - h_x_true = np.hstack((h_x_true, x_true)) - - if show_animation: - 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]) - - for i in range(len(z[:, 0])): - plt.plot([x_true[0, 0], z[i, 1]], [x_true[1, 0], z[i, 2]], "-k") - plt.plot(rf_id[:, 0], rf_id[:, 1], "*k") - plt.plot(px[0, :], px[1, :], ".r") - plt.plot(np.array(h_x_true[0, :]).flatten(), - np.array(h_x_true[1, :]).flatten(), "-b") - plt.plot(np.array(h_x_dr[0, :]).flatten(), - np.array(h_x_dr[1, :]).flatten(), "-k") - plt.plot(np.array(h_x_est[0, :]).flatten(), - np.array(h_x_est[1, :]).flatten(), "-r") - plot_covariance_ellipse(x_est, PEst) - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/Localization/unscented_kalman_filter/unscented_kalman_filter.py b/Localization/unscented_kalman_filter/unscented_kalman_filter.py deleted file mode 100644 index 4af748ec71..0000000000 --- a/Localization/unscented_kalman_filter/unscented_kalman_filter.py +++ /dev/null @@ -1,264 +0,0 @@ -""" - -Unscented kalman filter (UKF) localization sample - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math - -import matplotlib.pyplot as plt -import numpy as np -import scipy.linalg - -from utils.angle import rot_mat_2d - -# Covariance for UKF simulation -Q = np.diag([ - 0.1, # variance of location on x-axis - 0.1, # variance of location on y-axis - np.deg2rad(1.0), # variance of yaw angle - 1.0 # variance of velocity -]) ** 2 # predict state covariance -R = np.diag([1.0, 1.0]) ** 2 # Observation x,y position covariance - -# Simulation parameter -INPUT_NOISE = np.diag([1.0, np.deg2rad(30.0)]) ** 2 -GPS_NOISE = np.diag([0.5, 0.5]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] - -# UKF Parameter -ALPHA = 0.001 -BETA = 2 -KAPPA = 0 - -show_animation = True - - -def calc_input(): - v = 1.0 # [m/s] - yawRate = 0.1 # [rad/s] - u = np.array([[v, yawRate]]).T - return u - - -def observation(xTrue, xd, u): - xTrue = motion_model(xTrue, u) - - # add noise to gps x-y - z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1) - - # add noise to input - ud = u + INPUT_NOISE @ np.random.randn(2, 1) - - xd = motion_model(xd, ud) - - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0, 0], - [0, 1.0, 0, 0], - [0, 0, 1.0, 0], - [0, 0, 0, 0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT], - [1.0, 0.0]]) - - x = F @ x + B @ u - - return x - - -def observation_model(x): - H = np.array([ - [1, 0, 0, 0], - [0, 1, 0, 0] - ]) - - z = H @ x - - return z - - -def generate_sigma_points(xEst, PEst, gamma): - sigma = xEst - Psqrt = scipy.linalg.sqrtm(PEst) - n = len(xEst[:, 0]) - # Positive direction - for i in range(n): - sigma = np.hstack((sigma, xEst + gamma * Psqrt[:, i:i + 1])) - - # Negative direction - for i in range(n): - sigma = np.hstack((sigma, xEst - gamma * Psqrt[:, i:i + 1])) - - return sigma - - -def predict_sigma_motion(sigma, u): - """ - Sigma Points prediction with motion model - """ - for i in range(sigma.shape[1]): - sigma[:, i:i + 1] = motion_model(sigma[:, i:i + 1], u) - - return sigma - - -def predict_sigma_observation(sigma): - """ - Sigma Points prediction with observation model - """ - for i in range(sigma.shape[1]): - sigma[0:2, i] = observation_model(sigma[:, i]) - - sigma = sigma[0:2, :] - - return sigma - - -def calc_sigma_covariance(x, sigma, wc, Pi): - nSigma = sigma.shape[1] - d = sigma - x[0:sigma.shape[0]] - P = Pi - for i in range(nSigma): - P = P + wc[0, i] * d[:, i:i + 1] @ d[:, i:i + 1].T - return P - - -def calc_pxz(sigma, x, z_sigma, zb, wc): - nSigma = sigma.shape[1] - dx = sigma - x - dz = z_sigma - zb[0:2] - P = np.zeros((dx.shape[0], dz.shape[0])) - - for i in range(nSigma): - P = P + wc[0, i] * dx[:, i:i + 1] @ dz[:, i:i + 1].T - - return P - - -def ukf_estimation(xEst, PEst, z, u, wm, wc, gamma): - # Predict - sigma = generate_sigma_points(xEst, PEst, gamma) - sigma = predict_sigma_motion(sigma, u) - xPred = (wm @ sigma.T).T - PPred = calc_sigma_covariance(xPred, sigma, wc, Q) - - # Update - zPred = observation_model(xPred) - y = z - zPred - sigma = generate_sigma_points(xPred, PPred, gamma) - zb = (wm @ sigma.T).T - z_sigma = predict_sigma_observation(sigma) - st = calc_sigma_covariance(zb, z_sigma, wc, R) - Pxz = calc_pxz(sigma, xPred, z_sigma, zb, wc) - K = Pxz @ np.linalg.inv(st) - xEst = xPred + K @ y - PEst = PPred - K @ st @ K.T - - return xEst, PEst - - -def plot_covariance_ellipse(xEst, PEst): # pragma: no cover - Pxy = PEst[0:2, 0:2] - eigval, eigvec = np.linalg.eig(Pxy) - - if eigval[0] >= eigval[1]: - bigind = 0 - smallind = 1 - else: - bigind = 1 - smallind = 0 - - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - a = math.sqrt(eigval[bigind]) - b = math.sqrt(eigval[smallind]) - x = [a * math.cos(it) for it in t] - y = [b * math.sin(it) for it in t] - angle = math.atan2(eigvec[1, bigind], eigvec[0, bigind]) - fx = rot_mat_2d(angle) @ np.array([x, y]) - px = np.array(fx[0, :] + xEst[0, 0]).flatten() - py = np.array(fx[1, :] + xEst[1, 0]).flatten() - plt.plot(px, py, "--r") - - -def setup_ukf(nx): - lamb = ALPHA ** 2 * (nx + KAPPA) - nx - # calculate weights - wm = [lamb / (lamb + nx)] - wc = [(lamb / (lamb + nx)) + (1 - ALPHA ** 2 + BETA)] - for i in range(2 * nx): - wm.append(1.0 / (2 * (nx + lamb))) - wc.append(1.0 / (2 * (nx + lamb))) - gamma = math.sqrt(nx + lamb) - - wm = np.array([wm]) - wc = np.array([wc]) - - return wm, wc, gamma - - -def main(): - print(__file__ + " start!!") - - nx = 4 # State Vector [x y yaw v]' - xEst = np.zeros((nx, 1)) - xTrue = np.zeros((nx, 1)) - PEst = np.eye(nx) - xDR = np.zeros((nx, 1)) # Dead reckoning - - wm, wc, gamma = setup_ukf(nx) - - # history - hxEst = xEst - hxTrue = xTrue - hxDR = xTrue - hz = np.zeros((2, 1)) - - time = 0.0 - - while SIM_TIME >= time: - time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u) - - xEst, PEst = ukf_estimation(xEst, PEst, z, ud, wm, wc, gamma) - - # store data history - hxEst = np.hstack((hxEst, xEst)) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - hz = np.hstack((hz, z)) - - if show_animation: - 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]) - plt.plot(hz[0, :], hz[1, :], ".g") - plt.plot(np.array(hxTrue[0, :]).flatten(), - np.array(hxTrue[1, :]).flatten(), "-b") - plt.plot(np.array(hxDR[0, :]).flatten(), - np.array(hxDR[1, :]).flatten(), "-k") - plt.plot(np.array(hxEst[0, :]).flatten(), - np.array(hxEst[1, :]).flatten(), "-r") - plot_covariance_ellipse(xEst, PEst) - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/Mapping/DistanceMap/distance_map.py b/Mapping/DistanceMap/distance_map.py deleted file mode 100644 index 0f96c9e8c6..0000000000 --- a/Mapping/DistanceMap/distance_map.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -Distance Map - -author: Wang Zheng (@Aglargil) - -Reference: - -- [Distance Map] -(https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf) -""" - -import numpy as np -import matplotlib.pyplot as plt -import scipy - -INF = 1e20 -ENABLE_PLOT = True - - -def compute_sdf_scipy(obstacles): - """ - Compute the signed distance field (SDF) from a boolean field using scipy. - This function has the same functionality as compute_sdf. - However, by using scipy.ndimage.distance_transform_edt, it can compute much faster. - - Example: 500×500 map - • compute_sdf: 3 sec - • compute_sdf_scipy: 0.05 sec - - Parameters - ---------- - obstacles : array_like - A 2D boolean array where '1' represents obstacles and '0' represents free space. - - Returns - ------- - array_like - A 2D array representing the signed distance field, where positive values indicate distance - to the nearest obstacle, and negative values indicate distance to the nearest free space. - """ - # distance_transform_edt use '0' as obstacles, so we need to convert the obstacles to '0' - a = scipy.ndimage.distance_transform_edt(obstacles == 0) - b = scipy.ndimage.distance_transform_edt(obstacles == 1) - return a - b - - -def compute_udf_scipy(obstacles): - """ - Compute the unsigned distance field (UDF) from a boolean field using scipy. - This function has the same functionality as compute_udf. - However, by using scipy.ndimage.distance_transform_edt, it can compute much faster. - - Example: 500×500 map - • compute_udf: 1.5 sec - • compute_udf_scipy: 0.02 sec - - Parameters - ---------- - obstacles : array_like - A 2D boolean array where '1' represents obstacles and '0' represents free space. - - Returns - ------- - array_like - A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`. - """ - return scipy.ndimage.distance_transform_edt(obstacles == 0) - - -def compute_sdf(obstacles): - """ - Compute the signed distance field (SDF) from a boolean field. - - Parameters - ---------- - obstacles : array_like - A 2D boolean array where '1' represents obstacles and '0' represents free space. - - Returns - ------- - array_like - A 2D array representing the signed distance field, where positive values indicate distance - to the nearest obstacle, and negative values indicate distance to the nearest free space. - """ - a = compute_udf(obstacles) - b = compute_udf(obstacles == 0) - return a - b - - -def compute_udf(obstacles): - """ - Compute the unsigned distance field (UDF) from a boolean field. - - Parameters - ---------- - obstacles : array_like - A 2D boolean array where '1' represents obstacles and '0' represents free space. - - Returns - ------- - array_like - A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`. - """ - edt = obstacles.copy() - if not np.all(np.isin(edt, [0, 1])): - raise ValueError("Input array should only contain 0 and 1") - edt = np.where(edt == 0, INF, edt) - edt = np.where(edt == 1, 0, edt) - for row in range(len(edt)): - dt(edt[row]) - edt = edt.T - for row in range(len(edt)): - dt(edt[row]) - edt = edt.T - return np.sqrt(edt) - - -def dt(d): - """ - Compute 1D distance transform under the squared Euclidean distance - - Parameters - ---------- - d : array_like - Input array containing the distances. - - Returns: - -------- - d : array_like - The transformed array with computed distances. - """ - v = np.zeros(len(d) + 1) - z = np.zeros(len(d) + 1) - k = 0 - v[0] = 0 - z[0] = -INF - z[1] = INF - for q in range(1, len(d)): - s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k]) - while s <= z[k]: - k = k - 1 - s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k]) - k = k + 1 - v[k] = q - z[k] = s - z[k + 1] = INF - k = 0 - for q in range(len(d)): - while z[k + 1] < q: - k = k + 1 - dx = q - v[k] - d[q] = dx * dx + d[int(v[k])] - - -def main(): - obstacles = np.array( - [ - [1, 0, 0, 0, 0], - [0, 1, 1, 1, 0], - [0, 1, 1, 1, 0], - [0, 0, 1, 1, 0], - [0, 0, 1, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 1, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - ] - ) - - # Compute the signed distance field - sdf = compute_sdf(obstacles) - udf = compute_udf(obstacles) - - if ENABLE_PLOT: - _, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5)) - - obstacles_plot = ax1.imshow(obstacles, cmap="binary") - ax1.set_title("Obstacles") - ax1.set_xlabel("x") - ax1.set_ylabel("y") - plt.colorbar(obstacles_plot, ax=ax1) - - udf_plot = ax2.imshow(udf, cmap="viridis") - ax2.set_title("Unsigned Distance Field") - ax2.set_xlabel("x") - ax2.set_ylabel("y") - plt.colorbar(udf_plot, ax=ax2) - - sdf_plot = ax3.imshow(sdf, cmap="RdBu") - ax3.set_title("Signed Distance Field") - ax3.set_xlabel("x") - ax3.set_ylabel("y") - plt.colorbar(sdf_plot, ax=ax3) - - plt.tight_layout() - plt.show() - - -if __name__ == "__main__": - main() diff --git a/Mapping/__init__.py b/Mapping/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Mapping/circle_fitting/circle_fitting.py b/Mapping/circle_fitting/circle_fitting.py deleted file mode 100644 index b5714b507c..0000000000 --- a/Mapping/circle_fitting/circle_fitting.py +++ /dev/null @@ -1,162 +0,0 @@ -""" - -Object shape recognition with circle fitting - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import matplotlib.pyplot as plt -import math -import random -import numpy as np - -show_animation = True - - -def circle_fitting(x, y): - """ - Fits a circle to a given set of points using a least-squares approach. - - This function calculates the center (x, y) and radius of a circle that best fits - the given set of points in a two-dimensional plane. It minimizes the squared - errors between the circle and the provided points and returns the calculated - center coordinates, radius, and the fitting error. - - Raises - ------ - ValueError - If the input lists x and y do not contain the same number of elements. - - Parameters - ---------- - x : list[float] - The x-coordinates of the points. - y : list[float] - The y-coordinates of the points. - - Returns - ------- - tuple[float, float, float, float] - A tuple containing: - - The x-coordinate of the center of the fitted circle (float). - - The y-coordinate of the center of the fitted circle (float). - - The radius of the fitted circle (float). - - The fitting error as a deviation metric (float). - """ - - sumx = sum(x) - sumy = sum(y) - sumx2 = sum([ix ** 2 for ix in x]) - sumy2 = sum([iy ** 2 for iy in y]) - sumxy = sum([ix * iy for (ix, iy) in zip(x, y)]) - - F = np.array([[sumx2, sumxy, sumx], - [sumxy, sumy2, sumy], - [sumx, sumy, len(x)]]) - - G = np.array([[-sum([ix ** 3 + ix * iy ** 2 for (ix, iy) in zip(x, y)])], - [-sum([ix ** 2 * iy + iy ** 3 for (ix, iy) in zip(x, y)])], - [-sum([ix ** 2 + iy ** 2 for (ix, iy) in zip(x, y)])]]) - - T = np.linalg.inv(F).dot(G) - - cxe = float(T[0, 0] / -2) - cye = float(T[1, 0] / -2) - re = math.sqrt(cxe**2 + cye**2 - T[2, 0]) - - error = sum([np.hypot(cxe - ix, cye - iy) - re for (ix, iy) in zip(x, y)]) - - return (cxe, cye, re, error) - - -def get_sample_points(cx, cy, cr, angle_reso): - x, y, angle, r = [], [], [], [] - - # points sampling - for theta in np.arange(0.0, 2.0 * math.pi, angle_reso): - nx = cx + cr * math.cos(theta) - ny = cy + cr * math.sin(theta) - nangle = math.atan2(ny, nx) - nr = math.hypot(nx, ny) * random.uniform(0.95, 1.05) - - x.append(nx) - y.append(ny) - angle.append(nangle) - r.append(nr) - - # ray casting filter - rx, ry = ray_casting_filter(x, y, angle, r, angle_reso) - - return rx, ry - - -def ray_casting_filter(xl, yl, thetal, rangel, angle_reso): - rx, ry = [], [] - rangedb = [float("inf") for _ in range( - int(math.floor((math.pi * 2.0) / angle_reso)) + 1)] - - for i, _ in enumerate(thetal): - angleid = math.floor(thetal[i] / angle_reso) - - if rangedb[angleid] > rangel[i]: - rangedb[angleid] = rangel[i] - - for i, _ in enumerate(rangedb): - t = i * angle_reso - if rangedb[i] != float("inf"): - rx.append(rangedb[i] * math.cos(t)) - ry.append(rangedb[i] * math.sin(t)) - - return rx, ry - - -def plot_circle(x, y, size, color="-b"): # pragma: no cover - deg = list(range(0, 360, 5)) - deg.append(0) - xl = [x + size * math.cos(np.deg2rad(d)) for d in deg] - yl = [y + size * math.sin(np.deg2rad(d)) for d in deg] - plt.plot(xl, yl, color) - - -def main(): - - # simulation parameters - simtime = 15.0 # simulation time - dt = 1.0 # time tick - - cx = -2.0 # initial x position of obstacle - cy = -8.0 # initial y position of obstacle - cr = 1.0 # obstacle radious - theta = np.deg2rad(30.0) # obstacle moving direction - angle_reso = np.deg2rad(3.0) # sensor angle resolution - - time = 0.0 - while time <= simtime: - time += dt - - cx += math.cos(theta) - cy += math.cos(theta) - - x, y = get_sample_points(cx, cy, cr, angle_reso) - - ex, ey, er, error = circle_fitting(x, y) - print("Error:", error) - - if show_animation: # pragma: no cover - 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]) - plt.axis("equal") - plt.plot(0.0, 0.0, "*r") - plot_circle(cx, cy, cr) - plt.plot(x, y, "xr") - plot_circle(ex, ey, er, "-r") - plt.pause(dt) - - print("Done") - - -if __name__ == '__main__': - main() diff --git a/Mapping/gaussian_grid_map/gaussian_grid_map.py b/Mapping/gaussian_grid_map/gaussian_grid_map.py deleted file mode 100644 index b7664f1acb..0000000000 --- a/Mapping/gaussian_grid_map/gaussian_grid_map.py +++ /dev/null @@ -1,86 +0,0 @@ -""" - -2D gaussian grid map sample - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import numpy as np -import matplotlib.pyplot as plt -from scipy.stats import norm - -EXTEND_AREA = 10.0 # [m] grid map extention length - -show_animation = True - - -def generate_gaussian_grid_map(ox, oy, xyreso, std): - - minx, miny, maxx, maxy, xw, yw = calc_grid_map_config(ox, oy, xyreso) - - gmap = [[0.0 for i in range(yw)] for i in range(xw)] - - for ix in range(xw): - for iy in range(yw): - - x = ix * xyreso + minx - y = iy * xyreso + miny - - # Search minimum distance - mindis = float("inf") - for (iox, ioy) in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if mindis >= d: - mindis = d - - pdf = (1.0 - norm.cdf(mindis, 0.0, std)) - gmap[ix][iy] = pdf - - return gmap, minx, maxx, miny, maxy - - -def calc_grid_map_config(ox, oy, xyreso): - minx = round(min(ox) - EXTEND_AREA / 2.0) - miny = round(min(oy) - EXTEND_AREA / 2.0) - maxx = round(max(ox) + EXTEND_AREA / 2.0) - maxy = round(max(oy) + EXTEND_AREA / 2.0) - xw = int(round((maxx - minx) / xyreso)) - yw = int(round((maxy - miny) / xyreso)) - - return minx, miny, maxx, maxy, xw, yw - - -def draw_heatmap(data, minx, maxx, miny, maxy, xyreso): - x, y = np.mgrid[slice(minx - xyreso / 2.0, maxx + xyreso / 2.0, xyreso), - slice(miny - xyreso / 2.0, maxy + xyreso / 2.0, xyreso)] - plt.pcolor(x, y, data, vmax=1.0, cmap=plt.cm.Blues) - plt.axis("equal") - - -def main(): - print(__file__ + " start!!") - - xyreso = 0.5 # xy grid resolution - STD = 5.0 # standard diviation for gaussian distribution - - for i in range(5): - ox = (np.random.rand(4) - 0.5) * 10.0 - oy = (np.random.rand(4) - 0.5) * 10.0 - gmap, minx, maxx, miny, maxy = generate_gaussian_grid_map( - ox, oy, xyreso, STD) - - if show_animation: # pragma: no cover - 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]) - draw_heatmap(gmap, minx, maxx, miny, maxy, xyreso) - plt.plot(ox, oy, "xr") - plt.plot(0.0, 0.0, "ob") - plt.pause(1.0) - - -if __name__ == '__main__': - main() diff --git a/Mapping/grid_map_lib/__init__.py b/Mapping/grid_map_lib/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Mapping/grid_map_lib/grid_map_lib.py b/Mapping/grid_map_lib/grid_map_lib.py deleted file mode 100644 index d08d8ec5ba..0000000000 --- a/Mapping/grid_map_lib/grid_map_lib.py +++ /dev/null @@ -1,312 +0,0 @@ -""" - -Grid map library in python - -author: Atsushi Sakai - -""" -from functools import total_ordering -import matplotlib.pyplot as plt -import numpy as np - - -@total_ordering -class FloatGrid: - - def __init__(self, init_val=0.0): - self.data = init_val - - def get_float_data(self): - return self.data - - def __eq__(self, other): - if not isinstance(other, FloatGrid): - return NotImplemented - return self.get_float_data() == other.get_float_data() - - def __lt__(self, other): - if not isinstance(other, FloatGrid): - return NotImplemented - return self.get_float_data() < other.get_float_data() - - -class GridMap: - """ - GridMap class - """ - - def __init__(self, width, height, resolution, - center_x, center_y, init_val=FloatGrid(0.0)): - """__init__ - - :param width: number of grid for width - :param height: number of grid for height - :param resolution: grid resolution [m] - :param center_x: center x position [m] - :param center_y: center y position [m] - :param init_val: initial value for all grid - """ - self.width = width - self.height = height - self.resolution = resolution - self.center_x = center_x - self.center_y = center_y - - self.left_lower_x = self.center_x - self.width / 2.0 * self.resolution - self.left_lower_y = self.center_y - self.height / 2.0 * self.resolution - - self.n_data = self.width * self.height - self.data = [init_val] * self.n_data - self.data_type = type(init_val) - - def get_value_from_xy_index(self, x_ind, y_ind): - """get_value_from_xy_index - - when the index is out of grid map area, return None - - :param x_ind: x index - :param y_ind: y index - """ - - grid_ind = self.calc_grid_index_from_xy_index(x_ind, y_ind) - - if 0 <= grid_ind < self.n_data: - return self.data[grid_ind] - else: - return None - - def get_xy_index_from_xy_pos(self, x_pos, y_pos): - """get_xy_index_from_xy_pos - - :param x_pos: x position [m] - :param y_pos: y position [m] - """ - x_ind = self.calc_xy_index_from_position( - x_pos, self.left_lower_x, self.width) - y_ind = self.calc_xy_index_from_position( - y_pos, self.left_lower_y, self.height) - - return x_ind, y_ind - - def set_value_from_xy_pos(self, x_pos, y_pos, val): - """set_value_from_xy_pos - - return bool flag, which means setting value is succeeded or not - - :param x_pos: x position [m] - :param y_pos: y position [m] - :param val: grid value - """ - - x_ind, y_ind = self.get_xy_index_from_xy_pos(x_pos, y_pos) - - if (not x_ind) or (not y_ind): - return False # NG - - flag = self.set_value_from_xy_index(x_ind, y_ind, val) - - return flag - - def set_value_from_xy_index(self, x_ind, y_ind, val): - """set_value_from_xy_index - - return bool flag, which means setting value is succeeded or not - - :param x_ind: x index - :param y_ind: y index - :param val: grid value - """ - - if (x_ind is None) or (y_ind is None): - return False, False - - grid_ind = int(y_ind * self.width + x_ind) - - if 0 <= grid_ind < self.n_data and isinstance(val, self.data_type): - self.data[grid_ind] = val - return True # OK - else: - return False # NG - - def set_value_from_polygon(self, pol_x, pol_y, val, inside=True): - """set_value_from_polygon - - Setting value inside or outside polygon - - :param pol_x: x position list for a polygon - :param pol_y: y position list for a polygon - :param val: grid value - :param inside: setting data inside or outside - """ - - # making ring polygon - if (pol_x[0] != pol_x[-1]) or (pol_y[0] != pol_y[-1]): - np.append(pol_x, pol_x[0]) - np.append(pol_y, pol_y[0]) - - # setting value for all grid - for x_ind in range(self.width): - for y_ind in range(self.height): - x_pos, y_pos = self.calc_grid_central_xy_position_from_xy_index( - x_ind, y_ind) - - flag = self.check_inside_polygon(x_pos, y_pos, pol_x, pol_y) - - if flag is inside: - self.set_value_from_xy_index(x_ind, y_ind, val) - - def calc_grid_index_from_xy_index(self, x_ind, y_ind): - grid_ind = int(y_ind * self.width + x_ind) - return grid_ind - - def calc_xy_index_from_grid_index(self, grid_ind): - y_ind, x_ind = divmod(grid_ind, self.width) - return x_ind, y_ind - - def calc_grid_index_from_xy_pos(self, x_pos, y_pos): - """get_xy_index_from_xy_pos - - :param x_pos: x position [m] - :param y_pos: y position [m] - """ - x_ind = self.calc_xy_index_from_position( - x_pos, self.left_lower_x, self.width) - y_ind = self.calc_xy_index_from_position( - y_pos, self.left_lower_y, self.height) - - return self.calc_grid_index_from_xy_index(x_ind, y_ind) - - def calc_grid_central_xy_position_from_grid_index(self, grid_ind): - x_ind, y_ind = self.calc_xy_index_from_grid_index(grid_ind) - return self.calc_grid_central_xy_position_from_xy_index(x_ind, y_ind) - - def calc_grid_central_xy_position_from_xy_index(self, x_ind, y_ind): - x_pos = self.calc_grid_central_xy_position_from_index( - x_ind, self.left_lower_x) - y_pos = self.calc_grid_central_xy_position_from_index( - y_ind, self.left_lower_y) - - return x_pos, y_pos - - def calc_grid_central_xy_position_from_index(self, index, lower_pos): - return lower_pos + index * self.resolution + self.resolution / 2.0 - - def calc_xy_index_from_position(self, pos, lower_pos, max_index): - ind = int(np.floor((pos - lower_pos) / self.resolution)) - if 0 <= ind <= max_index: - return ind - else: - return None - - def check_occupied_from_xy_index(self, x_ind, y_ind, occupied_val): - - val = self.get_value_from_xy_index(x_ind, y_ind) - - if val is None or val >= occupied_val: - return True - else: - return False - - def expand_grid(self, occupied_val=FloatGrid(1.0)): - x_inds, y_inds, values = [], [], [] - - for ix in range(self.width): - for iy in range(self.height): - if self.check_occupied_from_xy_index(ix, iy, occupied_val): - x_inds.append(ix) - y_inds.append(iy) - values.append(self.get_value_from_xy_index(ix, iy)) - - for (ix, iy, value) in zip(x_inds, y_inds, values): - self.set_value_from_xy_index(ix + 1, iy, val=value) - self.set_value_from_xy_index(ix, iy + 1, val=value) - self.set_value_from_xy_index(ix + 1, iy + 1, val=value) - self.set_value_from_xy_index(ix - 1, iy, val=value) - self.set_value_from_xy_index(ix, iy - 1, val=value) - self.set_value_from_xy_index(ix - 1, iy - 1, val=value) - - @staticmethod - def check_inside_polygon(iox, ioy, x, y): - - n_point = len(x) - 1 - inside = False - for i1 in range(n_point): - i2 = (i1 + 1) % (n_point + 1) - - if x[i1] >= x[i2]: - min_x, max_x = x[i2], x[i1] - else: - min_x, max_x = x[i1], x[i2] - if not min_x <= iox < max_x: - continue - - tmp1 = (y[i2] - y[i1]) / (x[i2] - x[i1]) - if (y[i1] + tmp1 * (iox - x[i1]) - ioy) > 0.0: - inside = not inside - - return inside - - def print_grid_map_info(self): - print("width:", self.width) - print("height:", self.height) - print("resolution:", self.resolution) - print("center_x:", self.center_x) - print("center_y:", self.center_y) - print("left_lower_x:", self.left_lower_x) - print("left_lower_y:", self.left_lower_y) - print("n_data:", self.n_data) - - def plot_grid_map(self, ax=None): - float_data_array = np.array([d.get_float_data() for d in self.data]) - grid_data = np.reshape(float_data_array, (self.height, self.width)) - if not ax: - fig, ax = plt.subplots() - heat_map = ax.pcolor(grid_data, cmap="Blues", vmin=0.0, vmax=1.0) - plt.axis("equal") - - return heat_map - - -def polygon_set_demo(): - ox = [0.0, 4.35, 20.0, 50.0, 100.0, 130.0, 40.0] - oy = [0.0, -4.15, -20.0, 0.0, 30.0, 60.0, 80.0] - - grid_map = GridMap(600, 290, 0.7, 60.0, 30.5) - - grid_map.set_value_from_polygon(ox, oy, FloatGrid(1.0), inside=False) - - grid_map.plot_grid_map() - - plt.axis("equal") - plt.grid(True) - - -def position_set_demo(): - grid_map = GridMap(100, 120, 0.5, 10.0, -0.5) - - grid_map.set_value_from_xy_pos(10.1, -1.1, FloatGrid(1.0)) - grid_map.set_value_from_xy_pos(10.1, -0.1, FloatGrid(1.0)) - grid_map.set_value_from_xy_pos(10.1, 1.1, FloatGrid(1.0)) - grid_map.set_value_from_xy_pos(11.1, 0.1, FloatGrid(1.0)) - grid_map.set_value_from_xy_pos(10.1, 0.1, FloatGrid(1.0)) - grid_map.set_value_from_xy_pos(9.1, 0.1, FloatGrid(1.0)) - - grid_map.plot_grid_map() - - plt.axis("equal") - plt.grid(True) - - -def main(): - print("start!!") - - position_set_demo() - polygon_set_demo() - - plt.show() - - print("done!!") - - -if __name__ == '__main__': - main() diff --git a/Mapping/kmeans_clustering/kmeans_clustering.py b/Mapping/kmeans_clustering/kmeans_clustering.py deleted file mode 100644 index cee01e5ad5..0000000000 --- a/Mapping/kmeans_clustering/kmeans_clustering.py +++ /dev/null @@ -1,174 +0,0 @@ -""" - -Object clustering with k-means algorithm - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import matplotlib.pyplot as plt -import random - -# k means parameters -MAX_LOOP = 10 -DCOST_TH = 0.1 -show_animation = True - - -def kmeans_clustering(rx, ry, nc): - """ - Performs k-means clustering on the given dataset, iteratively adjusting cluster centroids - until convergence within a defined threshold or reaching the maximum number of - iterations. - - The implementation initializes clusters, calculates initial centroids, and refines the - clusters through iterative updates to optimize the cost function based on minimum - distance between datapoints and centroids. - - Arguments: - rx: List[float] - The x-coordinates of the dataset points to be clustered. - ry: List[float] - The y-coordinates of the dataset points to be clustered. - nc: int - The number of clusters to group the data into. - - Returns: - Clusters - An instance containing the final cluster assignments and centroids after - convergence. - - Raises: - None - - """ - clusters = Clusters(rx, ry, nc) - clusters.calc_centroid() - - pre_cost = float("inf") - for loop in range(MAX_LOOP): - cost = clusters.update_clusters() - clusters.calc_centroid() - - d_cost = abs(cost - pre_cost) - if d_cost < DCOST_TH: - break - pre_cost = cost - - return clusters - - -class Clusters: - - def __init__(self, x, y, n_label): - self.x = x - self.y = y - self.n_data = len(self.x) - self.n_label = n_label - self.labels = [random.randint(0, n_label - 1) - for _ in range(self.n_data)] - self.center_x = [0.0 for _ in range(n_label)] - self.center_y = [0.0 for _ in range(n_label)] - - def plot_cluster(self): - for label in set(self.labels): - x, y = self._get_labeled_x_y(label) - plt.plot(x, y, ".") - - def calc_centroid(self): - for label in set(self.labels): - x, y = self._get_labeled_x_y(label) - n_data = len(x) - self.center_x[label] = sum(x) / n_data - self.center_y[label] = sum(y) / n_data - - def update_clusters(self): - cost = 0.0 - - for ip in range(self.n_data): - px = self.x[ip] - py = self.y[ip] - - dx = [icx - px for icx in self.center_x] - dy = [icy - py for icy in self.center_y] - - dist_list = [math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)] - min_dist = min(dist_list) - min_id = dist_list.index(min_dist) - self.labels[ip] = min_id - cost += min_dist - - return cost - - def _get_labeled_x_y(self, target_label): - x = [self.x[i] for i, label in enumerate(self.labels) if label == target_label] - y = [self.y[i] for i, label in enumerate(self.labels) if label == target_label] - return x, y - - -def calc_raw_data(cx, cy, n_points, rand_d): - rx, ry = [], [] - - for (icx, icy) in zip(cx, cy): - for _ in range(n_points): - rx.append(icx + rand_d * (random.random() - 0.5)) - ry.append(icy + rand_d * (random.random() - 0.5)) - - return rx, ry - - -def update_positions(cx, cy): - # object moving parameters - DX1 = 0.4 - DY1 = 0.5 - DX2 = -0.3 - DY2 = -0.5 - - cx[0] += DX1 - cy[0] += DY1 - cx[1] += DX2 - cy[1] += DY2 - - return cx, cy - - -def main(): - print(__file__ + " start!!") - - cx = [0.0, 8.0] - cy = [0.0, 8.0] - n_points = 10 - rand_d = 3.0 - n_cluster = 2 - sim_time = 15.0 - dt = 1.0 - time = 0.0 - - while time <= sim_time: - print("Time:", time) - time += dt - - # objects moving simulation - cx, cy = update_positions(cx, cy) - raw_x, raw_y = calc_raw_data(cx, cy, n_points, rand_d) - - clusters = kmeans_clustering(raw_x, raw_y, n_cluster) - - # for animation - if show_animation: # pragma: no cover - 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]) - clusters.plot_cluster() - plt.plot(cx, cy, "or") - plt.xlim(-2.0, 10.0) - plt.ylim(-2.0, 10.0) - plt.pause(dt) - - print("Done") - - -if __name__ == '__main__': - main() diff --git a/Mapping/lidar_to_grid_map/lidar01.csv b/Mapping/lidar_to_grid_map/lidar01.csv deleted file mode 100644 index 1f6b3f5cd9..0000000000 --- a/Mapping/lidar_to_grid_map/lidar01.csv +++ /dev/null @@ -1,154 +0,0 @@ -0.008450416037156572,0.5335 -0.046902201120156306,0.5345 -0.08508127850753233,0.537 -0.1979822644959155,0.2605 -0.21189035697274505,0.2625 -0.2587960806200922,0.26475 -0.3043382657893199,0.2675 -0.34660795861105775,0.27075 -0.43632879047139106,0.59 -0.4739624524675188,0.60025 -0.5137777760286397,0.611 -0.5492297764597742,0.6265 -0.5895905154121426,0.64 -0.6253152235389017,0.6565 -0.6645851317087743,0.676 -0.6997644244442851,0.6975 -0.7785769484796541,0.3345 -0.7772134100015329,0.74575 -0.8652979956881222,0.3315 -0.8996591653367609,0.31775 -0.9397471965935056,0.31275 -0.9847439663714841,0.31125 -1.0283771976713423,0.31325 -1.0641019057981014,0.31975 -1.1009174447073562,0.3335 -1.2012738766970301,0.92275 -1.2397256617800307,0.95325 -1.2779047391674068,0.9865 -1.316629231946031,1.01775 -1.3561718478115274,1.011 -1.3948963405901518,1.0055 -1.4330754179775278,1.00225 -1.4731634492342724,0.99975 -1.5113425266216485,0.9975 -1.5517032655740168,1.001 -1.5896096352657691,1.00275 -1.6288795434356418,1.008 -1.6684221593011381,1.0135 -1.7066012366885142,1.022 -1.7453257294671385,1.02875 -1.7862318838107551,0.9935 -1.8257744996762515,1.0025 -1.8661352386286207,0.96075 -1.9045870237116205,0.92125 -1.9465840088377355,0.8855 -1.986944747790103,0.85725 -2.025669240568728,0.832 -2.065757271825472,0.80675 -2.1066634261690886,0.78875 -2.1464787497302105,0.7705 -2.1865667809869542,0.75625 -2.2261093968524506,0.74475 -2.2683790896741876,0.68275 -2.3090125363221823,0.6375 -2.3510095214482956,0.59925 -2.3916429680962885,0.5665 -2.4330945378311526,0.538 -2.4783640153047557,0.50825 -2.5203610004308707,0.4875 -2.562903400948233,0.46825 -2.599173524466238,0.45 -2.642806755766097,0.4355 -2.685076448587836,0.42275 -2.722437402888339,0.4125 -2.766888757275069,0.40125 -2.8007045115324587,0.39525 -2.841883373571701,0.385 -2.8819714048284446,0.3805 -2.922332143780814,0.38575 -2.9637837135156797,0.38425 -3.0005992524249336,0.36575 -3.0401418682904318,0.3765 -3.079957191851552,0.3915 -3.115409192282687,0.408 -3.154679100452558,0.4265 -3.184949654666836,0.447 -3.2242195628367085,0.4715 -3.2574899017028507,0.49875 -3.2959416867858504,0.52875 -3.3292120256519926,0.564 -3.3665729799524957,0.6055 -3.4031158111661277,0.6515 -3.438022396206014,0.70675 -3.4756560582021407,0.771 -3.513562427893893,0.77075 -3.5522869206725183,0.7785 -3.621827383056667,0.79575 -3.65918833735717,0.8045 -3.697367414744546,0.81725 -3.7377281536969154,0.83325 -3.775634523388667,0.8415 -3.8135408930804187,0.85575 -3.8522653858590425,0.87325 -3.8898990478551703,0.88725 -3.9299870791119154,0.906 -3.9665299103255465,0.9265 -4.006072526191043,0.94575 -4.043978895882795,0.97175 -4.081885265574547,1.02075 -4.1206097583531704,1.046 -4.1587888357405465,1.07025 -4.196422497736674,1.097 -4.234874282819675,1.12575 -4.286688744988257,0.73475 -4.324322406984384,0.72225 -4.364410438241129,0.731 -4.405862007975994,0.7405 -4.44267754688525,0.749 -4.484129116620115,0.76025 -4.522853609398739,0.76825 -4.560759979090491,0.77125 -4.5989390564778665,0.77725 -4.640117918517108,0.782 -4.679115118991357,0.78425 -4.717294196378733,0.789 -4.757109519939853,0.78825 -4.796652135805349,0.7855 -4.8337403824102285,0.786 -4.871646752101981,0.78275 -4.9109166602718535,0.7785 -4.950186568441726,0.7635 -4.990274599698471,0.74725 -5.028180969390222,0.737 -5.0677235852557185,0.72575 -5.109720570381833,0.71525 -5.149263186247329,0.70625 -5.1863514328522085,0.69975 -5.230530079543315,0.693 -5.269799987713188,0.68925 -5.307979065100563,0.68425 -5.347248973270435,0.68275 -5.386518881440308,0.68075 -5.426606912697053,0.68175 -5.465604113171301,0.67825 -5.502419652080556,0.6835 -5.543871221815422,0.6885 -5.580959468420302,0.67925 -5.624319992024535,0.6555 -5.660044700151294,0.639 -5.700950854494911,0.623 -5.740220762664784,0.6075 -5.783581286269018,0.59475 -5.820124117482649,0.58475 -5.861848394913139,0.57325 -5.899209349213642,0.565 -5.938751965079138,0.55525 -5.9782945809446355,0.55175 -6.017564489114507,0.546 -6.059288766544997,0.5405 -6.097467843932373,0.53825 -6.139464829058487,0.534 -6.176825783358991,0.5325 -6.215822983833238,0.53125 -6.252911230438118,0.53075 \ No newline at end of file diff --git a/Mapping/lidar_to_grid_map/lidar_to_grid_map.py b/Mapping/lidar_to_grid_map/lidar_to_grid_map.py deleted file mode 100644 index ad987392f5..0000000000 --- a/Mapping/lidar_to_grid_map/lidar_to_grid_map.py +++ /dev/null @@ -1,240 +0,0 @@ -""" - -LIDAR to 2D grid map example - -author: Erno Horvath, Csaba Hajdu based on Atsushi Sakai's scripts - -""" - -import math -from collections import deque - -import matplotlib.pyplot as plt -import numpy as np - -EXTEND_AREA = 1.0 - - -def file_read(f): - """ - Reading LIDAR laser beams (angles and corresponding distance data) - """ - with open(f) as data: - measures = [line.split(",") for line in data] - angles = [] - distances = [] - for measure in measures: - angles.append(float(measure[0])) - distances.append(float(measure[1])) - angles = np.array(angles) - distances = np.array(distances) - return angles, distances - - -def bresenham(start, end): - """ - Implementation of Bresenham's line drawing algorithm - See en.wikipedia.org/wiki/Bresenham's_line_algorithm - Bresenham's Line Algorithm - Produces a np.array from start and end (original from roguebasin.com) - >>> points1 = bresenham((4, 4), (6, 10)) - >>> print(points1) - np.array([[4,4], [4,5], [5,6], [5,7], [5,8], [6,9], [6,10]]) - """ - # setup initial conditions - x1, y1 = start - x2, y2 = end - dx = x2 - x1 - dy = y2 - y1 - is_steep = abs(dy) > abs(dx) # determine how steep the line is - if is_steep: # rotate line - x1, y1 = y1, x1 - x2, y2 = y2, x2 - # swap start and end points if necessary and store swap state - swapped = False - if x1 > x2: - x1, x2 = x2, x1 - y1, y2 = y2, y1 - swapped = True - dx = x2 - x1 # recalculate differentials - dy = y2 - y1 # recalculate differentials - error = int(dx / 2.0) # calculate error - y_step = 1 if y1 < y2 else -1 - # iterate over bounding box generating points between start and end - y = y1 - points = [] - for x in range(x1, x2 + 1): - coord = [y, x] if is_steep else (x, y) - points.append(coord) - error -= abs(dy) - if error < 0: - y += y_step - error += dx - if swapped: # reverse the list if the coordinates were swapped - points.reverse() - points = np.array(points) - return points - - -def calc_grid_map_config(ox, oy, xy_resolution): - """ - Calculates the size, and the maximum distances according to the the - measurement center - """ - min_x = round(min(ox) - EXTEND_AREA / 2.0) - min_y = round(min(oy) - EXTEND_AREA / 2.0) - max_x = round(max(ox) + EXTEND_AREA / 2.0) - max_y = round(max(oy) + EXTEND_AREA / 2.0) - xw = int(round((max_x - min_x) / xy_resolution)) - yw = int(round((max_y - min_y) / xy_resolution)) - print("The grid map is ", xw, "x", yw, ".") - return min_x, min_y, max_x, max_y, xw, yw - - -def atan_zero_to_twopi(y, x): - angle = math.atan2(y, x) - if angle < 0.0: - angle += math.pi * 2.0 - return angle - - -def init_flood_fill(center_point, obstacle_points, xy_points, min_coord, - xy_resolution): - """ - center_point: center point - obstacle_points: detected obstacles points (x,y) - xy_points: (x,y) point pairs - """ - center_x, center_y = center_point - prev_ix, prev_iy = center_x - 1, center_y - ox, oy = obstacle_points - xw, yw = xy_points - min_x, min_y = min_coord - occupancy_map = (np.ones((xw, yw))) * 0.5 - for (x, y) in zip(ox, oy): - # x coordinate of the the occupied area - ix = int(round((x - min_x) / xy_resolution)) - # y coordinate of the the occupied area - iy = int(round((y - min_y) / xy_resolution)) - free_area = bresenham((prev_ix, prev_iy), (ix, iy)) - for fa in free_area: - occupancy_map[fa[0]][fa[1]] = 0 # free area 0.0 - prev_ix = ix - prev_iy = iy - return occupancy_map - - -def flood_fill(center_point, occupancy_map): - """ - center_point: starting point (x,y) of fill - occupancy_map: occupancy map generated from Bresenham ray-tracing - """ - # Fill empty areas with queue method - sx, sy = occupancy_map.shape - fringe = deque() - fringe.appendleft(center_point) - while fringe: - n = fringe.pop() - nx, ny = n - # West - if nx > 0: - if occupancy_map[nx - 1, ny] == 0.5: - occupancy_map[nx - 1, ny] = 0.0 - fringe.appendleft((nx - 1, ny)) - # East - if nx < sx - 1: - if occupancy_map[nx + 1, ny] == 0.5: - occupancy_map[nx + 1, ny] = 0.0 - fringe.appendleft((nx + 1, ny)) - # North - if ny > 0: - if occupancy_map[nx, ny - 1] == 0.5: - occupancy_map[nx, ny - 1] = 0.0 - fringe.appendleft((nx, ny - 1)) - # South - if ny < sy - 1: - if occupancy_map[nx, ny + 1] == 0.5: - occupancy_map[nx, ny + 1] = 0.0 - fringe.appendleft((nx, ny + 1)) - - -def generate_ray_casting_grid_map(ox, oy, xy_resolution, breshen=True): - """ - The breshen boolean tells if it's computed with bresenham ray casting - (True) or with flood fill (False) - """ - min_x, min_y, max_x, max_y, x_w, y_w = calc_grid_map_config( - ox, oy, xy_resolution) - # default 0.5 -- [[0.5 for i in range(y_w)] for i in range(x_w)] - occupancy_map = np.ones((x_w, y_w)) / 2 - center_x = int( - round(-min_x / xy_resolution)) # center x coordinate of the grid map - center_y = int( - round(-min_y / xy_resolution)) # center y coordinate of the grid map - # occupancy grid computed with bresenham ray casting - if breshen: - for (x, y) in zip(ox, oy): - # x coordinate of the the occupied area - ix = int(round((x - min_x) / xy_resolution)) - # y coordinate of the the occupied area - iy = int(round((y - min_y) / xy_resolution)) - laser_beams = bresenham((center_x, center_y), ( - ix, iy)) # line form the lidar to the occupied point - for laser_beam in laser_beams: - occupancy_map[laser_beam[0]][ - laser_beam[1]] = 0.0 # free area 0.0 - occupancy_map[ix][iy] = 1.0 # occupied area 1.0 - occupancy_map[ix + 1][iy] = 1.0 # extend the occupied area - occupancy_map[ix][iy + 1] = 1.0 # extend the occupied area - occupancy_map[ix + 1][iy + 1] = 1.0 # extend the occupied area - # occupancy grid computed with with flood fill - else: - occupancy_map = init_flood_fill((center_x, center_y), (ox, oy), - (x_w, y_w), - (min_x, min_y), xy_resolution) - flood_fill((center_x, center_y), occupancy_map) - occupancy_map = np.array(occupancy_map, dtype=float) - for (x, y) in zip(ox, oy): - ix = int(round((x - min_x) / xy_resolution)) - iy = int(round((y - min_y) / xy_resolution)) - occupancy_map[ix][iy] = 1.0 # occupied area 1.0 - occupancy_map[ix + 1][iy] = 1.0 # extend the occupied area - occupancy_map[ix][iy + 1] = 1.0 # extend the occupied area - occupancy_map[ix + 1][iy + 1] = 1.0 # extend the occupied area - return occupancy_map, min_x, max_x, min_y, max_y, xy_resolution - - -def main(): - """ - Example usage - """ - print(__file__, "start") - xy_resolution = 0.02 # x-y grid resolution - ang, dist = file_read("lidar01.csv") - ox = np.sin(ang) * dist - oy = np.cos(ang) * dist - occupancy_map, min_x, max_x, min_y, max_y, xy_resolution = \ - generate_ray_casting_grid_map(ox, oy, xy_resolution, True) - xy_res = np.array(occupancy_map).shape - plt.figure(1, figsize=(10, 4)) - plt.subplot(122) - plt.imshow(occupancy_map, cmap="PiYG_r") - # cmap = "binary" "PiYG_r" "PiYG_r" "bone" "bone_r" "RdYlGn_r" - plt.clim(-0.4, 1.4) - plt.gca().set_xticks(np.arange(-.5, xy_res[1], 1), minor=True) - plt.gca().set_yticks(np.arange(-.5, xy_res[0], 1), minor=True) - plt.grid(True, which="minor", color="w", linewidth=0.6, alpha=0.5) - plt.colorbar() - plt.subplot(121) - plt.plot([oy, np.zeros(np.size(oy))], [ox, np.zeros(np.size(oy))], "ro-") - plt.axis("equal") - plt.plot(0.0, 0.0, "ob") - plt.gca().set_aspect("equal", "box") - bottom, top = plt.ylim() # return the current y-lim - plt.ylim((top, bottom)) # rescale y axis, to match the grid orientation - plt.grid(True) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/Mapping/ndt_map/ndt_map.py b/Mapping/ndt_map/ndt_map.py deleted file mode 100644 index f4f3299662..0000000000 --- a/Mapping/ndt_map/ndt_map.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Normal Distribution Transform (NDTGrid) mapping sample -""" -import matplotlib.pyplot as plt -import numpy as np -from collections import defaultdict - -from Mapping.grid_map_lib.grid_map_lib import GridMap -from utils.plot import plot_covariance_ellipse - - -class NDTMap: - """ - Normal Distribution Transform (NDT) map class - - :param ox: obstacle x position list - :param oy: obstacle y position list - :param resolution: grid resolution [m] - """ - - class NDTGrid: - """ - NDT grid - """ - - def __init__(self): - #: Number of points in the NDTGrid grid - self.n_points = 0 - #: Mean x position of points in the NDTGrid cell - self.mean_x = None - #: Mean y position of points in the NDTGrid cell - self.mean_y = None - #: Center x position of the NDT grid - self.center_grid_x = None - #: Center y position of the NDT grid - self.center_grid_y = None - #: Covariance matrix of the NDT grid - self.covariance = None - #: Eigen vectors of the NDT grid - self.eig_vec = None - #: Eigen values of the NDT grid - self.eig_values = None - - def __init__(self, ox, oy, resolution): - #: Minimum number of points in the NDT grid - self.min_n_points = 3 - #: Resolution of the NDT grid [m] - self.resolution = resolution - width = int((max(ox) - min(ox))/resolution) + 3 # rounding up + right and left margin - height = int((max(oy) - min(oy))/resolution) + 3 - center_x = np.mean(ox) - center_y = np.mean(oy) - self.ox = ox - self.oy = oy - #: NDT grid index map - self.grid_index_map = self._create_grid_index_map(ox, oy) - - #: NDT grid map. Each grid contains NDTGrid object - self._construct_grid_map(center_x, center_y, height, ox, oy, resolution, width) - - def _construct_grid_map(self, center_x, center_y, height, ox, oy, resolution, width): - self.grid_map = GridMap(width, height, resolution, center_x, center_y, self.NDTGrid()) - for grid_index, inds in self.grid_index_map.items(): - ndt = self.NDTGrid() - ndt.n_points = len(inds) - if ndt.n_points >= self.min_n_points: - ndt.mean_x = np.mean(ox[inds]) - ndt.mean_y = np.mean(oy[inds]) - ndt.center_grid_x, ndt.center_grid_y = \ - self.grid_map.calc_grid_central_xy_position_from_grid_index(grid_index) - ndt.covariance = np.cov(ox[inds], oy[inds]) - ndt.eig_values, ndt.eig_vec = np.linalg.eig(ndt.covariance) - self.grid_map.data[grid_index] = ndt - - def _create_grid_index_map(self, ox, oy): - grid_index_map = defaultdict(list) - for i in range(len(ox)): - grid_index = self.grid_map.calc_grid_index_from_xy_pos(ox[i], oy[i]) - grid_index_map[grid_index].append(i) - return grid_index_map - - -def create_dummy_observation_data(): - ox = [] - oy = [] - # left corridor - for y in range(-50, 50): - ox.append(-20.0) - oy.append(y) - # right corridor 1 - for y in range(-50, 0): - ox.append(20.0) - oy.append(y) - # right corridor 2 - for x in range(20, 50): - ox.append(x) - oy.append(0) - # right corridor 3 - for x in range(20, 50): - ox.append(x) - oy.append(x/2.0+10) - # right corridor 4 - for y in range(20, 50): - ox.append(20) - oy.append(y) - ox = np.array(ox) - oy = np.array(oy) - # Adding random noize - ox += np.random.rand(len(ox)) * 1.0 - oy += np.random.rand(len(ox)) * 1.0 - return ox, oy - - -def main(): - print(__file__ + " start!!") - - ox, oy = create_dummy_observation_data() - grid_resolution = 10.0 - ndt_map = NDTMap(ox, oy, grid_resolution) - - # plot raw observation - plt.plot(ox, oy, ".r") - - # plot grid clustering - [plt.plot(ox[inds], oy[inds], "x") for inds in ndt_map.grid_index_map.values()] - - # plot ndt grid map - [plot_covariance_ellipse(ndt.mean_x, ndt.mean_y, ndt.covariance, color="-k") for ndt in ndt_map.grid_map.data if ndt.n_points > 0] - - plt.axis("equal") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/Mapping/normal_vector_estimation/normal_vector_estimation.py b/Mapping/normal_vector_estimation/normal_vector_estimation.py deleted file mode 100644 index 996ba3ffee..0000000000 --- a/Mapping/normal_vector_estimation/normal_vector_estimation.py +++ /dev/null @@ -1,177 +0,0 @@ -import numpy as np -from matplotlib import pyplot as plt - -from utils.plot import plot_3d_vector_arrow, plot_triangle, set_equal_3d_axis - -show_animation = True - - -def calc_normal_vector(p1, p2, p3): - """Calculate normal vector of triangle - - Parameters - ---------- - p1 : np.array - 3D point - p2 : np.array - 3D point - p3 : np.array - 3D point - - Returns - ------- - normal_vector : np.array - normal vector (3,) - - """ - # calculate two vectors of triangle - v1 = p2 - p1 - v2 = p3 - p1 - - # calculate normal vector - normal_vector = np.cross(v1, v2) - - # normalize vector - normal_vector = normal_vector / np.linalg.norm(normal_vector) - - return normal_vector - - -def sample_3d_points_from_a_plane(num_samples, normal): - points_2d = np.random.normal(size=(num_samples, 2)) # 2D points on a plane - d = 0 - for i in range(len(points_2d)): - point_3d = np.append(points_2d[i], 0) - d += normal @ point_3d - d /= len(points_2d) - - points_3d = np.zeros((len(points_2d), 3)) - for i in range(len(points_2d)): - point_2d = np.append(points_2d[i], 0) - projection_length = (d - normal @ point_2d) / np.linalg.norm(normal) - points_3d[i] = point_2d + projection_length * normal - - return points_3d - - -def distance_to_plane(point, normal, origin): - dot_product = np.dot(normal, point) - np.dot(normal, origin) - if np.isclose(dot_product, 0): - return 0.0 - else: - distance = abs(dot_product) / np.linalg.norm(normal) - return distance - - -def ransac_normal_vector_estimation(points_3d, inlier_radio_th=0.7, - inlier_dist=0.1, p=0.99): - """ - RANSAC based normal vector estimation - - Parameters - ---------- - points_3d : np.array - 3D points (N, 3) - inlier_radio_th : float - Inlier ratio threshold. If inlier ratio is larger than this value, - the iteration is stopped. Default is 0.7. - inlier_dist : float - Inlier distance threshold. If distance between points and estimated - plane is smaller than this value, the point is inlier. Default is 0.1. - p : float - Probability that at least one of the sets of random samples does not - include an outlier. If this probability is near 1, the iteration - number is large. Default is 0.99. - - Returns - ------- - center_vector : np.array - Center of estimated plane. (3,) - normal_vector : np.array - Normal vector of estimated plane. (3,) - - """ - center = np.mean(points_3d, axis=0) - - max_iter = int(np.floor(np.log(1.0-p)/np.log(1.0-(1.0-inlier_radio_th)**3))) - - for ite in range(max_iter): - # Random sampling - sampled_ids = np.random.choice(points_3d.shape[0], size=3, - replace=False) - sampled_points = points_3d[sampled_ids, :] - p1 = sampled_points[0, :] - p2 = sampled_points[1, :] - p3 = sampled_points[2, :] - normal_vector = calc_normal_vector(p1, p2, p3) - - # calc inlier ratio - n_inliner = 0 - for i in range(points_3d.shape[0]): - p = points_3d[i, :] - if distance_to_plane(p, normal_vector, center) <= inlier_dist: - n_inliner += 1 - inlier_ratio = n_inliner / points_3d.shape[0] - print(f"Iter:{ite}, {inlier_ratio=}") - if inlier_ratio > inlier_radio_th: - return center, normal_vector - - return center, None - - -def main1(): - p1 = np.array([0.0, 0.0, 1.0]) - p2 = np.array([1.0, 1.0, 0.0]) - p3 = np.array([0.0, 1.0, 0.0]) - - center = np.mean([p1, p2, p3], axis=0) - normal_vector = calc_normal_vector(p1, p2, p3) - print(f"{center=}") - print(f"{normal_vector=}") - - if show_animation: - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - set_equal_3d_axis(ax, [0.0, 2.5], [0.0, 2.5], [0.0, 3.0]) - plot_triangle(p1, p2, p3, ax) - ax.plot(center[0], center[1], center[2], "ro") - plot_3d_vector_arrow(ax, center, center + normal_vector) - plt.show() - - -def main2(rng=None): - true_normal = np.array([0, 1, 1]) - true_normal = true_normal / np.linalg.norm(true_normal) - num_samples = 100 - noise_scale = 0.1 - - points_3d = sample_3d_points_from_a_plane(num_samples, true_normal) - # add random noise - points_3d += np.random.normal(size=points_3d.shape, scale=noise_scale) - - print(f"{points_3d.shape=}") - - center, estimated_normal = ransac_normal_vector_estimation( - points_3d, inlier_dist=noise_scale) - - if estimated_normal is None: - print("Failed to estimate normal vector") - return - - print(f"{true_normal=}") - print(f"{estimated_normal=}") - - if show_animation: - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - ax.plot(points_3d[:, 0], points_3d[:, 1], points_3d[:, 2], ".r") - plot_3d_vector_arrow(ax, center, center + true_normal) - plot_3d_vector_arrow(ax, center, center + estimated_normal) - set_equal_3d_axis(ax, [-3.0, 3.0], [-3.0, 3.0], [-3.0, 3.0]) - plt.title("RANSAC based Normal vector estimation") - plt.show() - - -if __name__ == '__main__': - # main1() - main2() diff --git a/Mapping/point_cloud_sampling/point_cloud_sampling.py b/Mapping/point_cloud_sampling/point_cloud_sampling.py deleted file mode 100644 index df7cde41c0..0000000000 --- a/Mapping/point_cloud_sampling/point_cloud_sampling.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Point cloud sampling example codes. This code supports -- Voxel point sampling -- Farthest point sampling -- Poisson disk sampling - -""" -import matplotlib.pyplot as plt -import numpy as np -import numpy.typing as npt -from collections import defaultdict - -do_plot = True - - -def voxel_point_sampling(original_points: npt.NDArray, voxel_size: float): - """ - Voxel Point Sampling function. - This function sample N-dimensional points with voxel grid. - Points in a same voxel grid will be merged by mean operation for sampling. - - Parameters - ---------- - original_points : (M, N) N-dimensional points for sampling. - The number of points is M. - voxel_size : voxel grid size - - Returns - ------- - sampled points (M', N) - """ - voxel_dict = defaultdict(list) - for i in range(original_points.shape[0]): - xyz = original_points[i, :] - xyz_index = tuple(xyz // voxel_size) - voxel_dict[xyz_index].append(xyz) - points = np.vstack([np.mean(v, axis=0) for v in voxel_dict.values()]) - return points - - -def farthest_point_sampling(orig_points: npt.NDArray, - n_points: int, seed: int): - """ - Farthest point sampling function - This function sample N-dimensional points with the farthest point policy. - - Parameters - ---------- - orig_points : (M, N) N-dimensional points for sampling. - The number of points is M. - n_points : number of points for sampling - seed : random seed number - - Returns - ------- - sampled points (n_points, N) - - """ - rng = np.random.default_rng(seed) - n_orig_points = orig_points.shape[0] - first_point_id = rng.choice(range(n_orig_points)) - min_distances = np.ones(n_orig_points) * float("inf") - selected_ids = [first_point_id] - while len(selected_ids) < n_points: - base_point = orig_points[selected_ids[-1], :] - distances = np.linalg.norm(orig_points[np.newaxis, :] - base_point, - axis=2).flatten() - min_distances = np.minimum(min_distances, distances) - distances_rank = np.argsort(-min_distances) # Farthest order - for i in distances_rank: # From the farthest point - if i not in selected_ids: # if not selected yes, select it - selected_ids.append(i) - break - return orig_points[selected_ids, :] - - -def poisson_disk_sampling(orig_points: npt.NDArray, n_points: int, - min_distance: float, seed: int, MAX_ITER=1000): - """ - Poisson disk sampling function - This function sample N-dimensional points randomly until the number of - points keeping minimum distance between selected points. - - Parameters - ---------- - orig_points : (M, N) N-dimensional points for sampling. - The number of points is M. - n_points : number of points for sampling - min_distance : minimum distance between selected points. - seed : random seed number - MAX_ITER : Maximum number of iteration. Default is 1000. - - Returns - ------- - sampled points (n_points or less, N) - """ - rng = np.random.default_rng(seed) - selected_id = rng.choice(range(orig_points.shape[0])) - selected_ids = [selected_id] - loop = 0 - while len(selected_ids) < n_points and loop <= MAX_ITER: - selected_id = rng.choice(range(orig_points.shape[0])) - base_point = orig_points[selected_id, :] - distances = np.linalg.norm( - orig_points[np.newaxis, selected_ids] - base_point, - axis=2).flatten() - if min(distances) >= min_distance: - selected_ids.append(selected_id) - loop += 1 - if len(selected_ids) != n_points: - print("Could not find the specified number of points...") - - return orig_points[selected_ids, :] - - -def plot_sampled_points(original_points, sampled_points, method_name): - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - ax.scatter(original_points[:, 0], original_points[:, 1], - original_points[:, 2], marker=".", label="Original points") - ax.scatter(sampled_points[:, 0], sampled_points[:, 1], - sampled_points[:, 2], marker="o", - label="Filtered points") - plt.legend() - plt.title(method_name) - plt.axis("equal") - - -def main(): - n_points = 1000 - seed = 1234 - rng = np.random.default_rng(seed) - - x = rng.normal(0.0, 10.0, n_points) - y = rng.normal(0.0, 1.0, n_points) - z = rng.normal(0.0, 10.0, n_points) - original_points = np.vstack((x, y, z)).T - print(f"{original_points.shape=}") - print("Voxel point sampling") - voxel_size = 20.0 - voxel_sampling_points = voxel_point_sampling(original_points, voxel_size) - print(f"{voxel_sampling_points.shape=}") - - print("Farthest point sampling") - n_points = 20 - farthest_sampling_points = farthest_point_sampling(original_points, - n_points, seed) - print(f"{farthest_sampling_points.shape=}") - - print("Poisson disk sampling") - n_points = 20 - min_distance = 10.0 - poisson_disk_points = poisson_disk_sampling(original_points, n_points, - min_distance, seed) - print(f"{poisson_disk_points.shape=}") - - if do_plot: - plot_sampled_points(original_points, voxel_sampling_points, - "Voxel point sampling") - plot_sampled_points(original_points, farthest_sampling_points, - "Farthest point sampling") - plot_sampled_points(original_points, poisson_disk_points, - "poisson disk sampling") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/Mapping/ray_casting_grid_map/ray_casting_grid_map.py b/Mapping/ray_casting_grid_map/ray_casting_grid_map.py deleted file mode 100644 index c7e73f0630..0000000000 --- a/Mapping/ray_casting_grid_map/ray_casting_grid_map.py +++ /dev/null @@ -1,137 +0,0 @@ -""" - -Ray casting 2D grid map example - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import numpy as np -import matplotlib.pyplot as plt - -EXTEND_AREA = 10.0 - -show_animation = True - - -def calc_grid_map_config(ox, oy, xyreso): - minx = round(min(ox) - EXTEND_AREA / 2.0) - miny = round(min(oy) - EXTEND_AREA / 2.0) - maxx = round(max(ox) + EXTEND_AREA / 2.0) - maxy = round(max(oy) + EXTEND_AREA / 2.0) - xw = int(round((maxx - minx) / xyreso)) - yw = int(round((maxy - miny) / xyreso)) - - return minx, miny, maxx, maxy, xw, yw - - -class precastDB: - - def __init__(self): - self.px = 0.0 - self.py = 0.0 - self.d = 0.0 - self.angle = 0.0 - self.ix = 0 - self.iy = 0 - - def __str__(self): - return str(self.px) + "," + str(self.py) + "," + str(self.d) + "," + str(self.angle) - - -def atan_zero_to_twopi(y, x): - angle = math.atan2(y, x) - if angle < 0.0: - angle += math.pi * 2.0 - - return angle - - -def pre_casting(minx, miny, xw, yw, xyreso, yawreso): - - precast = [[] for i in range(int(round((math.pi * 2.0) / yawreso)) + 1)] - - for ix in range(xw): - for iy in range(yw): - px = ix * xyreso + minx - py = iy * xyreso + miny - - d = math.hypot(px, py) - angle = atan_zero_to_twopi(py, px) - angleid = int(math.floor(angle / yawreso)) - - pc = precastDB() - - pc.px = px - pc.py = py - pc.d = d - pc.ix = ix - pc.iy = iy - pc.angle = angle - - precast[angleid].append(pc) - - return precast - - -def generate_ray_casting_grid_map(ox, oy, xyreso, yawreso): - - minx, miny, maxx, maxy, xw, yw = calc_grid_map_config(ox, oy, xyreso) - - pmap = [[0.0 for i in range(yw)] for i in range(xw)] - - precast = pre_casting(minx, miny, xw, yw, xyreso, yawreso) - - for (x, y) in zip(ox, oy): - - d = math.hypot(x, y) - angle = atan_zero_to_twopi(y, x) - angleid = int(math.floor(angle / yawreso)) - - gridlist = precast[angleid] - - ix = int(round((x - minx) / xyreso)) - iy = int(round((y - miny) / xyreso)) - - for grid in gridlist: - if grid.d > d: - pmap[grid.ix][grid.iy] = 0.5 - - pmap[ix][iy] = 1.0 - - return pmap, minx, maxx, miny, maxy, xyreso - - -def draw_heatmap(data, minx, maxx, miny, maxy, xyreso): - x, y = np.mgrid[slice(minx - xyreso / 2.0, maxx + xyreso / 2.0, xyreso), - slice(miny - xyreso / 2.0, maxy + xyreso / 2.0, xyreso)] - plt.pcolor(x, y, data, vmax=1.0, cmap=plt.cm.Blues) - plt.axis("equal") - - -def main(): - print(__file__ + " start!!") - - xyreso = 0.25 # x-y grid resolution [m] - yawreso = np.deg2rad(10.0) # yaw angle resolution [rad] - - for i in range(5): - ox = (np.random.rand(4) - 0.5) * 10.0 - oy = (np.random.rand(4) - 0.5) * 10.0 - pmap, minx, maxx, miny, maxy, xyreso = generate_ray_casting_grid_map( - ox, oy, xyreso, yawreso) - - if show_animation: # pragma: no cover - 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]) - draw_heatmap(pmap, minx, maxx, miny, maxy, xyreso) - plt.plot(ox, oy, "xr") - plt.plot(0.0, 0.0, "ob") - plt.pause(1.0) - - -if __name__ == '__main__': - main() diff --git a/Mapping/rectangle_fitting/__init_.py b/Mapping/rectangle_fitting/__init_.py deleted file mode 100644 index 2194d4c303..0000000000 --- a/Mapping/rectangle_fitting/__init_.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent)) diff --git a/Mapping/rectangle_fitting/rectangle_fitting.py b/Mapping/rectangle_fitting/rectangle_fitting.py deleted file mode 100644 index 7902619666..0000000000 --- a/Mapping/rectangle_fitting/rectangle_fitting.py +++ /dev/null @@ -1,291 +0,0 @@ -""" - -Object shape recognition with L-shape fitting - -author: Atsushi Sakai (@Atsushi_twi) - -Reference: -- Efficient L-Shape Fitting for Vehicle Detection Using Laser Scanners - -The Robotics Institute Carnegie Mellon University -https://www.ri.cmu.edu/publications/ -efficient-l-shape-fitting-for-vehicle-detection-using-laser-scanners/ - -""" - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import matplotlib.pyplot as plt -import numpy as np -import itertools -from enum import Enum - -from utils.angle import rot_mat_2d - -from Mapping.rectangle_fitting.simulator \ - import VehicleSimulator, LidarSimulator - -show_animation = True - - -class LShapeFitting: - """ - LShapeFitting class. You can use this class by initializing the class and - changing the parameters, and then calling the fitting method. - - """ - - class Criteria(Enum): - AREA = 1 - CLOSENESS = 2 - VARIANCE = 3 - - def __init__(self): - """ - Default parameter settings - """ - #: Fitting criteria parameter - self.criteria = self.Criteria.VARIANCE - #: Minimum distance for closeness criteria parameter [m] - self.min_dist_of_closeness_criteria = 0.01 - #: Angle difference parameter [deg] - self.d_theta_deg_for_search = 1.0 - #: Range segmentation parameter [m] - self.R0 = 3.0 - #: Range segmentation parameter [m] - self.Rd = 0.001 - - def fitting(self, ox, oy): - """ - Fitting L-shape model to object points - - Parameters - ---------- - ox : x positions of range points from an object - oy : y positions of range points from an object - - Returns - ------- - rects: Fitting rectangles - id_sets: id sets of each cluster - - """ - # step1: Adaptive Range Segmentation - id_sets = self._adoptive_range_segmentation(ox, oy) - - # step2 Rectangle search - rects = [] - for ids in id_sets: # for each cluster - cx = [ox[i] for i in range(len(ox)) if i in ids] - cy = [oy[i] for i in range(len(oy)) if i in ids] - rects.append(self._rectangle_search(cx, cy)) - - return rects, id_sets - - @staticmethod - def _calc_area_criterion(c1, c2): - c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) - alpha = -(c1_max - c1_min) * (c2_max - c2_min) - return alpha - - def _calc_closeness_criterion(self, c1, c2): - c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) - - # Vectorization - d1 = np.minimum(c1_max - c1, c1 - c1_min) - d2 = np.minimum(c2_max - c2, c2 - c2_min) - d = np.maximum(np.minimum(d1, d2), self.min_dist_of_closeness_criteria) - beta = (1.0 / d).sum() - - return beta - - @staticmethod - def _calc_variance_criterion(c1, c2): - c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) - - # Vectorization - d1 = np.minimum(c1_max - c1, c1 - c1_min) - d2 = np.minimum(c2_max - c2, c2 - c2_min) - e1 = d1[d1 < d2] - e2 = d2[d1 >= d2] - v1 = - np.var(e1) if len(e1) > 0 else 0. - v2 = - np.var(e2) if len(e2) > 0 else 0. - gamma = v1 + v2 - - return gamma - - @staticmethod - def _find_min_max(c1, c2): - c1_max = max(c1) - c2_max = max(c2) - c1_min = min(c1) - c2_min = min(c2) - return c1_max, c1_min, c2_max, c2_min - - def _rectangle_search(self, x, y): - - xy = np.array([x, y]).T - - d_theta = np.deg2rad(self.d_theta_deg_for_search) - min_cost = (-float('inf'), None) - for theta in np.arange(0.0, np.pi / 2.0 - d_theta, d_theta): - - c = xy @ rot_mat_2d(theta) - c1 = c[:, 0] - c2 = c[:, 1] - - # Select criteria - cost = 0.0 - if self.criteria == self.Criteria.AREA: - cost = self._calc_area_criterion(c1, c2) - elif self.criteria == self.Criteria.CLOSENESS: - cost = self._calc_closeness_criterion(c1, c2) - elif self.criteria == self.Criteria.VARIANCE: - cost = self._calc_variance_criterion(c1, c2) - - if min_cost[0] < cost: - min_cost = (cost, theta) - - # calc best rectangle - sin_s = np.sin(min_cost[1]) - cos_s = np.cos(min_cost[1]) - - c1_s = xy @ np.array([cos_s, sin_s]).T - c2_s = xy @ np.array([-sin_s, cos_s]).T - - rect = RectangleData() - rect.a[0] = cos_s - rect.b[0] = sin_s - rect.c[0] = min(c1_s) - rect.a[1] = -sin_s - rect.b[1] = cos_s - rect.c[1] = min(c2_s) - rect.a[2] = cos_s - rect.b[2] = sin_s - rect.c[2] = max(c1_s) - rect.a[3] = -sin_s - rect.b[3] = cos_s - rect.c[3] = max(c2_s) - - return rect - - def _adoptive_range_segmentation(self, ox, oy): - - # Setup initial cluster - segment_list = [] - for i, _ in enumerate(ox): - c = set() - r = self.R0 + self.Rd * np.linalg.norm([ox[i], oy[i]]) - for j, _ in enumerate(ox): - d = np.hypot(ox[i] - ox[j], oy[i] - oy[j]) - if d <= r: - c.add(j) - segment_list.append(c) - - # Merge cluster - while True: - no_change = True - for (c1, c2) in list(itertools.permutations(range(len(segment_list)), 2)): - if segment_list[c1] & segment_list[c2]: - segment_list[c1] = (segment_list[c1] | segment_list.pop(c2)) - no_change = False - break - if no_change: - break - - return segment_list - - -class RectangleData: - - def __init__(self): - self.a = [None] * 4 - self.b = [None] * 4 - self.c = [None] * 4 - - self.rect_c_x = [None] * 5 - self.rect_c_y = [None] * 5 - - def plot(self): - self.calc_rect_contour() - plt.plot(self.rect_c_x, self.rect_c_y, "-r") - - def calc_rect_contour(self): - - self.rect_c_x[0], self.rect_c_y[0] = self.calc_cross_point( - self.a[0:2], self.b[0:2], self.c[0:2]) - self.rect_c_x[1], self.rect_c_y[1] = self.calc_cross_point( - self.a[1:3], self.b[1:3], self.c[1:3]) - self.rect_c_x[2], self.rect_c_y[2] = self.calc_cross_point( - self.a[2:4], self.b[2:4], self.c[2:4]) - self.rect_c_x[3], self.rect_c_y[3] = self.calc_cross_point( - [self.a[3], self.a[0]], [self.b[3], self.b[0]], [self.c[3], self.c[0]]) - self.rect_c_x[4], self.rect_c_y[4] = self.rect_c_x[0], self.rect_c_y[0] - - @staticmethod - def calc_cross_point(a, b, c): - x = (b[0] * -c[1] - b[1] * -c[0]) / (a[0] * b[1] - a[1] * b[0]) - y = (a[1] * -c[0] - a[0] * -c[1]) / (a[0] * b[1] - a[1] * b[0]) - return x, y - - -def main(): - - # simulation parameters - sim_time = 30.0 # simulation time - dt = 0.2 # time tick - - angle_resolution = np.deg2rad(3.0) # sensor angle resolution - - v1 = VehicleSimulator(-10.0, 0.0, np.deg2rad(90.0), - 0.0, 50.0 / 3.6, 3.0, 5.0) - v2 = VehicleSimulator(20.0, 10.0, np.deg2rad(180.0), - 0.0, 50.0 / 3.6, 4.0, 10.0) - - l_shape_fitting = LShapeFitting() - lidar_sim = LidarSimulator() - - time = 0.0 - while time <= sim_time: - time += dt - - v1.update(dt, 0.1, 0.0) - v2.update(dt, 0.1, -0.05) - - ox, oy = lidar_sim.get_observation_points([v1, v2], angle_resolution) - - rects, id_sets = l_shape_fitting.fitting(ox, oy) - - if show_animation: # pragma: no cover - 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]) - plt.axis("equal") - plt.plot(0.0, 0.0, "*r") - v1.plot() - v2.plot() - - # Plot range observation - for ids in id_sets: - x = [ox[i] for i in range(len(ox)) if i in ids] - y = [oy[i] for i in range(len(ox)) if i in ids] - - for (ix, iy) in zip(x, y): - plt.plot([0.0, ix], [0.0, iy], "-og") - - plt.plot([ox[i] for i in range(len(ox)) if i in ids], - [oy[i] for i in range(len(ox)) if i in ids], - "o") - for rect in rects: - rect.plot() - - plt.pause(0.1) - - print("Done") - - -if __name__ == '__main__': - main() diff --git a/Mapping/rectangle_fitting/simulator.py b/Mapping/rectangle_fitting/simulator.py deleted file mode 100644 index aa32ae1b1f..0000000000 --- a/Mapping/rectangle_fitting/simulator.py +++ /dev/null @@ -1,140 +0,0 @@ -""" - -Simulator - -author: Atsushi Sakai - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import numpy as np -import matplotlib.pyplot as plt -import math -import random - -from utils.angle import rot_mat_2d - - -class VehicleSimulator: - - def __init__(self, i_x, i_y, i_yaw, i_v, max_v, w, L): - self.x = i_x - self.y = i_y - self.yaw = i_yaw - self.v = i_v - self.max_v = max_v - self.W = w - self.L = L - self._calc_vehicle_contour() - - def update(self, dt, a, omega): - self.x += self.v * np.cos(self.yaw) * dt - self.y += self.v * np.sin(self.yaw) * dt - self.yaw += omega * dt - self.v += a * dt - if self.v >= self.max_v: - self.v = self.max_v - - def plot(self): - plt.plot(self.x, self.y, ".b") - - # convert global coordinate - gx, gy = self.calc_global_contour() - plt.plot(gx, gy, "--b") - - def calc_global_contour(self): - gxy = np.stack([self.vc_x, self.vc_y]).T @ rot_mat_2d(self.yaw) - gx = gxy[:, 0] + self.x - gy = gxy[:, 1] + self.y - - return gx, gy - - def _calc_vehicle_contour(self): - - self.vc_x = [] - self.vc_y = [] - - self.vc_x.append(self.L / 2.0) - self.vc_y.append(self.W / 2.0) - - self.vc_x.append(self.L / 2.0) - self.vc_y.append(-self.W / 2.0) - - self.vc_x.append(-self.L / 2.0) - self.vc_y.append(-self.W / 2.0) - - self.vc_x.append(-self.L / 2.0) - self.vc_y.append(self.W / 2.0) - - self.vc_x.append(self.L / 2.0) - self.vc_y.append(self.W / 2.0) - - self.vc_x, self.vc_y = self._interpolate(self.vc_x, self.vc_y) - - @staticmethod - def _interpolate(x, y): - rx, ry = [], [] - d_theta = 0.05 - for i in range(len(x) - 1): - rx.extend([(1.0 - theta) * x[i] + theta * x[i + 1] - for theta in np.arange(0.0, 1.0, d_theta)]) - ry.extend([(1.0 - theta) * y[i] + theta * y[i + 1] - for theta in np.arange(0.0, 1.0, d_theta)]) - - rx.extend([(1.0 - theta) * x[len(x) - 1] + theta * x[1] - for theta in np.arange(0.0, 1.0, d_theta)]) - ry.extend([(1.0 - theta) * y[len(y) - 1] + theta * y[1] - for theta in np.arange(0.0, 1.0, d_theta)]) - - return rx, ry - - -class LidarSimulator: - - def __init__(self): - self.range_noise = 0.01 - - def get_observation_points(self, v_list, angle_resolution): - x, y, angle, r = [], [], [], [] - - # store all points - for v in v_list: - - gx, gy = v.calc_global_contour() - - for vx, vy in zip(gx, gy): - v_angle = math.atan2(vy, vx) - vr = np.hypot(vx, vy) * random.uniform(1.0 - self.range_noise, - 1.0 + self.range_noise) - - x.append(vx) - y.append(vy) - angle.append(v_angle) - r.append(vr) - - # ray casting filter - rx, ry = self.ray_casting_filter(angle, r, angle_resolution) - - return rx, ry - - @staticmethod - def ray_casting_filter(theta_l, range_l, angle_resolution): - rx, ry = [], [] - range_db = [float("inf") for _ in range( - int(np.floor((np.pi * 2.0) / angle_resolution)) + 1)] - - for i in range(len(theta_l)): - angle_id = int(round(theta_l[i] / angle_resolution)) - - if range_db[angle_id] > range_l[i]: - range_db[angle_id] = range_l[i] - - for i in range(len(range_db)): - t = i * angle_resolution - if range_db[i] != float("inf"): - rx.append(range_db[i] * np.cos(t)) - ry.append(range_db[i] * np.sin(t)) - - return rx, ry diff --git a/MissionPlanning/BehaviorTree/behavior_tree.py b/MissionPlanning/BehaviorTree/behavior_tree.py deleted file mode 100644 index 9ad886aafb..0000000000 --- a/MissionPlanning/BehaviorTree/behavior_tree.py +++ /dev/null @@ -1,690 +0,0 @@ -""" -Behavior Tree - -author: Wang Zheng (@Aglargil) - -Reference: - -- [Behavior Tree](https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)) -""" - -import time -import xml.etree.ElementTree as ET -from enum import Enum - - -class Status(Enum): - SUCCESS = "success" - FAILURE = "failure" - RUNNING = "running" - - -class NodeType(Enum): - CONTROL_NODE = "ControlNode" - ACTION_NODE = "ActionNode" - DECORATOR_NODE = "DecoratorNode" - - -class Node: - """ - Base class for all nodes in a behavior tree. - """ - - def __init__(self, name): - self.name = name - self.status = None - - def tick(self) -> Status: - """ - Tick the node. - - Returns: - Status: The status of the node. - """ - raise ValueError("Node is not implemented") - - def tick_and_set_status(self) -> Status: - """ - Tick the node and set the status. - - Returns: - Status: The status of the node. - """ - self.status = self.tick() - return self.status - - def reset(self): - """ - Reset the node. - """ - self.status = None - - def reset_children(self): - """ - Reset the children of the node. - """ - pass - - -class ControlNode(Node): - """ - Base class for all control nodes in a behavior tree. - - Control nodes manage the execution flow of their child nodes according to specific rules. - They typically have multiple children and determine which children to execute and in what order. - """ - - def __init__(self, name): - super().__init__(name) - self.children = [] - self.type = NodeType.CONTROL_NODE - - def not_set_children_raise_error(self): - if len(self.children) == 0: - raise ValueError("Children are not set") - - def reset_children(self): - for child in self.children: - child.reset() - - -class SequenceNode(ControlNode): - """ - Executes child nodes in sequence until one fails or all succeed. - - Returns: - - Returns FAILURE if any child returns FAILURE - - Returns SUCCESS when all children have succeeded - - Returns RUNNING when a child is still running or when moving to the next child - - Example: - .. code-block:: xml - - - - - - """ - - def __init__(self, name): - super().__init__(name) - self.current_child_index = 0 - - def tick(self) -> Status: - self.not_set_children_raise_error() - - if self.current_child_index >= len(self.children): - self.reset_children() - return Status.SUCCESS - status = self.children[self.current_child_index].tick_and_set_status() - if status == Status.FAILURE: - self.reset_children() - return Status.FAILURE - elif status == Status.SUCCESS: - self.current_child_index += 1 - return Status.RUNNING - elif status == Status.RUNNING: - return Status.RUNNING - else: - raise ValueError("Unknown status") - - -class SelectorNode(ControlNode): - """ - Executes child nodes in sequence until one succeeds or all fail. - - Returns: - - Returns SUCCESS if any child returns SUCCESS - - Returns FAILURE when all children have failed - - Returns RUNNING when a child is still running or when moving to the next child - - Examples: - .. code-block:: xml - - - - - - """ - - def __init__(self, name): - super().__init__(name) - self.current_child_index = 0 - - def tick(self) -> Status: - self.not_set_children_raise_error() - - if self.current_child_index >= len(self.children): - self.reset_children() - return Status.FAILURE - status = self.children[self.current_child_index].tick_and_set_status() - if status == Status.SUCCESS: - self.reset_children() - return Status.SUCCESS - elif status == Status.FAILURE: - self.current_child_index += 1 - return Status.RUNNING - elif status == Status.RUNNING: - return Status.RUNNING - else: - raise ValueError("Unknown status") - - -class WhileDoElseNode(ControlNode): - """ - Conditional execution node with three parts: condition, do, and optional else. - - Returns: - First executes the condition node (child[0]) - If condition succeeds, executes do node (child[1]) and returns RUNNING - If condition fails, executes else node (child[2]) if present and returns result of else node - If condition fails and there is no else node, returns SUCCESS - - Example: - .. code-block:: xml - - - - - - - """ - - def __init__(self, name): - super().__init__(name) - - def tick(self) -> Status: - if len(self.children) != 3 and len(self.children) != 2: - raise ValueError("WhileDoElseNode must have exactly 3 or 2 children") - - condition_node = self.children[0] - do_node = self.children[1] - else_node = self.children[2] if len(self.children) == 3 else None - - condition_status = condition_node.tick_and_set_status() - if condition_status == Status.SUCCESS: - do_node.tick_and_set_status() - return Status.RUNNING - elif condition_status == Status.FAILURE: - if else_node is not None: - else_status = else_node.tick_and_set_status() - if else_status == Status.SUCCESS: - self.reset_children() - return Status.SUCCESS - elif else_status == Status.FAILURE: - self.reset_children() - return Status.FAILURE - elif else_status == Status.RUNNING: - return Status.RUNNING - else: - raise ValueError("Unknown status") - else: - self.reset_children() - return Status.SUCCESS - else: - raise ValueError("Unknown status") - - -class ActionNode(Node): - """ - Base class for all action nodes in a behavior tree. - - Action nodes are responsible for performing specific tasks or actions. - They do not have children and are typically used to execute logic or operations. - """ - - def __init__(self, name): - super().__init__(name) - self.type = NodeType.ACTION_NODE - - -class SleepNode(ActionNode): - """ - Sleep node that sleeps for a specified duration. - - Returns: - Returns SUCCESS after the specified duration has passed - Returns RUNNING if the duration has not yet passed - - Example: - .. code-block:: xml - - - """ - - def __init__(self, name, duration): - super().__init__(name) - self.duration = duration - self.start_time = None - - def tick(self) -> Status: - if self.start_time is None: - self.start_time = time.time() - if time.time() - self.start_time > self.duration: - return Status.SUCCESS - return Status.RUNNING - - -class EchoNode(ActionNode): - """ - Echo node that prints a message to the console. - - Returns: - Returns SUCCESS after the message has been printed - - Example: - .. code-block:: xml - - - """ - - def __init__(self, name, message): - super().__init__(name) - self.message = message - - def tick(self) -> Status: - print(self.name, self.message) - return Status.SUCCESS - - -class DecoratorNode(Node): - """ - Base class for all decorator nodes in a behavior tree. - - Decorator nodes modify the behavior of their child node. - They must have a single child and can alter the status of the child node. - """ - - def __init__(self, name): - super().__init__(name) - self.type = NodeType.DECORATOR_NODE - self.child = None - - def not_set_child_raise_error(self): - if self.child is None: - raise ValueError("Child is not set") - - def reset_children(self): - self.child.reset() - - -class InverterNode(DecoratorNode): - """ - Inverter node that inverts the status of its child node. - - Returns: - - Returns SUCCESS if the child returns FAILURE - - Returns FAILURE if the child returns SUCCESS - - Returns RUNNING if the child returns RUNNING - - Examples: - .. code-block:: xml - - - - - """ - - def __init__(self, name): - super().__init__(name) - - def tick(self) -> Status: - self.not_set_child_raise_error() - status = self.child.tick_and_set_status() - return Status.SUCCESS if status == Status.FAILURE else Status.FAILURE - - -class TimeoutNode(DecoratorNode): - """ - Timeout node that fails if the child node takes too long to execute - - Returns: - - FAILURE: If the timeout duration has been exceeded - - Child's status: Otherwise, passes through the status of the child node - - Example: - .. code-block:: xml - - - - - """ - - def __init__(self, name, timeout): - super().__init__(name) - self.timeout = timeout - self.start_time = None - - def tick(self) -> Status: - self.not_set_child_raise_error() - if self.start_time is None: - self.start_time = time.time() - if time.time() - self.start_time > self.timeout: - return Status.FAILURE - print(f"{self.name} is running") - return self.child.tick_and_set_status() - - -class DelayNode(DecoratorNode): - """ - Delay node that delays the execution of its child node for a specified duration. - - Returns: - - Returns RUNNING if the duration has not yet passed - - Returns child's status after the duration has passed - - Example: - .. code-block:: xml - - - - - """ - - def __init__(self, name, delay): - super().__init__(name) - self.delay = delay - self.start_time = None - - def tick(self) -> Status: - self.not_set_child_raise_error() - if self.start_time is None: - self.start_time = time.time() - if time.time() - self.start_time > self.delay: - return self.child.tick_and_set_status() - return Status.RUNNING - - -class ForceSuccessNode(DecoratorNode): - """ - ForceSuccess node that always returns SUCCESS. - - Returns: - - Returns RUNNING if the child returns RUNNING - - Returns SUCCESS if the child returns SUCCESS or FAILURE - """ - - def __init__(self, name): - super().__init__(name) - - def tick(self) -> Status: - self.not_set_child_raise_error() - status = self.child.tick_and_set_status() - if status == Status.FAILURE: - return Status.SUCCESS - return status - - -class ForceFailureNode(DecoratorNode): - """ - ForceFailure node that always returns FAILURE. - - Returns: - - Returns RUNNING if the child returns RUNNING - - Returns FAILURE if the child returns SUCCESS or FAILURE - """ - - def __init__(self, name): - super().__init__(name) - - def tick(self) -> Status: - self.not_set_child_raise_error() - status = self.child.tick_and_set_status() - if status == Status.SUCCESS: - return Status.FAILURE - return status - - -class BehaviorTree: - """ - Behavior tree class that manages the execution of a behavior tree. - """ - - def __init__(self, root): - self.root = root - - def tick(self): - """ - Tick once on the behavior tree. - """ - self.root.tick_and_set_status() - - def reset(self): - """ - Reset the behavior tree. - """ - self.root.reset() - - def tick_while_running(self, interval=None, enable_print=True): - """ - Tick the behavior tree while it is running. - - Args: - interval (float, optional): The interval between ticks. Defaults to None. - enable_print (bool, optional): Whether to print the behavior tree. Defaults to True. - """ - while self.root.tick_and_set_status() == Status.RUNNING: - if enable_print: - self.print_tree() - if interval is not None: - time.sleep(interval) - if enable_print: - self.print_tree() - - def to_text(self, root, indent=0): - """ - Recursively convert the behavior tree to a text representation. - """ - current_text = "" - if root.status == Status.RUNNING: - # yellow - current_text = "\033[93m" + root.name + "\033[0m" - elif root.status == Status.SUCCESS: - # green - current_text = "\033[92m" + root.name + "\033[0m" - elif root.status == Status.FAILURE: - # red - current_text = "\033[91m" + root.name + "\033[0m" - else: - current_text = root.name - if root.type == NodeType.CONTROL_NODE: - current_text = " " * indent + "[" + current_text + "]\n" - for child in root.children: - current_text += self.to_text(child, indent + 2) - elif root.type == NodeType.DECORATOR_NODE: - current_text = " " * indent + "(" + current_text + ")\n" - current_text += self.to_text(root.child, indent + 2) - elif root.type == NodeType.ACTION_NODE: - current_text = " " * indent + "<" + current_text + ">\n" - return current_text - - def print_tree(self): - """ - Print the behavior tree. - - Node print format: - Action: - Decorator: (Decorator) - Control: [Control] - - Node status colors: - Yellow: RUNNING - Green: SUCCESS - Red: FAILURE - """ - text = self.to_text(self.root) - text = text.strip() - print("\033[94m" + "Behavior Tree" + "\033[0m") - print(text) - print("\033[94m" + "Behavior Tree" + "\033[0m") - - -class BehaviorTreeFactory: - """ - Factory class for creating behavior trees from XML strings. - """ - - def __init__(self): - self.node_builders = {} - # Control nodes - self.register_node_builder( - "Sequence", - lambda node: SequenceNode(node.attrib.get("name", SequenceNode.__name__)), - ) - self.register_node_builder( - "Selector", - lambda node: SelectorNode(node.attrib.get("name", SelectorNode.__name__)), - ) - self.register_node_builder( - "WhileDoElse", - lambda node: WhileDoElseNode( - node.attrib.get("name", WhileDoElseNode.__name__) - ), - ) - # Decorator nodes - self.register_node_builder( - "Inverter", - lambda node: InverterNode(node.attrib.get("name", InverterNode.__name__)), - ) - self.register_node_builder( - "Timeout", - lambda node: TimeoutNode( - node.attrib.get("name", SelectorNode.__name__), - float(node.attrib["sec"]), - ), - ) - self.register_node_builder( - "Delay", - lambda node: DelayNode( - node.attrib.get("name", DelayNode.__name__), - float(node.attrib["sec"]), - ), - ) - self.register_node_builder( - "ForceSuccess", - lambda node: ForceSuccessNode( - node.attrib.get("name", ForceSuccessNode.__name__) - ), - ) - self.register_node_builder( - "ForceFailure", - lambda node: ForceFailureNode( - node.attrib.get("name", ForceFailureNode.__name__) - ), - ) - # Action nodes - self.register_node_builder( - "Sleep", - lambda node: SleepNode( - node.attrib.get("name", SleepNode.__name__), - float(node.attrib["sec"]), - ), - ) - self.register_node_builder( - "Echo", - lambda node: EchoNode( - node.attrib.get("name", EchoNode.__name__), - node.attrib["message"], - ), - ) - - def register_node_builder(self, node_name, builder): - """ - Register a builder for a node - - Args: - node_name (str): The name of the node. - builder (function): The builder function. - - Example: - .. code-block:: python - - factory = BehaviorTreeFactory() - factory.register_node_builder( - "MyNode", - lambda node: MyNode( - node.attrib.get("name", MyNode.__name__), - node.attrib["my_param"], - ), - ) - """ - self.node_builders[node_name] = builder - - def build_node(self, node): - """ - Build a node from an XML element. - - Args: - node (Element): The XML element to build the node from. - - Returns: - BehaviorTree Node: the built node - """ - if node.tag in self.node_builders: - root = self.node_builders[node.tag](node) - if root.type == NodeType.CONTROL_NODE: - if len(node) <= 0: - raise ValueError(f"{root.name} Control node must have children") - for child in node: - root.children.append(self.build_node(child)) - elif root.type == NodeType.DECORATOR_NODE: - if len(node) != 1: - raise ValueError( - f"{root.name} Decorator node must have exactly one child" - ) - root.child = self.build_node(node[0]) - elif root.type == NodeType.ACTION_NODE: - if len(node) != 0: - raise ValueError(f"{root.name} Action node must have no children") - return root - else: - raise ValueError(f"Unknown node type: {node.tag}") - - def build_tree(self, xml_string): - """ - Build a behavior tree from an XML string. - - Args: - xml_string (str): The XML string containing the behavior tree. - - Returns: - BehaviorTree: The behavior tree. - """ - xml_tree = ET.fromstring(xml_string) - root = self.build_node(xml_tree) - return BehaviorTree(root) - - def build_tree_from_file(self, file_path): - """ - Build a behavior tree from a file. - - Args: - file_path (str): The path to the file containing the behavior tree. - - Returns: - BehaviorTree: The behavior tree. - """ - with open(file_path) as file: - xml_string = file.read() - return self.build_tree(xml_string) - - -xml_string = """ - - - - - - - - """ - - -def main(): - factory = BehaviorTreeFactory() - tree = factory.build_tree(xml_string) - tree.tick_while_running() - - -if __name__ == "__main__": - main() diff --git a/MissionPlanning/BehaviorTree/robot_behavior_case.py b/MissionPlanning/BehaviorTree/robot_behavior_case.py deleted file mode 100644 index 6c39aa76b2..0000000000 --- a/MissionPlanning/BehaviorTree/robot_behavior_case.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Robot Behavior Tree Case - -This file demonstrates how to use a behavior tree to control robot behavior. -""" - -from behavior_tree import ( - BehaviorTreeFactory, - Status, - ActionNode, -) -import time -import random -import os - - -class CheckBatteryNode(ActionNode): - """ - Node to check robot battery level - - If battery level is below threshold, returns FAILURE, otherwise returns SUCCESS - """ - - def __init__(self, name, threshold=20): - super().__init__(name) - self.threshold = threshold - self.battery_level = 100 # Initial battery level is 100% - - def tick(self): - # Simulate battery level decreasing - self.battery_level -= random.randint(1, 5) - print(f"Current battery level: {self.battery_level}%") - - if self.battery_level <= self.threshold: - return Status.FAILURE - return Status.SUCCESS - - -class ChargeBatteryNode(ActionNode): - """ - Node to charge the robot's battery - """ - - def __init__(self, name, charge_rate=10): - super().__init__(name) - self.charge_rate = charge_rate - self.charging_time = 0 - - def tick(self): - # Simulate charging process - if self.charging_time == 0: - print("Starting to charge...") - - self.charging_time += 1 - charge_amount = self.charge_rate * self.charging_time - - if charge_amount >= 100: - print("Charging complete! Battery level: 100%") - self.charging_time = 0 - return Status.SUCCESS - else: - print(f"Charging in progress... Battery level: {min(charge_amount, 100)}%") - return Status.RUNNING - - -class MoveToPositionNode(ActionNode): - """ - Node to move to a specified position - """ - - def __init__(self, name, position, move_duration=2): - super().__init__(name) - self.position = position - self.move_duration = move_duration - self.start_time = None - - def tick(self): - if self.start_time is None: - self.start_time = time.time() - print(f"Starting movement to position {self.position}") - - elapsed_time = time.time() - self.start_time - - if elapsed_time >= self.move_duration: - print(f"Arrived at position {self.position}") - self.start_time = None - return Status.SUCCESS - else: - print( - f"Moving to position {self.position}... {int(elapsed_time / self.move_duration * 100)}% complete" - ) - return Status.RUNNING - - -class DetectObstacleNode(ActionNode): - """ - Node to detect obstacles - """ - - def __init__(self, name, obstacle_probability=0.3): - super().__init__(name) - self.obstacle_probability = obstacle_probability - - def tick(self): - # Use random probability to simulate obstacle detection - if random.random() < self.obstacle_probability: - print("Obstacle detected!") - return Status.SUCCESS - else: - print("No obstacle detected") - return Status.FAILURE - - -class AvoidObstacleNode(ActionNode): - """ - Node to avoid obstacles - """ - - def __init__(self, name, avoid_duration=1.5): - super().__init__(name) - self.avoid_duration = avoid_duration - self.start_time = None - - def tick(self): - if self.start_time is None: - self.start_time = time.time() - print("Starting obstacle avoidance...") - - elapsed_time = time.time() - self.start_time - - if elapsed_time >= self.avoid_duration: - print("Obstacle avoidance complete") - self.start_time = None - return Status.SUCCESS - else: - print("Avoiding obstacle...") - return Status.RUNNING - - -class PerformTaskNode(ActionNode): - """ - Node to perform a specific task - """ - - def __init__(self, name, task_name, task_duration=3): - super().__init__(name) - self.task_name = task_name - self.task_duration = task_duration - self.start_time = None - - def tick(self): - if self.start_time is None: - self.start_time = time.time() - print(f"Starting task: {self.task_name}") - - elapsed_time = time.time() - self.start_time - - if elapsed_time >= self.task_duration: - print(f"Task complete: {self.task_name}") - self.start_time = None - return Status.SUCCESS - else: - print( - f"Performing task: {self.task_name}... {int(elapsed_time / self.task_duration * 100)}% complete" - ) - return Status.RUNNING - - -def create_robot_behavior_tree(): - """ - Create robot behavior tree - """ - - factory = BehaviorTreeFactory() - - # Register custom nodes - factory.register_node_builder( - "CheckBattery", - lambda node: CheckBatteryNode( - node.attrib.get("name", "CheckBattery"), - int(node.attrib.get("threshold", "20")), - ), - ) - - factory.register_node_builder( - "ChargeBattery", - lambda node: ChargeBatteryNode( - node.attrib.get("name", "ChargeBattery"), - int(node.attrib.get("charge_rate", "10")), - ), - ) - - factory.register_node_builder( - "MoveToPosition", - lambda node: MoveToPositionNode( - node.attrib.get("name", "MoveToPosition"), - node.attrib.get("position", "Unknown Position"), - float(node.attrib.get("move_duration", "2")), - ), - ) - - factory.register_node_builder( - "DetectObstacle", - lambda node: DetectObstacleNode( - node.attrib.get("name", "DetectObstacle"), - float(node.attrib.get("obstacle_probability", "0.3")), - ), - ) - - factory.register_node_builder( - "AvoidObstacle", - lambda node: AvoidObstacleNode( - node.attrib.get("name", "AvoidObstacle"), - float(node.attrib.get("avoid_duration", "1.5")), - ), - ) - - factory.register_node_builder( - "PerformTask", - lambda node: PerformTaskNode( - node.attrib.get("name", "PerformTask"), - node.attrib.get("task_name", "Unknown Task"), - float(node.attrib.get("task_duration", "3")), - ), - ) - # Read XML from file - xml_path = os.path.join(os.path.dirname(__file__), "robot_behavior_tree.xml") - return factory.build_tree_from_file(xml_path) - - -def main(): - """ - Main function: Create and run the robot behavior tree - """ - print("Creating robot behavior tree...") - tree = create_robot_behavior_tree() - - print("\nStarting robot behavior tree execution...\n") - # Run for a period of time or until completion - - tree.tick_while_running(interval=0.01) - - print("\nBehavior tree execution complete!") - - -if __name__ == "__main__": - main() diff --git a/MissionPlanning/BehaviorTree/robot_behavior_tree.xml b/MissionPlanning/BehaviorTree/robot_behavior_tree.xml deleted file mode 100644 index 0bca76a3ff..0000000000 --- a/MissionPlanning/BehaviorTree/robot_behavior_tree.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MissionPlanning/StateMachine/robot_behavior_case.py b/MissionPlanning/StateMachine/robot_behavior_case.py deleted file mode 100644 index 03ee60ae9f..0000000000 --- a/MissionPlanning/StateMachine/robot_behavior_case.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -A case study of robot behavior using state machine - -author: Wang Zheng (@Aglargil) -""" - -from state_machine import StateMachine - - -class Robot: - def __init__(self): - self.battery = 100 - self.task_progress = 0 - - # Initialize state machine - self.machine = StateMachine("robot_sm", self) - - # Add state transition rules - self.machine.add_transition( - src_state="patrolling", - event="detect_task", - dst_state="executing_task", - guard=None, - action=None, - ) - - self.machine.add_transition( - src_state="executing_task", - event="task_complete", - dst_state="patrolling", - guard=None, - action="reset_task", - ) - - self.machine.add_transition( - src_state="executing_task", - event="low_battery", - dst_state="returning_to_base", - guard="is_battery_low", - ) - - self.machine.add_transition( - src_state="returning_to_base", - event="reach_base", - dst_state="charging", - guard=None, - action=None, - ) - - self.machine.add_transition( - src_state="charging", - event="charge_complete", - dst_state="patrolling", - guard=None, - action="battery_full", - ) - - # Set initial state - self.machine.set_current_state("patrolling") - - def is_battery_low(self): - """Battery level check condition""" - return self.battery < 30 - - def reset_task(self): - """Reset task progress""" - self.task_progress = 0 - print("[Action] Task progress has been reset") - - # Modify state entry callback naming convention (add state_ prefix) - def on_enter_executing_task(self): - print("\n------ Start Executing Task ------") - print(f"Current battery: {self.battery}%") - while self.machine.get_current_state().name == "executing_task": - self.task_progress += 10 - self.battery -= 25 - print( - f"Task progress: {self.task_progress}%, Remaining battery: {self.battery}%" - ) - - if self.task_progress >= 100: - self.machine.process("task_complete") - break - elif self.is_battery_low(): - self.machine.process("low_battery") - break - - def on_enter_returning_to_base(self): - print("\nLow battery, returning to charging station...") - self.machine.process("reach_base") - - def on_enter_charging(self): - print("\n------ Charging ------") - self.battery = 100 - print("Charging complete!") - self.machine.process("charge_complete") - - -# Keep the test section structure the same, only modify the trigger method -if __name__ == "__main__": - robot = Robot() - print(robot.machine.generate_plantuml()) - - print(f"Initial state: {robot.machine.get_current_state().name}") - print("------------") - - # Trigger task detection event - robot.machine.process("detect_task") - - print("\n------------") - print(f"Final state: {robot.machine.get_current_state().name}") diff --git a/MissionPlanning/StateMachine/state_machine.py b/MissionPlanning/StateMachine/state_machine.py deleted file mode 100644 index 454759236e..0000000000 --- a/MissionPlanning/StateMachine/state_machine.py +++ /dev/null @@ -1,294 +0,0 @@ -""" -State Machine - -author: Wang Zheng (@Aglargil) - -Reference: - -- [State Machine] -(https://en.wikipedia.org/wiki/Finite-state_machine) -""" - -import string -from urllib.request import urlopen, Request -from base64 import b64encode -from zlib import compress -from io import BytesIO -from collections.abc import Callable -from matplotlib.image import imread -from matplotlib import pyplot as plt - - -def deflate_and_encode(plantuml_text): - """ - zlib compress the plantuml text and encode it for the plantuml server. - - Reference https://plantuml.com/en/text-encoding - """ - plantuml_alphabet = ( - string.digits + string.ascii_uppercase + string.ascii_lowercase + "-_" - ) - base64_alphabet = ( - string.ascii_uppercase + string.ascii_lowercase + string.digits + "+/" - ) - b64_to_plantuml = bytes.maketrans( - base64_alphabet.encode("utf-8"), plantuml_alphabet.encode("utf-8") - ) - zlibbed_str = compress(plantuml_text.encode("utf-8")) - compressed_string = zlibbed_str[2:-4] - return b64encode(compressed_string).translate(b64_to_plantuml).decode("utf-8") - - -class State: - def __init__(self, name, on_enter=None, on_exit=None): - self.name = name - self.on_enter = on_enter - self.on_exit = on_exit - - def enter(self): - print(f"entering <{self.name}>") - if self.on_enter: - self.on_enter() - - def exit(self): - print(f"exiting <{self.name}>") - if self.on_exit: - self.on_exit() - - -class StateMachine: - def __init__(self, name: str, model=object): - """Initialize the state machine. - - Args: - name (str): Name of the state machine. - model (object, optional): Model object used to automatically look up callback functions - for states and transitions: - State callbacks: Automatically searches for 'on_enter_' and 'on_exit_' methods. - Transition callbacks: When action or guard parameters are strings, looks up corresponding methods in the model. - - Example: - >>> class MyModel: - ... def on_enter_idle(self): - ... print("Entering idle state") - ... def on_exit_idle(self): - ... print("Exiting idle state") - ... def can_start(self): - ... return True - ... def on_start(self): - ... print("Starting operation") - >>> model = MyModel() - >>> machine = StateMachine("my_machine", model) - """ - self._name = name - self._states = {} - self._events = {} - self._transition_table = {} - self._model = model - self._state: State = None - - def _register_event(self, event: str): - self._events[event] = event - - def _get_state(self, name): - return self._states[name] - - def _get_event(self, name): - return self._events[name] - - def _has_event(self, event: str): - return event in self._events - - def add_transition( - self, - src_state: str | State, - event: str, - dst_state: str | State, - guard: str | Callable = None, - action: str | Callable = None, - ) -> None: - """Add a transition to the state machine. - - Args: - src_state (str | State): The source state where the transition begins. - Can be either a state name or a State object. - event (str): The event that triggers this transition. - dst_state (str | State): The destination state where the transition ends. - Can be either a state name or a State object. - guard (str | Callable, optional): Guard condition for the transition. - If callable: Function that returns bool. - If str: Name of a method in the model class. - If returns True: Transition proceeds. - If returns False: Transition is skipped. - action (str | Callable, optional): Action to execute during transition. - If callable: Function to execute. - If str: Name of a method in the model class. - Executed after guard passes and before entering new state. - - Example: - >>> machine.add_transition( - ... src_state="idle", - ... event="start", - ... dst_state="running", - ... guard="can_start", - ... action="on_start" - ... ) - """ - # Convert string parameters to objects if necessary - self.register_state(src_state) - self._register_event(event) - self.register_state(dst_state) - - def get_state_obj(state): - return state if isinstance(state, State) else self._get_state(state) - - def get_callable(func): - return func if callable(func) else getattr(self._model, func, None) - - src_state_obj = get_state_obj(src_state) - dst_state_obj = get_state_obj(dst_state) - - guard_func = get_callable(guard) if guard else None - action_func = get_callable(action) if action else None - self._transition_table[(src_state_obj.name, event)] = ( - dst_state_obj, - guard_func, - action_func, - ) - - def state_transition(self, src_state: State, event: str): - if (src_state.name, event) not in self._transition_table: - raise ValueError( - f"|{self._name}| invalid transition: <{src_state.name}> : [{event}]" - ) - - dst_state, guard, action = self._transition_table[(src_state.name, event)] - - def call_guard(guard): - if callable(guard): - return guard() - else: - return True - - def call_action(action): - if callable(action): - action() - - if call_guard(guard): - call_action(action) - if src_state.name != dst_state.name: - print( - f"|{self._name}| transitioning from <{src_state.name}> to <{dst_state.name}>" - ) - src_state.exit() - self._state = dst_state - dst_state.enter() - else: - print( - f"|{self._name}| skipping transition from <{src_state.name}> to <{dst_state.name}> because guard failed" - ) - - def register_state(self, state: str | State, on_enter=None, on_exit=None): - """Register a state in the state machine. - - Args: - state (str | State): The state to register. Can be either a string (state name) - or a State object. - on_enter (Callable, optional): Callback function to be executed when entering the state. - If state is a string and on_enter is None, it will look for - a method named 'on_enter_' in the model. - on_exit (Callable, optional): Callback function to be executed when exiting the state. - If state is a string and on_exit is None, it will look for - a method named 'on_exit_' in the model. - Example: - >>> machine.register_state("idle", on_enter=on_enter_idle, on_exit=on_exit_idle) - >>> machine.register_state(State("running", on_enter=on_enter_running, on_exit=on_exit_running)) - """ - if isinstance(state, str): - if on_enter is None: - on_enter = getattr(self._model, "on_enter_" + state, None) - if on_exit is None: - on_exit = getattr(self._model, "on_exit_" + state, None) - self._states[state] = State(state, on_enter, on_exit) - return - - self._states[state.name] = state - - def set_current_state(self, state: State | str): - if isinstance(state, str): - self._state = self._get_state(state) - else: - self._state = state - - def get_current_state(self): - return self._state - - def process(self, event: str) -> None: - """Process an event in the state machine. - - Args: - event: Event name. - - Example: - >>> machine.process("start") - """ - if self._state is None: - raise ValueError("State machine is not initialized") - - if self._has_event(event): - self.state_transition(self._state, event) - else: - raise ValueError(f"Invalid event: {event}") - - def generate_plantuml(self) -> str: - """Generate PlantUML state diagram representation of the state machine. - - Returns: - str: PlantUML state diagram code. - """ - if self._state is None: - raise ValueError("State machine is not initialized") - - plant_uml = ["@startuml"] - plant_uml.append("[*] --> " + self._state.name) - - # Generate transitions - for (src_state, event), ( - dst_state, - guard, - action, - ) in self._transition_table.items(): - transition = f"{src_state} --> {dst_state.name} : {event}" - - # Add guard and action if present - conditions = [] - if guard: - guard_name = guard.__name__ if callable(guard) else guard - conditions.append(f"[{guard_name}]") - if action: - action_name = action.__name__ if callable(action) else action - conditions.append(f"/ {action_name}") - - if conditions: - transition += "\\n" + " ".join(conditions) - - plant_uml.append(transition) - - plant_uml.append("@enduml") - plant_uml_text = "\n".join(plant_uml) - - try: - url = f"http://www.plantuml.com/plantuml/img/{deflate_and_encode(plant_uml_text)}" - headers = {"User-Agent": "Mozilla/5.0"} - request = Request(url, headers=headers) - - with urlopen(request) as response: - content = response.read() - - plt.imshow(imread(BytesIO(content), format="png")) - plt.axis("off") - plt.show() - except Exception as e: - print(f"Error showing PlantUML: {e}") - - return plant_uml_text diff --git a/PathPlanning/AStar/a_star.py b/PathPlanning/AStar/a_star.py deleted file mode 100644 index 6d20350432..0000000000 --- a/PathPlanning/AStar/a_star.py +++ /dev/null @@ -1,282 +0,0 @@ -""" - -A* grid planning - -author: Atsushi Sakai(@Atsushi_twi) - Nikos Kanargias (nkana@tee.gr) - -See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class AStarPlanner: - - def __init__(self, ox, oy, resolution, rr): - """ - Initialize grid map for a star planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.resolution = resolution - self.rr = rr - self.min_x, self.min_y = 0, 0 - self.max_x, self.max_y = 0, 0 - self.obstacle_map = None - self.x_width, self.y_width = 0, 0 - self.motion = self.get_motion_model() - self.calc_obstacle_map(ox, oy) - - class Node: - def __init__(self, x, y, cost, parent_index): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - A star path search - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - start_node = self.Node(self.calc_xy_index(sx, self.min_x), - self.calc_xy_index(sy, self.min_y), 0.0, -1) - goal_node = self.Node(self.calc_xy_index(gx, self.min_x), - self.calc_xy_index(gy, self.min_y), 0.0, -1) - - open_set, closed_set = dict(), dict() - open_set[self.calc_grid_index(start_node)] = start_node - - while True: - if len(open_set) == 0: - print("Open set is empty..") - break - - c_id = min( - open_set, - key=lambda o: open_set[o].cost + self.calc_heuristic(goal_node, - open_set[ - o])) - current = open_set[c_id] - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current.x, self.min_x), - self.calc_grid_position(current.y, self.min_y), "xc") - # 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]) - if len(closed_set.keys()) % 10 == 0: - plt.pause(0.001) - - if current.x == goal_node.x and current.y == goal_node.y: - print("Find goal") - goal_node.parent_index = current.parent_index - goal_node.cost = current.cost - break - - # Remove the item from the open set - del open_set[c_id] - - # Add it to the closed set - closed_set[c_id] = current - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - node = self.Node(current.x + self.motion[i][0], - current.y + self.motion[i][1], - current.cost + self.motion[i][2], c_id) - n_id = self.calc_grid_index(node) - - # If the node is not safe, do nothing - if not self.verify_node(node): - continue - - if n_id in closed_set: - continue - - if n_id not in open_set: - open_set[n_id] = node # discovered a new node - else: - if open_set[n_id].cost > node.cost: - # This path is the best until now. record it - open_set[n_id] = node - - rx, ry = self.calc_final_path(goal_node, closed_set) - - return rx, ry - - def calc_final_path(self, goal_node, closed_set): - # generate final course - rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [ - self.calc_grid_position(goal_node.y, self.min_y)] - parent_index = goal_node.parent_index - while parent_index != -1: - n = closed_set[parent_index] - rx.append(self.calc_grid_position(n.x, self.min_x)) - ry.append(self.calc_grid_position(n.y, self.min_y)) - parent_index = n.parent_index - - return rx, ry - - @staticmethod - def calc_heuristic(n1, n2): - w = 1.0 # weight of heuristic - d = w * math.hypot(n1.x - n2.x, n1.y - n2.y) - return d - - def calc_grid_position(self, index, min_position): - """ - calc grid position - - :param index: - :param min_position: - :return: - """ - pos = index * self.resolution + min_position - return pos - - def calc_xy_index(self, position, min_pos): - return round((position - min_pos) / self.resolution) - - def calc_grid_index(self, node): - return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.min_x) - py = self.calc_grid_position(node.y, self.min_y) - - if px < self.min_x: - return False - elif py < self.min_y: - return False - elif px >= self.max_x: - return False - elif py >= self.max_y: - return False - - # collision check - if self.obstacle_map[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.min_x = round(min(ox)) - self.min_y = round(min(oy)) - self.max_x = round(max(ox)) - self.max_y = round(max(oy)) - print("min_x:", self.min_x) - print("min_y:", self.min_y) - print("max_x:", self.max_x) - print("max_y:", self.max_y) - - self.x_width = round((self.max_x - self.min_x) / self.resolution) - self.y_width = round((self.max_y - self.min_y) / self.resolution) - print("x_width:", self.x_width) - print("y_width:", self.y_width) - - # obstacle map generation - self.obstacle_map = [[False for _ in range(self.y_width)] - for _ in range(self.x_width)] - for ix in range(self.x_width): - x = self.calc_grid_position(ix, self.min_x) - for iy in range(self.y_width): - y = self.calc_grid_position(iy, self.min_y) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obstacle_map[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - - a_star = AStarPlanner(ox, oy, grid_size, robot_radius) - rx, ry = a_star.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.001) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/AStar/a_star_searching_from_two_side.py b/PathPlanning/AStar/a_star_searching_from_two_side.py deleted file mode 100644 index f43cea71b4..0000000000 --- a/PathPlanning/AStar/a_star_searching_from_two_side.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -A* algorithm -Author: Weicent -randomly generate obstacles, start and goal point -searching path from start and end simultaneously -""" - -import numpy as np -import matplotlib.pyplot as plt -import math - -show_animation = True - - -class Node: - """node with properties of g, h, coordinate and parent node""" - - def __init__(self, G=0, H=0, coordinate=None, parent=None): - self.G = G - self.H = H - self.F = G + H - self.parent = parent - self.coordinate = coordinate - - def reset_f(self): - self.F = self.G + self.H - - -def hcost(node_coordinate, goal): - dx = abs(node_coordinate[0] - goal[0]) - dy = abs(node_coordinate[1] - goal[1]) - hcost = dx + dy - return hcost - - -def gcost(fixed_node, update_node_coordinate): - dx = abs(fixed_node.coordinate[0] - update_node_coordinate[0]) - dy = abs(fixed_node.coordinate[1] - update_node_coordinate[1]) - gc = math.hypot(dx, dy) # gc = move from fixed_node to update_node - gcost = fixed_node.G + gc # gcost = move from start point to update_node - return gcost - - -def boundary_and_obstacles(start, goal, top_vertex, bottom_vertex, obs_number): - """ - :param start: start coordinate - :param goal: goal coordinate - :param top_vertex: top right vertex coordinate of boundary - :param bottom_vertex: bottom left vertex coordinate of boundary - :param obs_number: number of obstacles generated in the map - :return: boundary_obstacle array, obstacle list - """ - # below can be merged into a rectangle boundary - ay = list(range(bottom_vertex[1], top_vertex[1])) - ax = [bottom_vertex[0]] * len(ay) - cy = ay - cx = [top_vertex[0]] * len(cy) - bx = list(range(bottom_vertex[0] + 1, top_vertex[0])) - by = [bottom_vertex[1]] * len(bx) - dx = [bottom_vertex[0]] + bx + [top_vertex[0]] - dy = [top_vertex[1]] * len(dx) - - # generate random obstacles - ob_x = np.random.randint(bottom_vertex[0] + 1, - top_vertex[0], obs_number).tolist() - ob_y = np.random.randint(bottom_vertex[1] + 1, - top_vertex[1], obs_number).tolist() - # x y coordinate in certain order for boundary - x = ax + bx + cx + dx - y = ay + by + cy + dy - obstacle = np.vstack((ob_x, ob_y)).T.tolist() - # remove start and goal coordinate in obstacle list - obstacle = [coor for coor in obstacle if coor != start and coor != goal] - obs_array = np.array(obstacle) - bound = np.vstack((x, y)).T - bound_obs = np.vstack((bound, obs_array)) - return bound_obs, obstacle - - -def find_neighbor(node, ob, closed): - # Convert obstacles to a set for faster lookup - ob_set = set(map(tuple, ob.tolist())) - neighbor_set = set() - - # Generate neighbors within the 3x3 grid around the node - for x in range(node.coordinate[0] - 1, node.coordinate[0] + 2): - for y in range(node.coordinate[1] - 1, node.coordinate[1] + 2): - coord = (x, y) - if coord not in ob_set and coord != tuple(node.coordinate): - neighbor_set.add(coord) - - # Define neighbors in cardinal and diagonal directions - top_nei = (node.coordinate[0], node.coordinate[1] + 1) - bottom_nei = (node.coordinate[0], node.coordinate[1] - 1) - left_nei = (node.coordinate[0] - 1, node.coordinate[1]) - right_nei = (node.coordinate[0] + 1, node.coordinate[1]) - lt_nei = (node.coordinate[0] - 1, node.coordinate[1] + 1) - rt_nei = (node.coordinate[0] + 1, node.coordinate[1] + 1) - lb_nei = (node.coordinate[0] - 1, node.coordinate[1] - 1) - rb_nei = (node.coordinate[0] + 1, node.coordinate[1] - 1) - - # Remove neighbors that violate diagonal motion rules - if top_nei in ob_set and left_nei in ob_set: - neighbor_set.discard(lt_nei) - if top_nei in ob_set and right_nei in ob_set: - neighbor_set.discard(rt_nei) - if bottom_nei in ob_set and left_nei in ob_set: - neighbor_set.discard(lb_nei) - if bottom_nei in ob_set and right_nei in ob_set: - neighbor_set.discard(rb_nei) - - # Filter out neighbors that are in the closed set - closed_set = set(map(tuple, closed)) - neighbor_set -= closed_set - - return list(neighbor_set) - - - -def find_node_index(coordinate, node_list): - # find node index in the node list via its coordinate - ind = 0 - for node in node_list: - if node.coordinate == coordinate: - target_node = node - ind = node_list.index(target_node) - break - return ind - - -def find_path(open_list, closed_list, goal, obstacle): - # searching for the path, update open and closed list - # obstacle = obstacle and boundary - flag = len(open_list) - for i in range(flag): - node = open_list[0] - open_coordinate_list = [node.coordinate for node in open_list] - closed_coordinate_list = [node.coordinate for node in closed_list] - temp = find_neighbor(node, obstacle, closed_coordinate_list) - for element in temp: - if element in closed_list: - continue - elif element in open_coordinate_list: - # if node in open list, update g value - ind = open_coordinate_list.index(element) - new_g = gcost(node, element) - if new_g <= open_list[ind].G: - open_list[ind].G = new_g - open_list[ind].reset_f() - open_list[ind].parent = node - else: # new coordinate, create corresponding node - ele_node = Node(coordinate=element, parent=node, - G=gcost(node, element), H=hcost(element, goal)) - open_list.append(ele_node) - open_list.remove(node) - closed_list.append(node) - open_list.sort(key=lambda x: x.F) - return open_list, closed_list - - -def node_to_coordinate(node_list): - # convert node list into coordinate list and array - coordinate_list = [node.coordinate for node in node_list] - return coordinate_list - - -def check_node_coincide(close_ls1, closed_ls2): - """ - :param close_ls1: node closed list for searching from start - :param closed_ls2: node closed list for searching from end - :return: intersect node list for above two - """ - # check if node in close_ls1 intersect with node in closed_ls2 - cl1 = node_to_coordinate(close_ls1) - cl2 = node_to_coordinate(closed_ls2) - intersect_ls = [node for node in cl1 if node in cl2] - return intersect_ls - - -def find_surrounding(coordinate, obstacle): - # find obstacles around node, help to draw the borderline - boundary: list = [] - for x in range(coordinate[0] - 1, coordinate[0] + 2): - for y in range(coordinate[1] - 1, coordinate[1] + 2): - if [x, y] in obstacle: - boundary.append([x, y]) - return boundary - - -def get_border_line(node_closed_ls, obstacle): - # if no path, find border line which confine goal or robot - border: list = [] - coordinate_closed_ls = node_to_coordinate(node_closed_ls) - for coordinate in coordinate_closed_ls: - temp = find_surrounding(coordinate, obstacle) - border = border + temp - border_ary = np.array(border) - return border_ary - - -def get_path(org_list, goal_list, coordinate): - # get path from start to end - path_org: list = [] - path_goal: list = [] - ind = find_node_index(coordinate, org_list) - node = org_list[ind] - while node != org_list[0]: - path_org.append(node.coordinate) - node = node.parent - path_org.append(org_list[0].coordinate) - ind = find_node_index(coordinate, goal_list) - node = goal_list[ind] - while node != goal_list[0]: - path_goal.append(node.coordinate) - node = node.parent - path_goal.append(goal_list[0].coordinate) - path_org.reverse() - path = path_org + path_goal - path = np.array(path) - return path - - -def random_coordinate(bottom_vertex, top_vertex): - # generate random coordinates inside maze - coordinate = [np.random.randint(bottom_vertex[0] + 1, top_vertex[0]), - np.random.randint(bottom_vertex[1] + 1, top_vertex[1])] - return coordinate - - -def draw(close_origin, close_goal, start, end, bound): - # plot the map - if not close_goal.tolist(): # ensure the close_goal not empty - # in case of the obstacle number is really large (>4500), the - # origin is very likely blocked at the first search, and then - # the program is over and the searching from goal to origin - # will not start, which remain the closed_list for goal == [] - # in order to plot the map, add the end coordinate to array - close_goal = np.array([end]) - plt.cla() - plt.gcf().set_size_inches(11, 9, forward=True) - plt.axis('equal') - plt.plot(close_origin[:, 0], close_origin[:, 1], 'oy') - plt.plot(close_goal[:, 0], close_goal[:, 1], 'og') - plt.plot(bound[:, 0], bound[:, 1], 'sk') - plt.plot(end[0], end[1], '*b', label='Goal') - plt.plot(start[0], start[1], '^b', label='Origin') - plt.legend() - plt.pause(0.0001) - - -def draw_control(org_closed, goal_closed, flag, start, end, bound, obstacle): - """ - control the plot process, evaluate if the searching finished - flag == 0 : draw the searching process and plot path - flag == 1 or 2 : start or end is blocked, draw the border line - """ - stop_loop = 0 # stop sign for the searching - org_closed_ls = node_to_coordinate(org_closed) - org_array = np.array(org_closed_ls) - goal_closed_ls = node_to_coordinate(goal_closed) - goal_array = np.array(goal_closed_ls) - path = None - if show_animation: # draw the searching process - draw(org_array, goal_array, start, end, bound) - if flag == 0: - node_intersect = check_node_coincide(org_closed, goal_closed) - if node_intersect: # a path is find - path = get_path(org_closed, goal_closed, node_intersect[0]) - stop_loop = 1 - print('Path found!') - if show_animation: # draw the path - plt.plot(path[:, 0], path[:, 1], '-r') - plt.title('Robot Arrived', size=20, loc='center') - plt.pause(0.01) - plt.show() - elif flag == 1: # start point blocked first - stop_loop = 1 - print('There is no path to the goal! Start point is blocked!') - elif flag == 2: # end point blocked first - stop_loop = 1 - print('There is no path to the goal! End point is blocked!') - if show_animation: # blocked case, draw the border line - info = 'There is no path to the goal!' \ - ' Robot&Goal are split by border' \ - ' shown in red \'x\'!' - if flag == 1: - border = get_border_line(org_closed, obstacle) - plt.plot(border[:, 0], border[:, 1], 'xr') - plt.title(info, size=14, loc='center') - plt.pause(0.01) - plt.show() - elif flag == 2: - border = get_border_line(goal_closed, obstacle) - plt.plot(border[:, 0], border[:, 1], 'xr') - plt.title(info, size=14, loc='center') - plt.pause(0.01) - plt.show() - return stop_loop, path - - -def searching_control(start, end, bound, obstacle): - """manage the searching process, start searching from two side""" - # initial origin node and end node - origin = Node(coordinate=start, H=hcost(start, end)) - goal = Node(coordinate=end, H=hcost(end, start)) - # list for searching from origin to goal - origin_open: list = [origin] - origin_close: list = [] - # list for searching from goal to origin - goal_open = [goal] - goal_close: list = [] - # initial target - target_goal = end - # flag = 0 (not blocked) 1 (start point blocked) 2 (end point blocked) - flag = 0 # init flag - path = None - while True: - # searching from start to end - origin_open, origin_close = \ - find_path(origin_open, origin_close, target_goal, bound) - if not origin_open: # no path condition - flag = 1 # origin node is blocked - draw_control(origin_close, goal_close, flag, start, end, bound, - obstacle) - break - # update target for searching from end to start - target_origin = min(origin_open, key=lambda x: x.F).coordinate - - # searching from end to start - goal_open, goal_close = \ - find_path(goal_open, goal_close, target_origin, bound) - if not goal_open: # no path condition - flag = 2 # goal is blocked - draw_control(origin_close, goal_close, flag, start, end, bound, - obstacle) - break - # update target for searching from start to end - target_goal = min(goal_open, key=lambda x: x.F).coordinate - - # continue searching, draw the process - stop_sign, path = draw_control(origin_close, goal_close, flag, start, - end, bound, obstacle) - if stop_sign: - break - return path - - -def main(obstacle_number=1500): - print(__file__ + ' start!') - - top_vertex = [60, 60] # top right vertex of boundary - bottom_vertex = [0, 0] # bottom left vertex of boundary - - # generate start and goal point randomly - start = random_coordinate(bottom_vertex, top_vertex) - end = random_coordinate(bottom_vertex, top_vertex) - - # generate boundary and obstacles - bound, obstacle = boundary_and_obstacles(start, end, top_vertex, - bottom_vertex, - obstacle_number) - - path = searching_control(start, end, bound, obstacle) - if not show_animation: - print(path) - - -if __name__ == '__main__': - main(obstacle_number=1500) diff --git a/PathPlanning/AStar/a_star_variants.py b/PathPlanning/AStar/a_star_variants.py deleted file mode 100644 index e0053ee224..0000000000 --- a/PathPlanning/AStar/a_star_variants.py +++ /dev/null @@ -1,483 +0,0 @@ -""" -a star variants -author: Sarim Mehdi(muhammadsarim.mehdi@studio.unibo.it) -Source: http://theory.stanford.edu/~amitp/GameProgramming/Variations.html -""" - -import numpy as np -import matplotlib.pyplot as plt - -show_animation = True -use_beam_search = False -use_iterative_deepening = False -use_dynamic_weighting = False -use_theta_star = False -use_jump_point = False - -beam_capacity = 30 -max_theta = 5 -only_corners = False -max_corner = 5 -w, epsilon, upper_bound_depth = 1, 4, 500 - - -def draw_horizontal_line(start_x, start_y, length, o_x, o_y, o_dict): - for i in range(start_x, start_x + length): - for j in range(start_y, start_y + 2): - o_x.append(i) - o_y.append(j) - o_dict[(i, j)] = True - - -def draw_vertical_line(start_x, start_y, length, o_x, o_y, o_dict): - for i in range(start_x, start_x + 2): - for j in range(start_y, start_y + length): - o_x.append(i) - o_y.append(j) - o_dict[(i, j)] = True - - -def in_line_of_sight(obs_grid, x1, y1, x2, y2): - t = 0 - while t <= 0.5: - xt = (1 - t) * x1 + t * x2 - yt = (1 - t) * y1 + t * y2 - if obs_grid[(int(xt), int(yt))]: - return False, None - xt = (1 - t) * x2 + t * x1 - yt = (1 - t) * y2 + t * y1 - if obs_grid[(int(xt), int(yt))]: - return False, None - t += 0.001 - dist = np.linalg.norm(np.array([x1, y1] - np.array([x2, y2]))) - return True, dist - - -def key_points(o_dict): - offsets1 = [(1, 0), (0, 1), (-1, 0), (1, 0)] - offsets2 = [(1, 1), (-1, 1), (-1, -1), (1, -1)] - offsets3 = [(0, 1), (-1, 0), (0, -1), (0, -1)] - c_list = [] - for grid_point, obs_status in o_dict.items(): - if obs_status: - continue - empty_space = True - x, y = grid_point - for i in [-1, 0, 1]: - for j in [-1, 0, 1]: - if (x + i, y + j) not in o_dict.keys(): - continue - if o_dict[(x + i, y + j)]: - empty_space = False - break - if empty_space: - continue - for offset1, offset2, offset3 in zip(offsets1, offsets2, offsets3): - i1, j1 = offset1 - i2, j2 = offset2 - i3, j3 = offset3 - if ((x + i1, y + j1) not in o_dict.keys()) or \ - ((x + i2, y + j2) not in o_dict.keys()) or \ - ((x + i3, y + j3) not in o_dict.keys()): - continue - obs_count = 0 - if o_dict[(x + i1, y + j1)]: - obs_count += 1 - if o_dict[(x + i2, y + j2)]: - obs_count += 1 - if o_dict[(x + i3, y + j3)]: - obs_count += 1 - if obs_count == 3 or obs_count == 1: - c_list.append((x, y)) - if show_animation: - plt.plot(x, y, ".y") - break - if only_corners: - return c_list - - e_list = [] - for corner in c_list: - x1, y1 = corner - for other_corner in c_list: - x2, y2 = other_corner - if x1 == x2 and y1 == y2: - continue - reachable, _ = in_line_of_sight(o_dict, x1, y1, x2, y2) - if not reachable: - continue - x_m, y_m = int((x1 + x2) / 2), int((y1 + y2) / 2) - e_list.append((x_m, y_m)) - if show_animation: - plt.plot(x_m, y_m, ".y") - return c_list + e_list - - -class SearchAlgo: - def __init__(self, obs_grid, goal_x, goal_y, start_x, start_y, - limit_x, limit_y, corner_list=None): - self.start_pt = [start_x, start_y] - self.goal_pt = [goal_x, goal_y] - self.obs_grid = obs_grid - g_cost, h_cost = 0, self.get_hval(start_x, start_y, goal_x, goal_y) - f_cost = g_cost + h_cost - self.all_nodes, self.open_set = {}, [] - - if use_jump_point: - for corner in corner_list: - i, j = corner - h_c = self.get_hval(i, j, goal_x, goal_y) - self.all_nodes[(i, j)] = {'pos': [i, j], 'pred': None, - 'gcost': np.inf, 'hcost': h_c, - 'fcost': np.inf, - 'open': True, 'in_open_list': False} - self.all_nodes[tuple(self.goal_pt)] = \ - {'pos': self.goal_pt, 'pred': None, - 'gcost': np.inf, 'hcost': 0, 'fcost': np.inf, - 'open': True, 'in_open_list': True} - else: - for i in range(limit_x): - for j in range(limit_y): - h_c = self.get_hval(i, j, goal_x, goal_y) - self.all_nodes[(i, j)] = {'pos': [i, j], 'pred': None, - 'gcost': np.inf, 'hcost': h_c, - 'fcost': np.inf, - 'open': True, - 'in_open_list': False} - self.all_nodes[tuple(self.start_pt)] = \ - {'pos': self.start_pt, 'pred': None, - 'gcost': g_cost, 'hcost': h_cost, 'fcost': f_cost, - 'open': True, 'in_open_list': True} - self.open_set.append(self.all_nodes[tuple(self.start_pt)]) - - @staticmethod - def get_hval(x1, y1, x2, y2): - x, y = x1, y1 - val = 0 - while x != x2 or y != y2: - if x != x2 and y != y2: - val += 14 - else: - val += 10 - x, y = x + np.sign(x2 - x), y + np.sign(y2 - y) - return val - - def get_farthest_point(self, x, y, i, j): - i_temp, j_temp = i, j - counter = 1 - got_goal = False - while not self.obs_grid[(x + i_temp, y + j_temp)] and \ - counter < max_theta: - i_temp += i - j_temp += j - counter += 1 - if [x + i_temp, y + j_temp] == self.goal_pt: - got_goal = True - break - if (x + i_temp, y + j_temp) not in self.obs_grid.keys(): - break - return i_temp - 2*i, j_temp - 2*j, counter, got_goal - - def jump_point(self): - """Jump point: Instead of exploring all empty spaces of the - map, just explore the corners.""" - - goal_found = False - while len(self.open_set) > 0: - self.open_set = sorted(self.open_set, key=lambda x: x['fcost']) - lowest_f = self.open_set[0]['fcost'] - lowest_h = self.open_set[0]['hcost'] - lowest_g = self.open_set[0]['gcost'] - p = 0 - for p_n in self.open_set[1:]: - if p_n['fcost'] == lowest_f and \ - p_n['gcost'] < lowest_g: - lowest_g = p_n['gcost'] - p += 1 - elif p_n['fcost'] == lowest_f and \ - p_n['gcost'] == lowest_g and \ - p_n['hcost'] < lowest_h: - lowest_h = p_n['hcost'] - p += 1 - else: - break - current_node = self.all_nodes[tuple(self.open_set[p]['pos'])] - x1, y1 = current_node['pos'] - - for cand_pt, cand_node in self.all_nodes.items(): - x2, y2 = cand_pt - if x1 == x2 and y1 == y2: - continue - if np.linalg.norm(np.array([x1, y1] - - np.array([x2, y2]))) > max_corner: - continue - reachable, offset = in_line_of_sight(self.obs_grid, x1, - y1, x2, y2) - if not reachable: - continue - - if list(cand_pt) == self.goal_pt: - current_node['open'] = False - self.all_nodes[tuple(cand_pt)]['pred'] = \ - current_node['pos'] - goal_found = True - break - - g_cost = offset + current_node['gcost'] - h_cost = self.all_nodes[cand_pt]['hcost'] - f_cost = g_cost + h_cost - cand_pt = tuple(cand_pt) - if f_cost < self.all_nodes[cand_pt]['fcost']: - self.all_nodes[cand_pt]['pred'] = current_node['pos'] - self.all_nodes[cand_pt]['gcost'] = g_cost - self.all_nodes[cand_pt]['fcost'] = f_cost - if not self.all_nodes[cand_pt]['in_open_list']: - self.open_set.append(self.all_nodes[cand_pt]) - self.all_nodes[cand_pt]['in_open_list'] = True - if show_animation: - plt.plot(cand_pt[0], cand_pt[1], "r*") - - if goal_found: - break - if show_animation: - plt.pause(0.001) - if goal_found: - current_node = self.all_nodes[tuple(self.goal_pt)] - while goal_found: - if current_node['pred'] is None: - break - x = [current_node['pos'][0], current_node['pred'][0]] - y = [current_node['pos'][1], current_node['pred'][1]] - current_node = self.all_nodes[tuple(current_node['pred'])] - if show_animation: - plt.plot(x, y, "b") - plt.pause(0.001) - if goal_found: - break - - current_node['open'] = False - current_node['in_open_list'] = False - if show_animation: - plt.plot(current_node['pos'][0], current_node['pos'][1], "g*") - del self.open_set[p] - current_node['fcost'], current_node['hcost'] = np.inf, np.inf - if show_animation: - plt.title('Jump Point') - plt.show() - - def a_star(self): - """Beam search: Maintain an open list of just 30 nodes. - If more than 30 nodes, then get rid of nodes with high - f values. - Iterative deepening: At every iteration, get a cut-off - value for the f cost. This cut-off is minimum of the f - value of all nodes whose f value is higher than the - current cut-off value. Nodes with f value higher than - the current cut off value are not put in the open set. - Dynamic weighting: Multiply heuristic with the following: - (1 + epsilon - (epsilon*d)/N) where d is the current - iteration of loop and N is upper bound on number of - iterations. - Theta star: Same as A star but you don't need to move - one neighbor at a time. In fact, you can look for the - next node as far out as you can as long as there is a - clear line of sight from your current node to that node.""" - if show_animation: - if use_beam_search: - plt.title('A* with beam search') - elif use_iterative_deepening: - plt.title('A* with iterative deepening') - elif use_dynamic_weighting: - plt.title('A* with dynamic weighting') - elif use_theta_star: - plt.title('Theta*') - else: - plt.title('A*') - - goal_found = False - curr_f_thresh = np.inf - depth = 0 - no_valid_f = False - w = None - while len(self.open_set) > 0: - self.open_set = sorted(self.open_set, key=lambda x: x['fcost']) - lowest_f = self.open_set[0]['fcost'] - lowest_h = self.open_set[0]['hcost'] - lowest_g = self.open_set[0]['gcost'] - p = 0 - for p_n in self.open_set[1:]: - if p_n['fcost'] == lowest_f and \ - p_n['gcost'] < lowest_g: - lowest_g = p_n['gcost'] - p += 1 - elif p_n['fcost'] == lowest_f and \ - p_n['gcost'] == lowest_g and \ - p_n['hcost'] < lowest_h: - lowest_h = p_n['hcost'] - p += 1 - else: - break - current_node = self.all_nodes[tuple(self.open_set[p]['pos'])] - - while len(self.open_set) > beam_capacity and use_beam_search: - del self.open_set[-1] - - f_cost_list = [] - if use_dynamic_weighting: - w = (1 + epsilon - epsilon*depth/upper_bound_depth) - for i in range(-1, 2): - for j in range(-1, 2): - x, y = current_node['pos'] - if (i == 0 and j == 0) or \ - ((x + i, y + j) not in self.obs_grid.keys()): - continue - if (i, j) in [(1, 0), (0, 1), (-1, 0), (0, -1)]: - offset = 10 - else: - offset = 14 - if use_theta_star: - new_i, new_j, counter, goal_found = \ - self.get_farthest_point(x, y, i, j) - offset = offset * counter - cand_pt = [current_node['pos'][0] + new_i, - current_node['pos'][1] + new_j] - else: - cand_pt = [current_node['pos'][0] + i, - current_node['pos'][1] + j] - - if use_theta_star and goal_found: - current_node['open'] = False - cand_pt = self.goal_pt - self.all_nodes[tuple(cand_pt)]['pred'] = \ - current_node['pos'] - break - - if cand_pt == self.goal_pt: - current_node['open'] = False - self.all_nodes[tuple(cand_pt)]['pred'] = \ - current_node['pos'] - goal_found = True - break - - cand_pt = tuple(cand_pt) - no_valid_f = self.update_node_cost(cand_pt, curr_f_thresh, - current_node, - f_cost_list, no_valid_f, - offset, w) - if goal_found: - break - if show_animation: - plt.pause(0.001) - if goal_found: - current_node = self.all_nodes[tuple(self.goal_pt)] - while goal_found: - if current_node['pred'] is None: - break - if use_theta_star or use_jump_point: - x, y = [current_node['pos'][0], current_node['pred'][0]], \ - [current_node['pos'][1], current_node['pred'][1]] - if show_animation: - plt.plot(x, y, "b") - else: - if show_animation: - plt.plot(current_node['pred'][0], - current_node['pred'][1], "b*") - current_node = self.all_nodes[tuple(current_node['pred'])] - if goal_found: - break - - if use_iterative_deepening and f_cost_list: - curr_f_thresh = min(f_cost_list) - if use_iterative_deepening and not f_cost_list: - curr_f_thresh = np.inf - if use_iterative_deepening and not f_cost_list and no_valid_f: - current_node['fcost'], current_node['hcost'] = np.inf, np.inf - continue - - current_node['open'] = False - current_node['in_open_list'] = False - if show_animation: - plt.plot(current_node['pos'][0], current_node['pos'][1], "g*") - del self.open_set[p] - current_node['fcost'], current_node['hcost'] = np.inf, np.inf - depth += 1 - if show_animation: - plt.show() - - def update_node_cost(self, cand_pt, curr_f_thresh, current_node, - f_cost_list, no_valid_f, offset, w): - if not self.obs_grid[tuple(cand_pt)] and \ - self.all_nodes[cand_pt]['open']: - g_cost = offset + current_node['gcost'] - h_cost = self.all_nodes[cand_pt]['hcost'] - if use_dynamic_weighting: - h_cost = h_cost * w - f_cost = g_cost + h_cost - if f_cost < self.all_nodes[cand_pt]['fcost'] and \ - f_cost <= curr_f_thresh: - f_cost_list.append(f_cost) - self.all_nodes[cand_pt]['pred'] = \ - current_node['pos'] - self.all_nodes[cand_pt]['gcost'] = g_cost - self.all_nodes[cand_pt]['fcost'] = f_cost - if not self.all_nodes[cand_pt]['in_open_list']: - self.open_set.append(self.all_nodes[cand_pt]) - self.all_nodes[cand_pt]['in_open_list'] = True - if show_animation: - plt.plot(cand_pt[0], cand_pt[1], "r*") - if curr_f_thresh < f_cost < \ - self.all_nodes[cand_pt]['fcost']: - no_valid_f = True - return no_valid_f - - -def main(): - # set obstacle positions - obs_dict = {} - for i in range(51): - for j in range(51): - obs_dict[(i, j)] = False - o_x, o_y = [], [] - - s_x = 5.0 - s_y = 5.0 - g_x = 35.0 - g_y = 45.0 - - # draw outer border of maze - draw_vertical_line(0, 0, 50, o_x, o_y, obs_dict) - draw_vertical_line(48, 0, 50, o_x, o_y, obs_dict) - draw_horizontal_line(0, 0, 50, o_x, o_y, obs_dict) - draw_horizontal_line(0, 48, 50, o_x, o_y, obs_dict) - - # draw inner walls - all_x = [10, 10, 10, 15, 20, 20, 30, 30, 35, 30, 40, 45] - all_y = [10, 30, 45, 20, 5, 40, 10, 40, 5, 40, 10, 25] - all_len = [10, 10, 5, 10, 10, 5, 20, 10, 25, 10, 35, 15] - for x, y, l in zip(all_x, all_y, all_len): - draw_vertical_line(x, y, l, o_x, o_y, obs_dict) - - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [35, 40, 15, 10, 45, 20, 10, 15, 25, 45, 10, 30, 10, 40] - all_y = [5, 10, 15, 20, 20, 25, 30, 35, 35, 35, 40, 40, 45, 45] - all_len = [10, 5, 10, 10, 5, 5, 10, 5, 10, 5, 10, 5, 5, 5] - for x, y, l in zip(all_x, all_y, all_len): - draw_horizontal_line(x, y, l, o_x, o_y, obs_dict) - - if show_animation: - plt.plot(o_x, o_y, ".k") - plt.plot(s_x, s_y, "og") - plt.plot(g_x, g_y, "xb") - plt.grid(True) - - if use_jump_point: - keypoint_list = key_points(obs_dict) - search_obj = SearchAlgo(obs_dict, g_x, g_y, s_x, s_y, 101, 101, - keypoint_list) - search_obj.jump_point() - else: - search_obj = SearchAlgo(obs_dict, g_x, g_y, s_x, s_y, 101, 101) - search_obj.a_star() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BSplinePath/bspline_path.py b/PathPlanning/BSplinePath/bspline_path.py deleted file mode 100644 index a2a396efaa..0000000000 --- a/PathPlanning/BSplinePath/bspline_path.py +++ /dev/null @@ -1,152 +0,0 @@ -""" - -Path Planner with B-Spline - -author: Atsushi Sakai (@Atsushi_twi) - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import numpy as np -import matplotlib.pyplot as plt -import scipy.interpolate as interpolate - -from utils.plot import plot_curvature - - -def approximate_b_spline_path(x: list, - y: list, - n_path_points: int, - degree: int = 3, - s=None, - ) -> tuple: - """ - Approximate points with a B-Spline path - - Parameters - ---------- - x : array_like - x position list of approximated points - y : array_like - y position list of approximated points - n_path_points : int - number of path points - degree : int, optional - B Spline curve degree. Must be 2<= k <= 5. Default: 3. - s : int, optional - smoothing parameter. If this value is bigger, the path will be - smoother, but it will be less accurate. If this value is smaller, - the path will be more accurate, but it will be less smooth. - When `s` is 0, it is equivalent to the interpolation. Default is None, - in this case `s` will be `len(x)`. - - Returns - ------- - x : array - x positions of the result path - y : array - y positions of the result path - heading : array - heading of the result path - curvature : array - curvature of the result path - - """ - distances = _calc_distance_vector(x, y) - - spl_i_x = interpolate.UnivariateSpline(distances, x, k=degree, s=s) - spl_i_y = interpolate.UnivariateSpline(distances, y, k=degree, s=s) - - sampled = np.linspace(0.0, distances[-1], n_path_points) - return _evaluate_spline(sampled, spl_i_x, spl_i_y) - - -def interpolate_b_spline_path(x, y, - n_path_points: int, - degree: int = 3) -> tuple: - """ - Interpolate x-y points with a B-Spline path - - Parameters - ---------- - x : array_like - x positions of interpolated points - y : array_like - y positions of interpolated points - n_path_points : int - number of path points - degree : int, optional - B-Spline degree. Must be 2<= k <= 5. Default: 3 - - Returns - ------- - x : array - x positions of the result path - y : array - y positions of the result path - heading : array - heading of the result path - curvature : array - curvature of the result path - - """ - return approximate_b_spline_path(x, y, n_path_points, degree, s=0.0) - - -def _calc_distance_vector(x, y): - dx, dy = np.diff(x), np.diff(y) - distances = np.cumsum([np.hypot(idx, idy) for idx, idy in zip(dx, dy)]) - distances = np.concatenate(([0.0], distances)) - distances /= distances[-1] - return distances - - -def _evaluate_spline(sampled, spl_i_x, spl_i_y): - x = spl_i_x(sampled) - y = spl_i_y(sampled) - dx = spl_i_x.derivative(1)(sampled) - dy = spl_i_y.derivative(1)(sampled) - heading = np.arctan2(dy, dx) - ddx = spl_i_x.derivative(2)(sampled) - ddy = spl_i_y.derivative(2)(sampled) - curvature = (ddy * dx - ddx * dy) / np.power(dx * dx + dy * dy, 2.0 / 3.0) - return np.array(x), y, heading, curvature, - - -def main(): - print(__file__ + " start!!") - # way points - way_point_x = [-1.0, 3.0, 4.0, 2.0, 1.0] - way_point_y = [0.0, -3.0, 1.0, 1.0, 3.0] - n_course_point = 50 # sampling number - - plt.subplots() - rax, ray, heading, curvature = approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5) - plt.plot(rax, ray, '-r', label="Approximated B-Spline path") - plot_curvature(rax, ray, heading, curvature) - - plt.title("B-Spline approximation") - plt.plot(way_point_x, way_point_y, '-og', label="way points") - plt.grid(True) - plt.legend() - plt.axis("equal") - - plt.subplots() - rix, riy, heading, curvature = interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point) - plt.plot(rix, riy, '-b', label="Interpolated B-Spline path") - plot_curvature(rix, riy, heading, curvature) - - plt.title("B-Spline interpolation") - plt.plot(way_point_x, way_point_y, '-og', label="way points") - plt.grid(True) - plt.legend() - plt.axis("equal") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BatchInformedRRTStar/batch_informed_rrt_star.py b/PathPlanning/BatchInformedRRTStar/batch_informed_rrt_star.py deleted file mode 100644 index 92c3a58761..0000000000 --- a/PathPlanning/BatchInformedRRTStar/batch_informed_rrt_star.py +++ /dev/null @@ -1,635 +0,0 @@ -""" -Batch Informed Trees based path planning: -Uses a heuristic to efficiently search increasingly dense -RGGs while reusing previous information. Provides faster -convergence that RRT*, Informed RRT* and other sampling based -methods. - -Uses lazy connecting by combining sampling based methods and A* -like incremental graph search algorithms. - -author: Karan Chawla(@karanchawla) - Atsushi Sakai(@Atsushi_twi) - -Reference: https://arxiv.org/abs/1405.5848 -""" - -import math -import random - -import matplotlib.pyplot as plt -import numpy as np - -show_animation = True - - -class RTree: - # Class to represent the explicit tree created - # while sampling through the state space - - def __init__(self, start=None, lowerLimit=None, upperLimit=None, - resolution=1.0): - - if upperLimit is None: - upperLimit = [10, 10] - if lowerLimit is None: - lowerLimit = [0, 0] - if start is None: - start = [0, 0] - self.vertices = dict() - self.edges = [] - self.start = start - self.lowerLimit = lowerLimit - self.upperLimit = upperLimit - self.dimension = len(lowerLimit) - self.num_cells = [0] * self.dimension - self.resolution = resolution - # compute the number of grid cells based on the limits and - # resolution given - for idx in range(self.dimension): - self.num_cells[idx] = np.ceil( - (upperLimit[idx] - lowerLimit[idx]) / resolution) - - vertex_id = self.real_world_to_node_id(start) - self.vertices[vertex_id] = [] - - @staticmethod - def get_root_id(): - # return the id of the root of the tree - return 0 - - def add_vertex(self, vertex): - # add a vertex to the tree - vertex_id = self.real_world_to_node_id(vertex) - self.vertices[vertex_id] = [] - return vertex_id - - def add_edge(self, v, x): - # create an edge between v and x vertices - if (v, x) not in self.edges: - self.edges.append((v, x)) - # since the tree is undirected - self.vertices[v].append(x) - self.vertices[x].append(v) - - def real_coords_to_grid_coord(self, real_coord): - # convert real world coordinates to grid space - # depends on the resolution of the grid - # the output is the same as real world coords if the resolution - # is set to 1 - coord = [0] * self.dimension - for i in range(len(coord)): - start = self.lowerLimit[i] # start of the grid space - coord[i] = int(np.around((real_coord[i] - start) / self.resolution)) - return coord - - def grid_coordinate_to_node_id(self, coord): - # This function maps a grid coordinate to a unique - # node id - nodeId = 0 - for i in range(len(coord) - 1, -1, -1): - product = 1 - for j in range(0, i): - product = product * self.num_cells[j] - nodeId = nodeId + coord[i] * product - return nodeId - - def real_world_to_node_id(self, real_coord): - # first convert the given coordinates to grid space and then - # convert the grid space coordinates to a unique node id - return self.grid_coordinate_to_node_id( - self.real_coords_to_grid_coord(real_coord)) - - def grid_coord_to_real_world_coord(self, coord): - # This function maps a grid coordinate in discrete space - # to a configuration in the full configuration space - config = [0] * self.dimension - for i in range(0, len(coord)): - # start of the real world / configuration space - start = self.lowerLimit[i] - # step from the coordinate in the grid - grid_step = self.resolution * coord[i] - config[i] = start + grid_step - return config - - def node_id_to_grid_coord(self, node_id): - # This function maps a node id to the associated - # grid coordinate - coord = [0] * len(self.lowerLimit) - for i in range(len(coord) - 1, -1, -1): - # Get the product of the grid space maximums - prod = 1 - for j in range(0, i): - prod = prod * self.num_cells[j] - coord[i] = np.floor(node_id / prod) - node_id = node_id - (coord[i] * prod) - return coord - - def node_id_to_real_world_coord(self, nid): - # This function maps a node in discrete space to a configuration - # in the full configuration space - return self.grid_coord_to_real_world_coord( - self.node_id_to_grid_coord(nid)) - - -class BITStar: - - def __init__(self, start, goal, - obstacleList, randArea, eta=2.0, - maxIter=80): - self.start = start - self.goal = goal - - self.min_rand = randArea[0] - self.max_rand = randArea[1] - self.max_iIter = maxIter - self.obstacleList = obstacleList - self.startId = None - self.goalId = None - - self.vertex_queue = [] - self.edge_queue = [] - self.samples = dict() - self.g_scores = dict() - self.f_scores = dict() - self.nodes = dict() - self.r = float('inf') - self.eta = eta # tunable parameter - self.unit_ball_measure = 1 - self.old_vertices = [] - - # initialize tree - lowerLimit = [randArea[0], randArea[0]] - upperLimit = [randArea[1], randArea[1]] - self.tree = RTree(start=start, lowerLimit=lowerLimit, - upperLimit=upperLimit, resolution=0.01) - - def setup_planning(self): - self.startId = self.tree.real_world_to_node_id(self.start) - self.goalId = self.tree.real_world_to_node_id(self.goal) - - # add goal to the samples - self.samples[self.goalId] = self.goal - self.g_scores[self.goalId] = float('inf') - self.f_scores[self.goalId] = 0 - - # add the start id to the tree - self.tree.add_vertex(self.start) - self.g_scores[self.startId] = 0 - self.f_scores[self.startId] = self.compute_heuristic_cost( - self.startId, self.goalId) - - # max length we expect to find in our 'informed' sample space, starts as infinite - cBest = self.g_scores[self.goalId] - - # Computing the sampling space - cMin = math.hypot(self.start[0] - self.goal[0], - self.start[1] - self.goal[1]) / 1.5 - xCenter = np.array([[(self.start[0] + self.goal[0]) / 2.0], - [(self.start[1] + self.goal[1]) / 2.0], [0]]) - a1 = np.array([[(self.goal[0] - self.start[0]) / cMin], - [(self.goal[1] - self.start[1]) / cMin], [0]]) - eTheta = math.atan2(a1[1, 0], a1[0, 0]) - # first column of identity matrix transposed - id1_t = np.array([1.0, 0.0, 0.0]).reshape(1, 3) - M = np.dot(a1, id1_t) - U, S, Vh = np.linalg.svd(M, True, True) - C = np.dot(np.dot(U, np.diag( - [1.0, 1.0, np.linalg.det(U) * np.linalg.det(np.transpose(Vh))])), - Vh) - - self.samples.update(self.informed_sample( - 200, cBest, cMin, xCenter, C)) - - return eTheta, cMin, xCenter, C, cBest - - def setup_sample(self, iterations, foundGoal, cMin, xCenter, C, cBest): - - if len(self.vertex_queue) == 0 and len(self.edge_queue) == 0: - print("Batch: ", iterations) - # Using informed rrt star way of computing the samples - self.r = 2.0 - if iterations != 0: - if foundGoal: - # a better way to do this would be to make number of samples - # a function of cMin - m = 200 - self.samples = dict() - self.samples[self.goalId] = self.goal - else: - m = 100 - cBest = self.g_scores[self.goalId] - self.samples.update(self.informed_sample( - m, cBest, cMin, xCenter, C)) - - # make the old vertices the new vertices - self.old_vertices += self.tree.vertices.keys() - # add the vertices to the vertex queue - for nid in self.tree.vertices.keys(): - if nid not in self.vertex_queue: - self.vertex_queue.append(nid) - return cBest - - def plan(self, animation=True): - - eTheta, cMin, xCenter, C, cBest = self.setup_planning() - iterations = 0 - - foundGoal = False - # run until done - while iterations < self.max_iIter: - cBest = self.setup_sample(iterations, - foundGoal, cMin, xCenter, C, cBest) - # expand the best vertices until an edge is better than the vertex - # this is done because the vertex cost represents the lower bound - # on the edge cost - while self.best_vertex_queue_value() <= \ - self.best_edge_queue_value(): - self.expand_vertex(self.best_in_vertex_queue()) - - # add the best edge to the tree - bestEdge = self.best_in_edge_queue() - self.edge_queue.remove(bestEdge) - - # Check if this can improve the current solution - estimatedCostOfVertex = self.g_scores[bestEdge[ - 0]] + self.compute_distance_cost( - bestEdge[0], bestEdge[1]) + self.compute_heuristic_cost( - bestEdge[1], self.goalId) - estimatedCostOfEdge = self.compute_distance_cost( - self.startId, bestEdge[0]) + self.compute_heuristic_cost( - bestEdge[0], bestEdge[1]) + self.compute_heuristic_cost( - bestEdge[1], self.goalId) - actualCostOfEdge = self.g_scores[ - bestEdge[0]] + self.compute_distance_cost( - bestEdge[0], bestEdge[1]) - - f1 = estimatedCostOfVertex < self.g_scores[self.goalId] - f2 = estimatedCostOfEdge < self.g_scores[self.goalId] - f3 = actualCostOfEdge < self.g_scores[self.goalId] - - if f1 and f2 and f3: - # connect this edge - firstCoord = self.tree.node_id_to_real_world_coord( - bestEdge[0]) - secondCoord = self.tree.node_id_to_real_world_coord( - bestEdge[1]) - path = self.connect(firstCoord, secondCoord) - lastEdge = self.tree.real_world_to_node_id(secondCoord) - if path is None or len(path) == 0: - continue - nextCoord = path[len(path) - 1, :] - nextCoordPathId = self.tree.real_world_to_node_id( - nextCoord) - bestEdge = (bestEdge[0], nextCoordPathId) - if bestEdge[1] in self.tree.vertices.keys(): - continue - else: - try: - del self.samples[bestEdge[1]] - except KeyError: - # invalid sample key - pass - eid = self.tree.add_vertex(nextCoord) - self.vertex_queue.append(eid) - if eid == self.goalId or bestEdge[0] == self.goalId or \ - bestEdge[1] == self.goalId: - print("Goal found") - foundGoal = True - - self.tree.add_edge(bestEdge[0], bestEdge[1]) - - g_score = self.compute_distance_cost( - bestEdge[0], bestEdge[1]) - self.g_scores[bestEdge[1]] = g_score + self.g_scores[ - bestEdge[0]] - self.f_scores[ - bestEdge[1]] = g_score + self.compute_heuristic_cost( - bestEdge[1], self.goalId) - self.update_graph() - - # visualize new edge - if animation: - self.draw_graph(xCenter=xCenter, cBest=cBest, - cMin=cMin, eTheta=eTheta, - samples=self.samples.values(), - start=firstCoord, end=secondCoord) - - self.remove_queue(lastEdge, bestEdge) - - else: - print("Nothing good") - self.edge_queue = [] - self.vertex_queue = [] - - iterations += 1 - - print("Finding the path") - return self.find_final_path() - - def find_final_path(self): - plan = [self.goal] - currId = self.goalId - while currId != self.startId: - plan.append(self.tree.node_id_to_real_world_coord(currId)) - try: - currId = self.nodes[currId] - except KeyError: - print("cannot find Path") - return [] - - plan.append(self.start) - plan = plan[::-1] # reverse the plan - - return plan - - def remove_queue(self, lastEdge, bestEdge): - for edge in self.edge_queue: - if edge[1] == bestEdge[1]: - dist_cost = self.compute_distance_cost(edge[1], bestEdge[1]) - if self.g_scores[edge[1]] + dist_cost >= \ - self.g_scores[self.goalId]: - if (lastEdge, bestEdge[1]) in self.edge_queue: - self.edge_queue.remove( - (lastEdge, bestEdge[1])) - - def connect(self, start, end): - # A function which attempts to extend from a start coordinates - # to goal coordinates - steps = int(self.compute_distance_cost( - self.tree.real_world_to_node_id(start), - self.tree.real_world_to_node_id(end)) * 10) - x = np.linspace(start[0], end[0], num=steps) - y = np.linspace(start[1], end[1], num=steps) - for i in range(len(x)): - if self._collision_check(x[i], y[i]): - if i == 0: - return None - # if collision, send path until collision - return np.vstack((x[0:i], y[0:i])).transpose() - - return np.vstack((x, y)).transpose() - - def _collision_check(self, x, y): - for (ox, oy, size) in self.obstacleList: - dx = ox - x - dy = oy - y - d = dx * dx + dy * dy - if d <= size ** 2: - return True # collision - return False - - def compute_heuristic_cost(self, start_id, goal_id): - # Using Manhattan distance as heuristic - start = np.array(self.tree.node_id_to_real_world_coord(start_id)) - goal = np.array(self.tree.node_id_to_real_world_coord(goal_id)) - - return np.linalg.norm(start - goal, 2) - - def compute_distance_cost(self, vid, xid): - # L2 norm distance - start = np.array(self.tree.node_id_to_real_world_coord(vid)) - stop = np.array(self.tree.node_id_to_real_world_coord(xid)) - - return np.linalg.norm(stop - start, 2) - - # Sample free space confined in the radius of ball R - def informed_sample(self, m, cMax, cMin, xCenter, C): - samples = dict() - print("g_Score goal id: ", self.g_scores[self.goalId]) - for i in range(m + 1): - if cMax < float('inf'): - r = [cMax / 2.0, - math.sqrt(cMax ** 2 - cMin ** 2) / 2.0, - math.sqrt(cMax ** 2 - cMin ** 2) / 2.0] - L = np.diag(r) - xBall = self.sample_unit_ball() - rnd = np.dot(np.dot(C, L), xBall) + xCenter - rnd = [rnd[(0, 0)], rnd[(1, 0)]] - random_id = self.tree.real_world_to_node_id(rnd) - samples[random_id] = rnd - else: - rnd = self.sample_free_space() - random_id = self.tree.real_world_to_node_id(rnd) - samples[random_id] = rnd - return samples - - # Sample point in a unit ball - @staticmethod - def sample_unit_ball(): - a = random.random() - b = random.random() - - if b < a: - a, b = b, a - - sample = (b * math.cos(2 * math.pi * a / b), - b * math.sin(2 * math.pi * a / b)) - return np.array([[sample[0]], [sample[1]], [0]]) - - def sample_free_space(self): - rnd = [random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand)] - - return rnd - - def best_vertex_queue_value(self): - if len(self.vertex_queue) == 0: - return float('inf') - values = [self.g_scores[v] - + self.compute_heuristic_cost(v, self.goalId) for v in - self.vertex_queue] - values.sort() - return values[0] - - def best_edge_queue_value(self): - if len(self.edge_queue) == 0: - return float('inf') - # return the best value in the queue by score g_tau[v] + c(v,x) + h(x) - values = [self.g_scores[e[0]] + self.compute_distance_cost(e[0], e[1]) - + self.compute_heuristic_cost(e[1], self.goalId) for e in - self.edge_queue] - values.sort(reverse=True) - return values[0] - - def best_in_vertex_queue(self): - # return the best value in the vertex queue - v_plus_values = [(v, self.g_scores[v] + - self.compute_heuristic_cost(v, self.goalId)) - for v in self.vertex_queue] - v_plus_values = sorted(v_plus_values, key=lambda x: x[1]) - # print(v_plus_values) - return v_plus_values[0][0] - - def best_in_edge_queue(self): - e_and_values = [ - (e[0], e[1], self.g_scores[e[0]] + self.compute_distance_cost( - e[0], e[1]) + self.compute_heuristic_cost(e[1], self.goalId)) - for e in self.edge_queue] - e_and_values = sorted(e_and_values, key=lambda x: x[2]) - - return e_and_values[0][0], e_and_values[0][1] - - def expand_vertex(self, vid): - self.vertex_queue.remove(vid) - - # get the coordinates for given vid - currCoord = np.array(self.tree.node_id_to_real_world_coord(vid)) - - # get the nearest value in vertex for every one in samples where difference is - # less than the radius - neighbors = [] - for sid, s_coord in self.samples.items(): - s_coord = np.array(s_coord) - if np.linalg.norm(s_coord - currCoord, 2) <= self.r and sid != vid: - neighbors.append((sid, s_coord)) - - # add an edge to the edge queue is the path might improve the solution - for neighbor in neighbors: - sid = neighbor[0] - h_cost = self.compute_heuristic_cost(sid, self.goalId) - estimated_f_score = self.compute_distance_cost( - self.startId, vid) + h_cost + self.compute_distance_cost(vid, - sid) - if estimated_f_score < self.g_scores[self.goalId]: - self.edge_queue.append((vid, sid)) - - # add the vertex to the edge queue - self.add_vertex_to_edge_queue(vid, currCoord) - - def add_vertex_to_edge_queue(self, vid, currCoord): - if vid not in self.old_vertices: - neighbors = [] - for v, edges in self.tree.vertices.items(): - if v != vid and (v, vid) not in self.edge_queue and \ - (vid, v) not in self.edge_queue: - v_coord = self.tree.node_id_to_real_world_coord(v) - if np.linalg.norm(currCoord - v_coord, 2) <= self.r: - neighbors.append((vid, v_coord)) - - for neighbor in neighbors: - sid = neighbor[0] - estimated_f_score = self.compute_distance_cost( - self.startId, vid) + self.compute_distance_cost( - vid, sid) + self.compute_heuristic_cost(sid, self.goalId) - if estimated_f_score < self.g_scores[self.goalId] and ( - self.g_scores[vid] + - self.compute_distance_cost(vid, sid)) < \ - self.g_scores[sid]: - self.edge_queue.append((vid, sid)) - - def update_graph(self): - closedSet = [] - openSet = [] - currId = self.startId - openSet.append(currId) - - while len(openSet) != 0: - # get the element with lowest f_score - currId = min(openSet, key=lambda x: self.f_scores[x]) - - # remove element from open set - openSet.remove(currId) - - # Check if we're at the goal - if currId == self.goalId: - break - - if currId not in closedSet: - closedSet.append(currId) - - # find a non visited successor to the current node - successors = self.tree.vertices[currId] - for successor in successors: - if successor in closedSet: - continue - else: - # calculate tentative g score - g_score = self.g_scores[currId] + \ - self.compute_distance_cost(currId, successor) - if successor not in openSet: - # add the successor to open set - openSet.append(successor) - elif g_score >= self.g_scores[successor]: - continue - - # update g and f scores - self.g_scores[successor] = g_score - self.f_scores[ - successor] = g_score + self.compute_heuristic_cost( - successor, self.goalId) - - # store the parent and child - self.nodes[successor] = currId - - def draw_graph(self, xCenter=None, cBest=None, cMin=None, eTheta=None, - samples=None, start=None, end=None): - plt.clf() - # 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]) - for rnd in samples: - if rnd is not None: - plt.plot(rnd[0], rnd[1], "^k") - if cBest != float('inf'): - self.plot_ellipse(xCenter, cBest, cMin, eTheta) - - if start is not None and end is not None: - plt.plot([start[0], start[1]], [end[0], end[1]], "-g") - - for (ox, oy, size) in self.obstacleList: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start[0], self.start[1], "xr") - plt.plot(self.goal[0], self.goal[1], "xr") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - plt.pause(0.01) - - @staticmethod - def plot_ellipse(xCenter, cBest, cMin, eTheta): # pragma: no cover - - a = math.sqrt(cBest ** 2 - cMin ** 2) / 2.0 - b = cBest / 2.0 - angle = math.pi / 2.0 - eTheta - cx = xCenter[0] - cy = xCenter[1] - - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - x = [a * math.cos(it) for it in t] - y = [b * math.sin(it) for it in t] - R = np.array([[math.cos(angle), math.sin(angle)], - [-math.sin(angle), math.cos(angle)]]) - fx = R @ np.array([x, y]) - px = np.array(fx[0, :] + cx).flatten() - py = np.array(fx[1, :] + cy).flatten() - plt.plot(cx, cy, "xc") - plt.plot(px, py, "--c") - - -def main(maxIter=80): - print("Starting Batch Informed Trees Star planning") - obstacleList = [ - (5, 5, 0.5), - (9, 6, 1), - (7, 5, 1), - (1, 5, 1), - (3, 6, 1), - (7, 9, 1) - ] - - bitStar = BITStar(start=[-1, 0], goal=[3, 8], obstacleList=obstacleList, - randArea=[-2, 15], maxIter=maxIter) - path = bitStar.plan(animation=show_animation) - print("Done") - - if show_animation: - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.05) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BezierPath/bezier_path.py b/PathPlanning/BezierPath/bezier_path.py deleted file mode 100644 index 46faf1f310..0000000000 --- a/PathPlanning/BezierPath/bezier_path.py +++ /dev/null @@ -1,215 +0,0 @@ -""" - -Path planning with Bezier curve. - -author: Atsushi Sakai(@Atsushi_twi) - -""" - -import matplotlib.pyplot as plt -import numpy as np -import scipy.special - -show_animation = True - - -def calc_4points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset): - """ - Compute control points and path given start and end position. - - :param sx: (float) x-coordinate of the starting point - :param sy: (float) y-coordinate of the starting point - :param syaw: (float) yaw angle at start - :param ex: (float) x-coordinate of the ending point - :param ey: (float) y-coordinate of the ending point - :param eyaw: (float) yaw angle at the end - :param offset: (float) - :return: (numpy array, numpy array) - """ - dist = np.hypot(sx - ex, sy - ey) / offset - control_points = np.array( - [[sx, sy], - [sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)], - [ex - dist * np.cos(eyaw), ey - dist * np.sin(eyaw)], - [ex, ey]]) - - path = calc_bezier_path(control_points, n_points=100) - - return path, control_points - - -def calc_bezier_path(control_points, n_points=100): - """ - Compute bezier path (trajectory) given control points. - - :param control_points: (numpy array) - :param n_points: (int) number of points in the trajectory - :return: (numpy array) - """ - traj = [] - for t in np.linspace(0, 1, n_points): - traj.append(bezier(t, control_points)) - - return np.array(traj) - - -def bernstein_poly(n, i, t): - """ - Bernstein polynom. - - :param n: (int) polynom degree - :param i: (int) - :param t: (float) - :return: (float) - """ - return scipy.special.comb(n, i) * t ** i * (1 - t) ** (n - i) - - -def bezier(t, control_points): - """ - Return one point on the bezier curve. - - :param t: (float) number in [0, 1] - :param control_points: (numpy array) - :return: (numpy array) Coordinates of the point - """ - n = len(control_points) - 1 - return np.sum([bernstein_poly(n, i, t) * control_points[i] for i in range(n + 1)], axis=0) - - -def bezier_derivatives_control_points(control_points, n_derivatives): - """ - Compute control points of the successive derivatives of a given bezier curve. - - A derivative of a bezier curve is a bezier curve. - See https://pomax.github.io/bezierinfo/#derivatives - for detailed explanations - - :param control_points: (numpy array) - :param n_derivatives: (int) - e.g., n_derivatives=2 -> compute control points for first and second derivatives - :return: ([numpy array]) - """ - w = {0: control_points} - for i in range(n_derivatives): - n = len(w[i]) - w[i + 1] = np.array([(n - 1) * (w[i][j + 1] - w[i][j]) - for j in range(n - 1)]) - return w - - -def curvature(dx, dy, ddx, ddy): - """ - Compute curvature at one point given first and second derivatives. - - :param dx: (float) First derivative along x axis - :param dy: (float) - :param ddx: (float) Second derivative along x axis - :param ddy: (float) - :return: (float) - """ - return (dx * ddy - dy * ddx) / (dx ** 2 + dy ** 2) ** (3 / 2) - - -def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): # pragma: no cover - """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 * np.cos(yaw), length * np.sin(yaw), - fc=fc, ec=ec, head_width=width, head_length=width) - plt.plot(x, y) - - -def main(): - """Plot an example bezier curve.""" - start_x = 10.0 # [m] - start_y = 1.0 # [m] - start_yaw = np.radians(180.0) # [rad] - - end_x = -0.0 # [m] - end_y = -3.0 # [m] - end_yaw = np.radians(-45.0) # [rad] - offset = 3.0 - - path, control_points = calc_4points_bezier_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset) - - # Note: alternatively, instead of specifying start and end position - # you can directly define n control points and compute the path: - # control_points = np.array([[5., 1.], [-2.78, 1.], [-11.5, -4.5], [-6., -8.]]) - # path = calc_bezier_path(control_points, n_points=100) - - # Display the tangent, normal and radius of cruvature at a given point - t = 0.86 # Number in [0, 1] - x_target, y_target = bezier(t, control_points) - derivatives_cp = bezier_derivatives_control_points(control_points, 2) - point = bezier(t, control_points) - dt = bezier(t, derivatives_cp[1]) - ddt = bezier(t, derivatives_cp[2]) - # Radius of curvature - radius = 1 / curvature(dt[0], dt[1], ddt[0], ddt[1]) - # Normalize derivative - dt /= np.linalg.norm(dt, 2) - tangent = np.array([point, point + dt]) - normal = np.array([point, point + [- dt[1], dt[0]]]) - curvature_center = point + np.array([- dt[1], dt[0]]) * radius - circle = plt.Circle(tuple(curvature_center), radius, - color=(0, 0.8, 0.8), fill=False, linewidth=1) - - assert path.T[0][0] == start_x, "path is invalid" - assert path.T[1][0] == start_y, "path is invalid" - assert path.T[0][-1] == end_x, "path is invalid" - assert path.T[1][-1] == end_y, "path is invalid" - - if show_animation: # pragma: no cover - fig, ax = plt.subplots() - ax.plot(path.T[0], path.T[1], label="Bezier Path") - ax.plot(control_points.T[0], control_points.T[1], - '--o', label="Control Points") - ax.plot(x_target, y_target) - ax.plot(tangent[:, 0], tangent[:, 1], label="Tangent") - ax.plot(normal[:, 0], normal[:, 1], label="Normal") - ax.add_artist(circle) - plot_arrow(start_x, start_y, start_yaw) - plot_arrow(end_x, end_y, end_yaw) - ax.legend() - ax.axis("equal") - ax.grid(True) - plt.show() - - -def main2(): - """Show the effect of the offset.""" - start_x = 10.0 # [m] - start_y = 1.0 # [m] - start_yaw = np.radians(180.0) # [rad] - - end_x = -0.0 # [m] - end_y = -3.0 # [m] - end_yaw = np.radians(-45.0) # [rad] - - for offset in np.arange(1.0, 5.0, 1.0): - path, control_points = calc_4points_bezier_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset) - assert path.T[0][0] == start_x, "path is invalid" - assert path.T[1][0] == start_y, "path is invalid" - assert path.T[0][-1] == end_x, "path is invalid" - assert path.T[1][-1] == end_y, "path is invalid" - - if show_animation: # pragma: no cover - plt.plot(path.T[0], path.T[1], label="Offset=" + str(offset)) - - if show_animation: # pragma: no cover - plot_arrow(start_x, start_y, start_yaw) - plot_arrow(end_x, end_y, end_yaw) - plt.legend() - plt.axis("equal") - plt.grid(True) - plt.show() - - -if __name__ == '__main__': - main() - # main2() diff --git a/PathPlanning/BidirectionalAStar/bidirectional_a_star.py b/PathPlanning/BidirectionalAStar/bidirectional_a_star.py deleted file mode 100644 index 5387cca1ad..0000000000 --- a/PathPlanning/BidirectionalAStar/bidirectional_a_star.py +++ /dev/null @@ -1,347 +0,0 @@ -""" - -Bidirectional A* grid planning - -author: Erwin Lejeune (@spida_rwin) - -See Wikipedia article (https://en.wikipedia.org/wiki/Bidirectional_search) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class BidirectionalAStarPlanner: - - def __init__(self, ox, oy, resolution, rr): - """ - Initialize grid map for a star planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.min_x, self.min_y = None, None - self.max_x, self.max_y = None, None - self.x_width, self.y_width, self.obstacle_map = None, None, None - self.resolution = resolution - self.rr = rr - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, parent_index): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - Bidirectional A star path search - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - start_node = self.Node(self.calc_xy_index(sx, self.min_x), - self.calc_xy_index(sy, self.min_y), 0.0, -1) - goal_node = self.Node(self.calc_xy_index(gx, self.min_x), - self.calc_xy_index(gy, self.min_y), 0.0, -1) - - open_set_A, closed_set_A = dict(), dict() - open_set_B, closed_set_B = dict(), dict() - open_set_A[self.calc_grid_index(start_node)] = start_node - open_set_B[self.calc_grid_index(goal_node)] = goal_node - - current_A = start_node - current_B = goal_node - meet_point_A, meet_point_B = None, None - - while True: - if len(open_set_A) == 0: - print("Open set A is empty..") - break - - if len(open_set_B) == 0: - print("Open set B is empty..") - break - - c_id_A = min( - open_set_A, - key=lambda o: self.find_total_cost(open_set_A, o, current_B)) - - current_A = open_set_A[c_id_A] - - c_id_B = min( - open_set_B, - key=lambda o: self.find_total_cost(open_set_B, o, current_A)) - - current_B = open_set_B[c_id_B] - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current_A.x, self.min_x), - self.calc_grid_position(current_A.y, self.min_y), - "xc") - plt.plot(self.calc_grid_position(current_B.x, self.min_x), - self.calc_grid_position(current_B.y, self.min_y), - "xc") - # 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]) - if len(closed_set_A.keys()) % 10 == 0: - plt.pause(0.001) - - if current_A.x == current_B.x and current_A.y == current_B.y: - print("Found goal") - meet_point_A = current_A - meet_point_B = current_B - break - - # Remove the item from the open set - del open_set_A[c_id_A] - del open_set_B[c_id_B] - - # Add it to the closed set - closed_set_A[c_id_A] = current_A - closed_set_B[c_id_B] = current_B - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - - c_nodes = [self.Node(current_A.x + self.motion[i][0], - current_A.y + self.motion[i][1], - current_A.cost + self.motion[i][2], - c_id_A), - self.Node(current_B.x + self.motion[i][0], - current_B.y + self.motion[i][1], - current_B.cost + self.motion[i][2], - c_id_B)] - - n_ids = [self.calc_grid_index(c_nodes[0]), - self.calc_grid_index(c_nodes[1])] - - # If the node is not safe, do nothing - continue_ = self.check_nodes_and_sets(c_nodes, closed_set_A, - closed_set_B, n_ids) - - if not continue_[0]: - if n_ids[0] not in open_set_A: - # discovered a new node - open_set_A[n_ids[0]] = c_nodes[0] - else: - if open_set_A[n_ids[0]].cost > c_nodes[0].cost: - # This path is the best until now. record it - open_set_A[n_ids[0]] = c_nodes[0] - - if not continue_[1]: - if n_ids[1] not in open_set_B: - # discovered a new node - open_set_B[n_ids[1]] = c_nodes[1] - else: - if open_set_B[n_ids[1]].cost > c_nodes[1].cost: - # This path is the best until now. record it - open_set_B[n_ids[1]] = c_nodes[1] - - rx, ry = self.calc_final_bidirectional_path( - meet_point_A, meet_point_B, closed_set_A, closed_set_B) - - return rx, ry - - # takes two sets and two meeting nodes and return the optimal path - def calc_final_bidirectional_path(self, n1, n2, setA, setB): - rx_A, ry_A = self.calc_final_path(n1, setA) - rx_B, ry_B = self.calc_final_path(n2, setB) - - rx_A.reverse() - ry_A.reverse() - - rx = rx_A + rx_B - ry = ry_A + ry_B - - return rx, ry - - def calc_final_path(self, goal_node, closed_set): - # generate final course - rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], \ - [self.calc_grid_position(goal_node.y, self.min_y)] - parent_index = goal_node.parent_index - while parent_index != -1: - n = closed_set[parent_index] - rx.append(self.calc_grid_position(n.x, self.min_x)) - ry.append(self.calc_grid_position(n.y, self.min_y)) - parent_index = n.parent_index - - return rx, ry - - def check_nodes_and_sets(self, c_nodes, closedSet_A, closedSet_B, n_ids): - continue_ = [False, False] - if not self.verify_node(c_nodes[0]) or n_ids[0] in closedSet_A: - continue_[0] = True - - if not self.verify_node(c_nodes[1]) or n_ids[1] in closedSet_B: - continue_[1] = True - - return continue_ - - @staticmethod - def calc_heuristic(n1, n2): - w = 1.0 # weight of heuristic - d = w * math.hypot(n1.x - n2.x, n1.y - n2.y) - return d - - def find_total_cost(self, open_set, lambda_, n1): - g_cost = open_set[lambda_].cost - h_cost = self.calc_heuristic(n1, open_set[lambda_]) - f_cost = g_cost + h_cost - return f_cost - - def calc_grid_position(self, index, min_position): - """ - calc grid position - - :param index: - :param min_position: - :return: - """ - pos = index * self.resolution + min_position - return pos - - def calc_xy_index(self, position, min_pos): - return round((position - min_pos) / self.resolution) - - def calc_grid_index(self, node): - return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.min_x) - py = self.calc_grid_position(node.y, self.min_y) - - if px < self.min_x: - return False - elif py < self.min_y: - return False - elif px >= self.max_x: - return False - elif py >= self.max_y: - return False - - # collision check - if self.obstacle_map[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.min_x = round(min(ox)) - self.min_y = round(min(oy)) - self.max_x = round(max(ox)) - self.max_y = round(max(oy)) - print("min_x:", self.min_x) - print("min_y:", self.min_y) - print("max_x:", self.max_x) - print("max_y:", self.max_y) - - self.x_width = round((self.max_x - self.min_x) / self.resolution) - self.y_width = round((self.max_y - self.min_y) / self.resolution) - print("x_width:", self.x_width) - print("y_width:", self.y_width) - - # obstacle map generation - self.obstacle_map = [[False for _ in range(self.y_width)] - for _ in range(self.x_width)] - for ix in range(self.x_width): - x = self.calc_grid_position(ix, self.min_x) - for iy in range(self.y_width): - y = self.calc_grid_position(iy, self.min_y) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obstacle_map[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "ob") - plt.grid(True) - plt.axis("equal") - - bidir_a_star = BidirectionalAStarPlanner(ox, oy, grid_size, robot_radius) - rx, ry = bidir_a_star.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(.0001) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BidirectionalBreadthFirstSearch/bidirectional_breadth_first_search.py b/PathPlanning/BidirectionalBreadthFirstSearch/bidirectional_breadth_first_search.py deleted file mode 100644 index 60a8c5e170..0000000000 --- a/PathPlanning/BidirectionalBreadthFirstSearch/bidirectional_breadth_first_search.py +++ /dev/null @@ -1,317 +0,0 @@ -""" - -Bidirectional Breadth-First grid planning - -author: Erwin Lejeune (@spida_rwin) - -See Wikipedia article (https://en.wikipedia.org/wiki/Breadth-first_search) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class BidirectionalBreadthFirstSearchPlanner: - - def __init__(self, ox, oy, resolution, rr): - """ - Initialize grid map for bfs planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.min_x, self.min_y = None, None - self.max_x, self.max_y = None, None - self.x_width, self.y_width, self.obstacle_map = None, None, None - self.resolution = resolution - self.rr = rr - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, parent_index, parent): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index - self.parent = parent - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - Bidirectional Breadth First search based planning - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - start_node = self.Node(self.calc_xy_index(sx, self.min_x), - self.calc_xy_index(sy, self.min_y), 0.0, -1, - None) - goal_node = self.Node(self.calc_xy_index(gx, self.min_x), - self.calc_xy_index(gy, self.min_y), 0.0, -1, - None) - - open_set_A, closed_set_A = dict(), dict() - open_set_B, closed_set_B = dict(), dict() - open_set_B[self.calc_grid_index(goal_node)] = goal_node - open_set_A[self.calc_grid_index(start_node)] = start_node - - meet_point_A, meet_point_B = None, None - - while True: - if len(open_set_A) == 0: - print("Open set A is empty..") - break - - if len(open_set_B) == 0: - print("Open set B is empty") - break - - current_A = open_set_A.pop(list(open_set_A.keys())[0]) - current_B = open_set_B.pop(list(open_set_B.keys())[0]) - - c_id_A = self.calc_grid_index(current_A) - c_id_B = self.calc_grid_index(current_B) - - closed_set_A[c_id_A] = current_A - closed_set_B[c_id_B] = current_B - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current_A.x, self.min_x), - self.calc_grid_position(current_A.y, self.min_y), - "xc") - plt.plot(self.calc_grid_position(current_B.x, self.min_x), - self.calc_grid_position(current_B.y, self.min_y), - "xc") - # 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]) - if len(closed_set_A.keys()) % 10 == 0: - plt.pause(0.001) - - if c_id_A in closed_set_B: - print("Find goal") - meet_point_A = closed_set_A[c_id_A] - meet_point_B = closed_set_B[c_id_A] - break - - elif c_id_B in closed_set_A: - print("Find goal") - meet_point_A = closed_set_A[c_id_B] - meet_point_B = closed_set_B[c_id_B] - break - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - breakA = False - breakB = False - - node_A = self.Node(current_A.x + self.motion[i][0], - current_A.y + self.motion[i][1], - current_A.cost + self.motion[i][2], - c_id_A, None) - node_B = self.Node(current_B.x + self.motion[i][0], - current_B.y + self.motion[i][1], - current_B.cost + self.motion[i][2], - c_id_B, None) - - n_id_A = self.calc_grid_index(node_A) - n_id_B = self.calc_grid_index(node_B) - - # If the node is not safe, do nothing - if not self.verify_node(node_A): - breakA = True - - if not self.verify_node(node_B): - breakB = True - - if (n_id_A not in closed_set_A) and \ - (n_id_A not in open_set_A) and (not breakA): - node_A.parent = current_A - open_set_A[n_id_A] = node_A - - if (n_id_B not in closed_set_B) and \ - (n_id_B not in open_set_B) and (not breakB): - node_B.parent = current_B - open_set_B[n_id_B] = node_B - - rx, ry = self.calc_final_path_bidir( - meet_point_A, meet_point_B, closed_set_A, closed_set_B) - return rx, ry - - # takes both set and meeting nodes and calculate optimal path - def calc_final_path_bidir(self, n1, n2, setA, setB): - rxA, ryA = self.calc_final_path(n1, setA) - rxB, ryB = self.calc_final_path(n2, setB) - - rxA.reverse() - ryA.reverse() - - rx = rxA + rxB - ry = ryA + ryB - - return rx, ry - - def calc_final_path(self, goal_node, closed_set): - # generate final course - rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [ - self.calc_grid_position(goal_node.y, self.min_y)] - n = closed_set[goal_node.parent_index] - while n is not None: - rx.append(self.calc_grid_position(n.x, self.min_x)) - ry.append(self.calc_grid_position(n.y, self.min_y)) - n = n.parent - - return rx, ry - - def calc_grid_position(self, index, min_position): - """ - calc grid position - - :param index: - :param min_position: - :return: - """ - pos = index * self.resolution + min_position - return pos - - def calc_xy_index(self, position, min_pos): - return round((position - min_pos) / self.resolution) - - def calc_grid_index(self, node): - return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.min_x) - py = self.calc_grid_position(node.y, self.min_y) - - if px < self.min_x: - return False - elif py < self.min_y: - return False - elif px >= self.max_x: - return False - elif py >= self.max_y: - return False - - # collision check - if self.obstacle_map[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.min_x = round(min(ox)) - self.min_y = round(min(oy)) - self.max_x = round(max(ox)) - self.max_y = round(max(oy)) - print("min_x:", self.min_x) - print("min_y:", self.min_y) - print("max_x:", self.max_x) - print("max_y:", self.max_y) - - self.x_width = round((self.max_x - self.min_x) / self.resolution) - self.y_width = round((self.max_y - self.min_y) / self.resolution) - print("x_width:", self.x_width) - print("y_width:", self.y_width) - - # obstacle map generation - self.obstacle_map = [[False for _ in range(self.y_width)] - for _ in range(self.x_width)] - for ix in range(self.x_width): - x = self.calc_grid_position(ix, self.min_x) - for iy in range(self.y_width): - y = self.calc_grid_position(iy, self.min_y) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obstacle_map[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "ob") - plt.grid(True) - plt.axis("equal") - - bi_bfs = BidirectionalBreadthFirstSearchPlanner( - ox, oy, grid_size, robot_radius) - rx, ry = bi_bfs.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BreadthFirstSearch/breadth_first_search.py b/PathPlanning/BreadthFirstSearch/breadth_first_search.py deleted file mode 100644 index ad994732a5..0000000000 --- a/PathPlanning/BreadthFirstSearch/breadth_first_search.py +++ /dev/null @@ -1,258 +0,0 @@ -""" - -Breadth-First grid planning - -author: Erwin Lejeune (@spida_rwin) - -See Wikipedia article (https://en.wikipedia.org/wiki/Breadth-first_search) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class BreadthFirstSearchPlanner: - - def __init__(self, ox, oy, reso, rr): - """ - Initialize grid map for bfs planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.reso = reso - self.rr = rr - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, parent_index, parent): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index - self.parent = parent - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - Breadth First search based planning - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - nstart = self.Node(self.calc_xyindex(sx, self.minx), - self.calc_xyindex(sy, self.miny), 0.0, -1, None) - ngoal = self.Node(self.calc_xyindex(gx, self.minx), - self.calc_xyindex(gy, self.miny), 0.0, -1, None) - - open_set, closed_set = dict(), dict() - open_set[self.calc_grid_index(nstart)] = nstart - - while True: - if len(open_set) == 0: - print("Open set is empty..") - break - - current = open_set.pop(list(open_set.keys())[0]) - - c_id = self.calc_grid_index(current) - - closed_set[c_id] = current - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current.x, self.minx), - self.calc_grid_position(current.y, self.miny), "xc") - # 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]) - if len(closed_set.keys()) % 10 == 0: - plt.pause(0.001) - - if current.x == ngoal.x and current.y == ngoal.y: - print("Find goal") - ngoal.parent_index = current.parent_index - ngoal.cost = current.cost - break - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - node = self.Node(current.x + self.motion[i][0], - current.y + self.motion[i][1], - current.cost + self.motion[i][2], c_id, None) - n_id = self.calc_grid_index(node) - - # If the node is not safe, do nothing - if not self.verify_node(node): - continue - - if (n_id not in closed_set) and (n_id not in open_set): - node.parent = current - open_set[n_id] = node - - rx, ry = self.calc_final_path(ngoal, closed_set) - return rx, ry - - def calc_final_path(self, ngoal, closedset): - # generate final course - rx, ry = [self.calc_grid_position(ngoal.x, self.minx)], [ - self.calc_grid_position(ngoal.y, self.miny)] - n = closedset[ngoal.parent_index] - while n is not None: - rx.append(self.calc_grid_position(n.x, self.minx)) - ry.append(self.calc_grid_position(n.y, self.miny)) - n = n.parent - - return rx, ry - - def calc_grid_position(self, index, minp): - """ - calc grid position - - :param index: - :param minp: - :return: - """ - pos = index * self.reso + minp - return pos - - def calc_xyindex(self, position, min_pos): - return round((position - min_pos) / self.reso) - - def calc_grid_index(self, node): - return (node.y - self.miny) * self.xwidth + (node.x - self.minx) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.minx) - py = self.calc_grid_position(node.y, self.miny) - - if px < self.minx: - return False - elif py < self.miny: - return False - elif px >= self.maxx: - return False - elif py >= self.maxy: - return False - - # collision check - if self.obmap[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.minx = round(min(ox)) - self.miny = round(min(oy)) - self.maxx = round(max(ox)) - self.maxy = round(max(oy)) - print("min_x:", self.minx) - print("min_y:", self.miny) - print("max_x:", self.maxx) - print("max_y:", self.maxy) - - self.xwidth = round((self.maxx - self.minx) / self.reso) - self.ywidth = round((self.maxy - self.miny) / self.reso) - print("x_width:", self.xwidth) - print("y_width:", self.ywidth) - - # obstacle map generation - self.obmap = [[False for _ in range(self.ywidth)] - for _ in range(self.xwidth)] - for ix in range(self.xwidth): - x = self.calc_grid_position(ix, self.minx) - for iy in range(self.ywidth): - y = self.calc_grid_position(iy, self.miny) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obmap[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(float(i)) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(float(i)) - for i in range(-10, 61): - ox.append(float(i)) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(float(i)) - for i in range(-10, 40): - ox.append(20.0) - oy.append(float(i)) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - - bfs = BreadthFirstSearchPlanner(ox, oy, grid_size, robot_radius) - rx, ry = bfs.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/BugPlanning/bug.py b/PathPlanning/BugPlanning/bug.py deleted file mode 100644 index 34890cb55a..0000000000 --- a/PathPlanning/BugPlanning/bug.py +++ /dev/null @@ -1,333 +0,0 @@ -""" -Bug Planning -author: Sarim Mehdi(muhammadsarim.mehdi@studio.unibo.it) -Source: https://web.archive.org/web/20201103052224/https://sites.google.com/site/ece452bugalgorithms/ -""" - -import numpy as np -import matplotlib.pyplot as plt - -show_animation = True - - -class BugPlanner: - def __init__(self, start_x, start_y, goal_x, goal_y, obs_x, obs_y): - self.goal_x = goal_x - self.goal_y = goal_y - self.obs_x = obs_x - self.obs_y = obs_y - self.r_x = [start_x] - self.r_y = [start_y] - self.out_x = [] - self.out_y = [] - for o_x, o_y in zip(obs_x, obs_y): - for add_x, add_y in zip([1, 0, -1, -1, -1, 0, 1, 1], - [1, 1, 1, 0, -1, -1, -1, 0]): - cand_x, cand_y = o_x+add_x, o_y+add_y - valid_point = True - for _x, _y in zip(obs_x, obs_y): - if cand_x == _x and cand_y == _y: - valid_point = False - break - if valid_point: - self.out_x.append(cand_x), self.out_y.append(cand_y) - - def mov_normal(self): - return self.r_x[-1] + np.sign(self.goal_x - self.r_x[-1]), \ - self.r_y[-1] + np.sign(self.goal_y - self.r_y[-1]) - - def mov_to_next_obs(self, visited_x, visited_y): - for add_x, add_y in zip([1, 0, -1, 0], [0, 1, 0, -1]): - c_x, c_y = self.r_x[-1] + add_x, self.r_y[-1] + add_y - for _x, _y in zip(self.out_x, self.out_y): - use_pt = True - if c_x == _x and c_y == _y: - for v_x, v_y in zip(visited_x, visited_y): - if c_x == v_x and c_y == v_y: - use_pt = False - break - if use_pt: - return c_x, c_y, False - if not use_pt: - break - return self.r_x[-1], self.r_y[-1], True - - def bug0(self): - """ - Greedy algorithm where you move towards goal - until you hit an obstacle. Then you go around it - (pick an arbitrary direction), until it is possible - for you to start moving towards goal in a greedy manner again - """ - mov_dir = 'normal' - cand_x, cand_y = -np.inf, -np.inf - if show_animation: - plt.plot(self.obs_x, self.obs_y, ".k") - plt.plot(self.r_x[-1], self.r_y[-1], "og") - plt.plot(self.goal_x, self.goal_y, "xb") - plt.plot(self.out_x, self.out_y, ".") - plt.grid(True) - plt.title('BUG 0') - - for x_ob, y_ob in zip(self.out_x, self.out_y): - if self.r_x[-1] == x_ob and self.r_y[-1] == y_ob: - mov_dir = 'obs' - break - - visited_x, visited_y = [], [] - while True: - if self.r_x[-1] == self.goal_x and \ - self.r_y[-1] == self.goal_y: - break - if mov_dir == 'normal': - cand_x, cand_y = self.mov_normal() - if mov_dir == 'obs': - cand_x, cand_y, _ = self.mov_to_next_obs(visited_x, visited_y) - if mov_dir == 'normal': - found_boundary = False - for x_ob, y_ob in zip(self.out_x, self.out_y): - if cand_x == x_ob and cand_y == y_ob: - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x[:], visited_y[:] = [], [] - visited_x.append(cand_x), visited_y.append(cand_y) - mov_dir = 'obs' - found_boundary = True - break - if not found_boundary: - self.r_x.append(cand_x), self.r_y.append(cand_y) - elif mov_dir == 'obs': - can_go_normal = True - for x_ob, y_ob in zip(self.obs_x, self.obs_y): - if self.mov_normal()[0] == x_ob and \ - self.mov_normal()[1] == y_ob: - can_go_normal = False - break - if can_go_normal: - mov_dir = 'normal' - else: - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x.append(cand_x), visited_y.append(cand_y) - if show_animation: - plt.plot(self.r_x, self.r_y, "-r") - plt.pause(0.001) - if show_animation: - plt.show() - - def bug1(self): - """ - Move towards goal in a greedy manner. - When you hit an obstacle, you go around it and - back to where you hit the obstacle initially. - Then, you go to the point on the obstacle that is - closest to your goal and you start moving towards - goal in a greedy manner from that new point. - """ - mov_dir = 'normal' - cand_x, cand_y = -np.inf, -np.inf - exit_x, exit_y = -np.inf, -np.inf - dist = np.inf - back_to_start = False - second_round = False - if show_animation: - plt.plot(self.obs_x, self.obs_y, ".k") - plt.plot(self.r_x[-1], self.r_y[-1], "og") - plt.plot(self.goal_x, self.goal_y, "xb") - plt.plot(self.out_x, self.out_y, ".") - plt.grid(True) - plt.title('BUG 1') - - for xob, yob in zip(self.out_x, self.out_y): - if self.r_x[-1] == xob and self.r_y[-1] == yob: - mov_dir = 'obs' - break - - visited_x, visited_y = [], [] - while True: - if self.r_x[-1] == self.goal_x and \ - self.r_y[-1] == self.goal_y: - break - if mov_dir == 'normal': - cand_x, cand_y = self.mov_normal() - if mov_dir == 'obs': - cand_x, cand_y, back_to_start = \ - self.mov_to_next_obs(visited_x, visited_y) - if mov_dir == 'normal': - found_boundary = False - for x_ob, y_ob in zip(self.out_x, self.out_y): - if cand_x == x_ob and cand_y == y_ob: - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x[:], visited_y[:] = [], [] - visited_x.append(cand_x), visited_y.append(cand_y) - mov_dir = 'obs' - dist = np.inf - back_to_start = False - second_round = False - found_boundary = True - break - if not found_boundary: - self.r_x.append(cand_x), self.r_y.append(cand_y) - elif mov_dir == 'obs': - d = np.linalg.norm(np.array([cand_x, cand_y] - - np.array([self.goal_x, - self.goal_y]))) - if d < dist and not second_round: - exit_x, exit_y = cand_x, cand_y - dist = d - if back_to_start and not second_round: - second_round = True - del self.r_x[-len(visited_x):] - del self.r_y[-len(visited_y):] - visited_x[:], visited_y[:] = [], [] - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x.append(cand_x), visited_y.append(cand_y) - if cand_x == exit_x and \ - cand_y == exit_y and \ - second_round: - mov_dir = 'normal' - if show_animation: - plt.plot(self.r_x, self.r_y, "-r") - plt.pause(0.001) - if show_animation: - plt.show() - - def bug2(self): - """ - Move towards goal in a greedy manner. - When you hit an obstacle, you go around it and - keep track of your distance from the goal. - If the distance from your goal was decreasing before - and now it starts increasing, that means the current - point is probably the closest point to the - goal (this may or may not be true because the algorithm - doesn't explore the entire boundary around the obstacle). - So, you depart from this point and continue towards the - goal in a greedy manner - """ - mov_dir = 'normal' - cand_x, cand_y = -np.inf, -np.inf - if show_animation: - plt.plot(self.obs_x, self.obs_y, ".k") - plt.plot(self.r_x[-1], self.r_y[-1], "og") - plt.plot(self.goal_x, self.goal_y, "xb") - plt.plot(self.out_x, self.out_y, ".") - - straight_x, straight_y = [self.r_x[-1]], [self.r_y[-1]] - hit_x, hit_y = [], [] - while True: - if straight_x[-1] == self.goal_x and \ - straight_y[-1] == self.goal_y: - break - c_x = straight_x[-1] + np.sign(self.goal_x - straight_x[-1]) - c_y = straight_y[-1] + np.sign(self.goal_y - straight_y[-1]) - for x_ob, y_ob in zip(self.out_x, self.out_y): - if c_x == x_ob and c_y == y_ob: - hit_x.append(c_x), hit_y.append(c_y) - break - straight_x.append(c_x), straight_y.append(c_y) - if show_animation: - plt.plot(straight_x, straight_y, ",") - plt.plot(hit_x, hit_y, "d") - plt.grid(True) - plt.title('BUG 2') - - for x_ob, y_ob in zip(self.out_x, self.out_y): - if self.r_x[-1] == x_ob and self.r_y[-1] == y_ob: - mov_dir = 'obs' - break - - visited_x, visited_y = [], [] - while True: - if self.r_x[-1] == self.goal_x \ - and self.r_y[-1] == self.goal_y: - break - if mov_dir == 'normal': - cand_x, cand_y = self.mov_normal() - if mov_dir == 'obs': - cand_x, cand_y, _ = self.mov_to_next_obs(visited_x, visited_y) - if mov_dir == 'normal': - found_boundary = False - for x_ob, y_ob in zip(self.out_x, self.out_y): - if cand_x == x_ob and cand_y == y_ob: - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x[:], visited_y[:] = [], [] - visited_x.append(cand_x), visited_y.append(cand_y) - del hit_x[0] - del hit_y[0] - mov_dir = 'obs' - found_boundary = True - break - if not found_boundary: - self.r_x.append(cand_x), self.r_y.append(cand_y) - elif mov_dir == 'obs': - self.r_x.append(cand_x), self.r_y.append(cand_y) - visited_x.append(cand_x), visited_y.append(cand_y) - for i_x, i_y in zip(range(len(hit_x)), range(len(hit_y))): - if cand_x == hit_x[i_x] and cand_y == hit_y[i_y]: - del hit_x[i_x] - del hit_y[i_y] - mov_dir = 'normal' - break - if show_animation: - plt.plot(self.r_x, self.r_y, "-r") - plt.pause(0.001) - if show_animation: - plt.show() - - -def main(bug_0, bug_1, bug_2): - # set obstacle positions - o_x, o_y = [], [] - - s_x = 0.0 - s_y = 0.0 - g_x = 167.0 - g_y = 50.0 - - for i in range(20, 40): - for j in range(20, 40): - o_x.append(i) - o_y.append(j) - - for i in range(60, 100): - for j in range(40, 80): - o_x.append(i) - o_y.append(j) - - for i in range(120, 140): - for j in range(80, 100): - o_x.append(i) - o_y.append(j) - - for i in range(80, 140): - for j in range(0, 20): - o_x.append(i) - o_y.append(j) - - for i in range(0, 20): - for j in range(60, 100): - o_x.append(i) - o_y.append(j) - - for i in range(20, 40): - for j in range(80, 100): - o_x.append(i) - o_y.append(j) - - for i in range(120, 160): - for j in range(40, 60): - o_x.append(i) - o_y.append(j) - - if bug_0: - my_Bug = BugPlanner(s_x, s_y, g_x, g_y, o_x, o_y) - my_Bug.bug0() - if bug_1: - my_Bug = BugPlanner(s_x, s_y, g_x, g_y, o_x, o_y) - my_Bug.bug1() - if bug_2: - my_Bug = BugPlanner(s_x, s_y, g_x, g_y, o_x, o_y) - my_Bug.bug2() - - -if __name__ == '__main__': - main(bug_0=True, bug_1=False, bug_2=False) diff --git a/PathPlanning/Catmull_RomSplinePath/blending_functions.py b/PathPlanning/Catmull_RomSplinePath/blending_functions.py deleted file mode 100644 index f3ef5dd323..0000000000 --- a/PathPlanning/Catmull_RomSplinePath/blending_functions.py +++ /dev/null @@ -1,34 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -def blending_function_1(t): - return -t + 2*t**2 - t**3 - -def blending_function_2(t): - return 2 - 5*t**2 + 3*t**3 - -def blending_function_3(t): - return t + 4*t**2 - 3*t**3 - -def blending_function_4(t): - return -t**2 + t**3 - -def plot_blending_functions(): - t = np.linspace(0, 1, 100) - - plt.plot(t, blending_function_1(t), label='b1') - plt.plot(t, blending_function_2(t), label='b2') - plt.plot(t, blending_function_3(t), label='b3') - plt.plot(t, blending_function_4(t), label='b4') - - plt.title("Catmull-Rom Blending Functions") - plt.xlabel("t") - plt.ylabel("Value") - plt.legend() - plt.grid(True) - plt.axhline(y=0, color='k', linestyle='--') - plt.axvline(x=0, color='k', linestyle='--') - plt.show() - -if __name__ == "__main__": - plot_blending_functions() \ No newline at end of file diff --git a/PathPlanning/Catmull_RomSplinePath/catmull_rom_spline_path.py b/PathPlanning/Catmull_RomSplinePath/catmull_rom_spline_path.py deleted file mode 100644 index 79916330c9..0000000000 --- a/PathPlanning/Catmull_RomSplinePath/catmull_rom_spline_path.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Path Planner with Catmull-Rom Spline -Author: Surabhi Gupta (@this_is_surabhi) -Source: http://graphics.cs.cmu.edu/nsp/course/15-462/Fall04/assts/catmullRom.pdf -""" - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import numpy as np -import matplotlib.pyplot as plt - -def catmull_rom_point(t, p0, p1, p2, p3): - """ - Parameters - ---------- - t : float - Parameter value (0 <= t <= 1) (0 <= t <= 1) - p0, p1, p2, p3 : np.ndarray - Control points for the spline segment - - Returns - ------- - np.ndarray - Calculated point on the spline - """ - return 0.5 * ((2 * p1) + - (-p0 + p2) * t + - (2 * p0 - 5 * p1 + 4 * p2 - p3) * t**2 + - (-p0 + 3 * p1 - 3 * p2 + p3) * t**3) - - -def catmull_rom_spline(control_points, num_points): - """ - Parameters - ---------- - control_points : list - List of control points - num_points : int - Number of points to generate on the spline - - Returns - ------- - tuple - x and y coordinates of the spline points - """ - t_vals = np.linspace(0, 1, num_points) - spline_points = [] - - control_points = np.array(control_points) - - for i in range(len(control_points) - 1): - if i == 0: - p1, p2, p3 = control_points[i:i+3] - p0 = p1 - elif i == len(control_points) - 2: - p0, p1, p2 = control_points[i-1:i+2] - p3 = p2 - else: - p0, p1, p2, p3 = control_points[i-1:i+3] - - for t in t_vals: - point = catmull_rom_point(t, p0, p1, p2, p3) - spline_points.append(point) - - return np.array(spline_points).T - - -def main(): - print(__file__ + " start!!") - - way_points = [[-1.0, -2.0], [1.0, -1.0], [3.0, -2.0], [4.0, -1.0], [3.0, 1.0], [1.0, 2.0], [0.0, 2.0]] - n_course_point = 100 - spline_x, spline_y = catmull_rom_spline(way_points, n_course_point) - - plt.plot(spline_x,spline_y, '-r', label="Catmull-Rom Spline Path") - plt.plot(np.array(way_points).T[0], np.array(way_points).T[1], '-og', label="Way points") - plt.title("Catmull-Rom Spline Path") - plt.grid(True) - plt.legend() - plt.axis("equal") - plt.show() - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/PathPlanning/ClosedLoopRRTStar/closed_loop_rrt_star_car.py b/PathPlanning/ClosedLoopRRTStar/closed_loop_rrt_star_car.py deleted file mode 100644 index 01ab8349a9..0000000000 --- a/PathPlanning/ClosedLoopRRTStar/closed_loop_rrt_star_car.py +++ /dev/null @@ -1,216 +0,0 @@ -""" - -Path planning Sample Code with Closed loop RRT for car like robot. - -author: AtsushiSakai(@Atsushi_twi) - -""" -import matplotlib.pyplot as plt -import numpy as np - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from ClosedLoopRRTStar import pure_pursuit -from ClosedLoopRRTStar import unicycle_model -from ReedsSheppPath import reeds_shepp_path_planning -from RRTStarReedsShepp.rrt_star_reeds_shepp import RRTStarReedsShepp - -show_animation = True - - -class ClosedLoopRRTStar(RRTStarReedsShepp): - """ - Class for Closed loop RRT star planning - """ - - def __init__(self, start, goal, obstacle_list, rand_area, - max_iter=200, - connect_circle_dist=50.0, - robot_radius=0.0 - ): - super().__init__(start, goal, obstacle_list, rand_area, - max_iter=max_iter, - connect_circle_dist=connect_circle_dist, - robot_radius=robot_radius - ) - - self.target_speed = 10.0 / 3.6 - self.yaw_th = np.deg2rad(3.0) - self.xy_th = 0.5 - self.invalid_travel_ratio = 5.0 - - def planning(self, animation=True): - """ - do planning - - animation: flag for animation on or off - """ - # planning with RRTStarReedsShepp - super().planning(animation=animation) - - # generate coruse - path_indexs = self.get_goal_indexes() - - flag, x, y, yaw, v, t, a, d = self.search_best_feasible_path( - path_indexs) - - return flag, x, y, yaw, v, t, a, d - - def search_best_feasible_path(self, path_indexs): - - print("Start search feasible path") - - best_time = float("inf") - - fx, fy, fyaw, fv, ft, fa, fd = None, None, None, None, None, None, None - - # pure pursuit tracking - for ind in path_indexs: - path = self.generate_final_course(ind) - - flag, x, y, yaw, v, t, a, d = self.check_tracking_path_is_feasible( - path) - - if flag and best_time >= t[-1]: - print("feasible path is found") - best_time = t[-1] - fx, fy, fyaw, fv, ft, fa, fd = x, y, yaw, v, t, a, d - - print("best time is") - print(best_time) - - if fx: - fx.append(self.end.x) - fy.append(self.end.y) - fyaw.append(self.end.yaw) - return True, fx, fy, fyaw, fv, ft, fa, fd - - return False, None, None, None, None, None, None, None - - def check_tracking_path_is_feasible(self, path): - cx = np.array([state[0] for state in path])[::-1] - cy = np.array([state[1] for state in path])[::-1] - cyaw = np.array([state[2] for state in path])[::-1] - - goal = [cx[-1], cy[-1], cyaw[-1]] - - cx, cy, cyaw = pure_pursuit.extend_path(cx, cy, cyaw) - - speed_profile = pure_pursuit.calc_speed_profile( - cx, cy, cyaw, self.target_speed) - - t, x, y, yaw, v, a, d, find_goal = pure_pursuit.closed_loop_prediction( - cx, cy, cyaw, speed_profile, goal) - yaw = [reeds_shepp_path_planning.pi_2_pi(iyaw) for iyaw in yaw] - - if not find_goal: - print("cannot reach goal") - - if abs(yaw[-1] - goal[2]) >= self.yaw_th * 10.0: - print("final angle is bad") - find_goal = False - - travel = unicycle_model.dt * sum(np.abs(v)) - origin_travel = sum(np.hypot(np.diff(cx), np.diff(cy))) - - if (travel / origin_travel) >= self.invalid_travel_ratio: - print("path is too long") - find_goal = False - - tmp_node = self.Node(x, y, 0) - tmp_node.path_x = x - tmp_node.path_y = y - if not self.check_collision( - tmp_node, self.obstacle_list, self.robot_radius): - print("This path is collision") - find_goal = False - - return find_goal, x, y, yaw, v, t, a, d - - def get_goal_indexes(self): - goalinds = [] - for (i, node) in enumerate(self.node_list): - if self.calc_dist_to_goal(node.x, node.y) <= self.xy_th: - goalinds.append(i) - print("OK XY TH num is") - print(len(goalinds)) - - # angle check - fgoalinds = [] - for i in goalinds: - if abs(self.node_list[i].yaw - self.end.yaw) <= self.yaw_th: - fgoalinds.append(i) - print("OK YAW TH num is") - print(len(fgoalinds)) - - return fgoalinds - - -def main(gx=6.0, gy=7.0, gyaw=np.deg2rad(90.0), max_iter=100): - print("Start" + __file__) - # ====Search Path with RRT==== - obstacle_list = [ - (5, 5, 1), - (4, 6, 1), - (4, 8, 1), - (4, 10, 1), - (6, 5, 1), - (7, 5, 1), - (8, 6, 1), - (8, 8, 1), - (8, 10, 1) - ] # [x,y,size(radius)] - - # Set Initial parameters - start = [0.0, 0.0, np.deg2rad(0.0)] - goal = [gx, gy, gyaw] - - closed_loop_rrt_star = ClosedLoopRRTStar(start, goal, - obstacle_list, - [-2.0, 20.0], - max_iter=max_iter) - flag, x, y, yaw, v, t, a, d = closed_loop_rrt_star.planning( - animation=show_animation) - - if not flag: - print("cannot find feasible path") - - # Draw final path - if show_animation: - closed_loop_rrt_star.draw_graph() - plt.plot(x, y, '-r') - plt.grid(True) - plt.pause(0.001) - - plt.subplots(1) - plt.plot(t, [np.rad2deg(iyaw) for iyaw in yaw[:-1]], '-r') - plt.xlabel("time[s]") - plt.ylabel("Yaw[deg]") - plt.grid(True) - - plt.subplots(1) - plt.plot(t, [iv * 3.6 for iv in v], '-r') - - plt.xlabel("time[s]") - plt.ylabel("velocity[km/h]") - plt.grid(True) - - plt.subplots(1) - plt.plot(t, a, '-r') - plt.xlabel("time[s]") - plt.ylabel("accel[m/ss]") - plt.grid(True) - - plt.subplots(1) - plt.plot(t, [np.rad2deg(td) for td in d], '-r') - plt.xlabel("time[s]") - plt.ylabel("Steering angle[deg]") - plt.grid(True) - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/ClosedLoopRRTStar/pure_pursuit.py b/PathPlanning/ClosedLoopRRTStar/pure_pursuit.py deleted file mode 100644 index 982ebeca06..0000000000 --- a/PathPlanning/ClosedLoopRRTStar/pure_pursuit.py +++ /dev/null @@ -1,300 +0,0 @@ -""" - -Path tracking simulation with pure pursuit steering control and PID speed control. - -author: Atsushi Sakai - -""" -import math - -import matplotlib.pyplot as plt -import numpy as np - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from ClosedLoopRRTStar import unicycle_model - -Kp = 2.0 # speed propotional gain -Lf = 0.5 # look-ahead distance -T = 100.0 # max simulation time -goal_dis = 0.5 -stop_speed = 0.5 - -# animation = True -animation = False - - -def PIDControl(target, current): - a = Kp * (target - current) - - if a > unicycle_model.accel_max: - a = unicycle_model.accel_max - elif a < -unicycle_model.accel_max: - a = -unicycle_model.accel_max - - return a - - -def pure_pursuit_control(state, cx, cy, pind): - - ind, dis = calc_target_index(state, cx, cy) - - if pind >= ind: - ind = pind - - # print(parent_index, ind) - if ind < len(cx): - tx = cx[ind] - ty = cy[ind] - else: - tx = cx[-1] - ty = cy[-1] - ind = len(cx) - 1 - - alpha = math.atan2(ty - state.y, tx - state.x) - state.yaw - - if state.v <= 0.0: # back - alpha = math.pi - alpha - - delta = math.atan2(2.0 * unicycle_model.L * math.sin(alpha) / Lf, 1.0) - - if delta > unicycle_model.steer_max: - delta = unicycle_model.steer_max - elif delta < - unicycle_model.steer_max: - delta = -unicycle_model.steer_max - - return delta, ind, dis - - -def calc_target_index(state, cx, cy): - dx = [state.x - icx for icx in cx] - dy = [state.y - icy for icy in cy] - - d = np.hypot(dx, dy) - mindis = min(d) - - ind = np.argmin(d) - - L = 0.0 - - while Lf > L and (ind + 1) < len(cx): - dx = cx[ind + 1] - cx[ind] - dy = cy[ind + 1] - cy[ind] - L += math.hypot(dx, dy) - ind += 1 - - # print(mindis) - return ind, mindis - - -def closed_loop_prediction(cx, cy, cyaw, speed_profile, goal): - - state = unicycle_model.State(x=-0.0, y=-0.0, yaw=0.0, v=0.0) - - # lastIndex = len(cx) - 1 - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - a = [0.0] - d = [0.0] - target_ind, mindis = calc_target_index(state, cx, cy) - find_goal = False - - maxdis = 0.5 - - while T >= time: - di, target_ind, dis = pure_pursuit_control(state, cx, cy, target_ind) - - target_speed = speed_profile[target_ind] - target_speed = target_speed * \ - (maxdis - min(dis, maxdis - 0.1)) / maxdis - - ai = PIDControl(target_speed, state.v) - state = unicycle_model.update(state, ai, di) - - if abs(state.v) <= stop_speed and target_ind <= len(cx) - 2: - target_ind += 1 - - time = time + unicycle_model.dt - - # check goal - dx = state.x - goal[0] - dy = state.y - goal[1] - if math.hypot(dx, dy) <= goal_dis: - find_goal = True - break - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - a.append(ai) - d.append(di) - - if target_ind % 1 == 0 and animation: # pragma: no cover - 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]) - plt.plot(cx, cy, "-r", label="course") - plt.plot(x, y, "ob", label="trajectory") - plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title("speed:" + str(round(state.v, 2)) - + "tind:" + str(target_ind)) - plt.pause(0.0001) - - else: - print("Time out!!") - - return t, x, y, yaw, v, a, d, find_goal - - -def set_stop_point(target_speed, cx, cy, cyaw): - speed_profile = [target_speed] * len(cx) - forward = True - - d = [] - is_back = False - - # Set stop point - for i in range(len(cx) - 1): - dx = cx[i + 1] - cx[i] - dy = cy[i + 1] - cy[i] - d.append(math.hypot(dx, dy)) - iyaw = cyaw[i] - move_direction = math.atan2(dy, dx) - is_back = abs(move_direction - iyaw) >= math.pi / 2.0 - - if dx == 0.0 and dy == 0.0: - continue - - if is_back: - speed_profile[i] = - target_speed - else: - speed_profile[i] = target_speed - - if is_back and forward: - speed_profile[i] = 0.0 - forward = False - # plt.plot(cx[i], cy[i], "xb") - # print(i_yaw, move_direction, dx, dy) - elif not is_back and not forward: - speed_profile[i] = 0.0 - forward = True - # plt.plot(cx[i], cy[i], "xb") - # print(i_yaw, move_direction, dx, dy) - speed_profile[0] = 0.0 - if is_back: - speed_profile[-1] = -stop_speed - else: - speed_profile[-1] = stop_speed - - d.append(d[-1]) - - return speed_profile, d - - -def calc_speed_profile(cx, cy, cyaw, target_speed): - - speed_profile, d = set_stop_point(target_speed, cx, cy, cyaw) - - if animation: # pragma: no cover - plt.plot(speed_profile, "xb") - - return speed_profile - - -def extend_path(cx, cy, cyaw): - - dl = 0.1 - dl_list = [dl] * (int(Lf / dl) + 1) - - move_direction = math.atan2(cy[-1] - cy[-3], cx[-1] - cx[-3]) - is_back = abs(move_direction - cyaw[-1]) >= math.pi / 2.0 - - for idl in dl_list: - if is_back: - idl *= -1 - cx = np.append(cx, cx[-1] + idl * math.cos(cyaw[-1])) - cy = np.append(cy, cy[-1] + idl * math.sin(cyaw[-1])) - cyaw = np.append(cyaw, cyaw[-1]) - - return cx, cy, cyaw - - -def main(): # pragma: no cover - # target course - cx = np.arange(0, 50, 0.1) - cy = [math.sin(ix / 5.0) * ix / 2.0 for ix in cx] - - target_speed = 5.0 / 3.6 - - T = 15.0 # max simulation time - - state = unicycle_model.State(x=-0.0, y=-3.0, yaw=0.0, v=0.0) - # state = unicycle_model.State(x=-1.0, y=-5.0, yaw=0.0, v=-30.0 / 3.6) - # state = unicycle_model.State(x=10.0, y=5.0, yaw=0.0, v=-30.0 / 3.6) - # state = unicycle_model.State( - # x=3.0, y=5.0, yaw=np.deg2rad(-40.0), v=-10.0 / 3.6) - # state = unicycle_model.State( - # x=3.0, y=5.0, yaw=np.deg2rad(40.0), v=50.0 / 3.6) - - lastIndex = len(cx) - 1 - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - target_ind, dis = calc_target_index(state, cx, cy) - - while T >= time and lastIndex > target_ind: - ai = PIDControl(target_speed, state.v) - di, target_ind, _ = pure_pursuit_control(state, cx, cy, target_ind) - state = unicycle_model.update(state, ai, di) - - time = time + unicycle_model.dt - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - - # plt.cla() - # plt.plot(cx, cy, ".r", label="course") - # plt.plot(x, y, "-b", label="trajectory") - # plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - # plt.axis("equal") - # plt.grid(True) - # plt.pause(0.1) - # input() - - plt.subplots(1) - plt.plot(cx, cy, ".r", label="course") - plt.plot(x, y, "-b", label="trajectory") - plt.legend() - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.axis("equal") - plt.grid(True) - - plt.subplots(1) - plt.plot(t, [iv * 3.6 for iv in v], "-r") - plt.xlabel("Time[s]") - plt.ylabel("Speed[km/h]") - plt.grid(True) - plt.show() - - -if __name__ == '__main__': # pragma: no cover - print("Pure pursuit path tracking simulation start") - main() diff --git a/PathPlanning/ClosedLoopRRTStar/unicycle_model.py b/PathPlanning/ClosedLoopRRTStar/unicycle_model.py deleted file mode 100644 index c05f76c84e..0000000000 --- a/PathPlanning/ClosedLoopRRTStar/unicycle_model.py +++ /dev/null @@ -1,80 +0,0 @@ -""" - -Unicycle model class - -author Atsushi Sakai - -""" - -import math -import numpy as np -from utils.angle import angle_mod - -dt = 0.05 # [s] -L = 0.9 # [m] -steer_max = np.deg2rad(40.0) -curvature_max = math.tan(steer_max) / L -curvature_max = 1.0 / curvature_max + 1.0 - -accel_max = 5.0 - - -class State: - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - - -def update(state, a, delta): - - state.x = state.x + state.v * math.cos(state.yaw) * dt - state.y = state.y + state.v * math.sin(state.yaw) * dt - state.yaw = state.yaw + state.v / L * math.tan(delta) * dt - state.yaw = pi_2_pi(state.yaw) - state.v = state.v + a * dt - - return state - - -def pi_2_pi(angle): - return angle_mod(angle) - - -if __name__ == '__main__': # pragma: no cover - print("start unicycle simulation") - import matplotlib.pyplot as plt - - T = 100 - a = [1.0] * T - delta = [np.deg2rad(1.0)] * T - # print(delta) - # print(a, delta) - - state = State() - - x = [] - y = [] - yaw = [] - v = [] - - for (ai, di) in zip(a, delta): - state = update(state, ai, di) - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - - plt.subplots(1) - plt.plot(x, y) - plt.axis("equal") - plt.grid(True) - - plt.subplots(1) - plt.plot(v) - plt.grid(True) - - plt.show() diff --git a/PathPlanning/ClothoidPath/clothoid_path_planner.py b/PathPlanning/ClothoidPath/clothoid_path_planner.py deleted file mode 100644 index 5e5fc6e9a3..0000000000 --- a/PathPlanning/ClothoidPath/clothoid_path_planner.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Clothoid Path Planner -Author: Daniel Ingram (daniel-s-ingram) - Atsushi Sakai (AtsushiSakai) -Reference paper: Fast and accurate G1 fitting of clothoid curves -https://www.researchgate.net/publication/237062806 -""" - -from collections import namedtuple -import matplotlib.pyplot as plt -import numpy as np -import scipy.integrate as integrate -from scipy.optimize import fsolve -from math import atan2, cos, hypot, pi, sin -from matplotlib import animation - -Point = namedtuple("Point", ["x", "y"]) - -show_animation = True - - -def generate_clothoid_paths(start_point, start_yaw_list, - goal_point, goal_yaw_list, - n_path_points): - """ - Generate clothoid path list. This function generate multiple clothoid paths - from multiple orientations(yaw) at start points to multiple orientations - (yaw) at goal point. - - :param start_point: Start point of the path - :param start_yaw_list: Orientation list at start point in radian - :param goal_point: Goal point of the path - :param goal_yaw_list: Orientation list at goal point in radian - :param n_path_points: number of path points - :return: clothoid path list - """ - clothoids = [] - for start_yaw in start_yaw_list: - for goal_yaw in goal_yaw_list: - clothoid = generate_clothoid_path(start_point, start_yaw, - goal_point, goal_yaw, - n_path_points) - clothoids.append(clothoid) - return clothoids - - -def generate_clothoid_path(start_point, start_yaw, - goal_point, goal_yaw, n_path_points): - """ - Generate a clothoid path list. - - :param start_point: Start point of the path - :param start_yaw: Orientation at start point in radian - :param goal_point: Goal point of the path - :param goal_yaw: Orientation at goal point in radian - :param n_path_points: number of path points - :return: a clothoid path - """ - dx = goal_point.x - start_point.x - dy = goal_point.y - start_point.y - r = hypot(dx, dy) - - phi = atan2(dy, dx) - phi1 = normalize_angle(start_yaw - phi) - phi2 = normalize_angle(goal_yaw - phi) - delta = phi2 - phi1 - - try: - # Step1: Solve g function - A = solve_g_for_root(phi1, phi2, delta) - - # Step2: Calculate path parameters - L = compute_path_length(r, phi1, delta, A) - curvature = compute_curvature(delta, A, L) - curvature_rate = compute_curvature_rate(A, L) - except Exception as e: - print(f"Failed to generate clothoid points: {e}") - return None - - # Step3: Construct a path with Fresnel integral - points = [] - for s in np.linspace(0, L, n_path_points): - try: - x = start_point.x + s * X(curvature_rate * s ** 2, curvature * s, - start_yaw) - y = start_point.y + s * Y(curvature_rate * s ** 2, curvature * s, - start_yaw) - points.append(Point(x, y)) - except Exception as e: - print(f"Skipping failed clothoid point: {e}") - - return points - - -def X(a, b, c): - return integrate.quad(lambda t: cos((a/2)*t**2 + b*t + c), 0, 1)[0] - - -def Y(a, b, c): - return integrate.quad(lambda t: sin((a/2)*t**2 + b*t + c), 0, 1)[0] - - -def solve_g_for_root(theta1, theta2, delta): - initial_guess = 3*(theta1 + theta2) - return fsolve(lambda A: Y(2*A, delta - A, theta1), [initial_guess]) - - -def compute_path_length(r, theta1, delta, A): - return r / X(2*A, delta - A, theta1) - - -def compute_curvature(delta, A, L): - return (delta - A) / L - - -def compute_curvature_rate(A, L): - return 2 * A / (L**2) - - -def normalize_angle(angle_rad): - return (angle_rad + pi) % (2 * pi) - pi - - -def get_axes_limits(clothoids): - x_vals = [p.x for clothoid in clothoids for p in clothoid] - y_vals = [p.y for clothoid in clothoids for p in clothoid] - - x_min = min(x_vals) - x_max = max(x_vals) - y_min = min(y_vals) - y_max = max(y_vals) - - x_offset = 0.1*(x_max - x_min) - y_offset = 0.1*(y_max - y_min) - - x_min = x_min - x_offset - x_max = x_max + x_offset - y_min = y_min - y_offset - y_max = y_max + y_offset - - return x_min, x_max, y_min, y_max - - -def draw_clothoids(start, goal, num_steps, clothoidal_paths, - save_animation=False): - - fig = plt.figure(figsize=(10, 10)) - x_min, x_max, y_min, y_max = get_axes_limits(clothoidal_paths) - axes = plt.axes(xlim=(x_min, x_max), ylim=(y_min, y_max)) - - axes.plot(start.x, start.y, 'ro') - axes.plot(goal.x, goal.y, 'ro') - lines = [axes.plot([], [], 'b-')[0] for _ in range(len(clothoidal_paths))] - - def animate(i): - for line, clothoid_path in zip(lines, clothoidal_paths): - x = [p.x for p in clothoid_path[:i]] - y = [p.y for p in clothoid_path[:i]] - line.set_data(x, y) - - return lines - - anim = animation.FuncAnimation( - fig, - animate, - frames=num_steps, - interval=25, - blit=True - ) - if save_animation: - anim.save('clothoid.gif', fps=30, writer="imagemagick") - plt.show() - - -def main(): - start_point = Point(0, 0) - start_orientation_list = [0.0] - goal_point = Point(10, 0) - goal_orientation_list = np.linspace(-pi, pi, 75) - num_path_points = 100 - clothoid_paths = generate_clothoid_paths( - start_point, start_orientation_list, - goal_point, goal_orientation_list, - num_path_points) - if show_animation: - draw_clothoids(start_point, goal_point, - num_path_points, clothoid_paths, - save_animation=False) - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/CubicSpline/__init__.py b/PathPlanning/CubicSpline/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/CubicSpline/cubic_spline_planner.py b/PathPlanning/CubicSpline/cubic_spline_planner.py deleted file mode 100644 index 2391f67c39..0000000000 --- a/PathPlanning/CubicSpline/cubic_spline_planner.py +++ /dev/null @@ -1,455 +0,0 @@ -""" -Cubic spline planner - -Author: Atsushi Sakai(@Atsushi_twi) - -""" -import math -import numpy as np -import bisect - - -class CubicSpline1D: - """ - 1D Cubic Spline class - - Parameters - ---------- - x : list - x coordinates for data points. This x coordinates must be - sorted - in ascending order. - y : list - y coordinates for data points - - Examples - -------- - You can interpolate 1D data points. - - >>> import numpy as np - >>> import matplotlib.pyplot as plt - >>> x = np.arange(5) - >>> y = [1.7, -6, 5, 6.5, 0.0] - >>> sp = CubicSpline1D(x, y) - >>> xi = np.linspace(0.0, 5.0) - >>> yi = [sp.calc_position(x) for x in xi] - >>> plt.plot(x, y, "xb", label="Data points") - >>> plt.plot(xi, yi , "r", label="Cubic spline interpolation") - >>> plt.grid(True) - >>> plt.legend() - >>> plt.show() - - .. image:: cubic_spline_1d.png - - """ - - def __init__(self, x, y): - - h = np.diff(x) - if np.any(h < 0): - raise ValueError("x coordinates must be sorted in ascending order") - - self.a, self.b, self.c, self.d = [], [], [], [] - self.x = x - self.y = y - self.nx = len(x) # dimension of x - - # calc coefficient a - self.a = [iy for iy in y] - - # calc coefficient c - A = self.__calc_A(h) - B = self.__calc_B(h, self.a) - self.c = np.linalg.solve(A, B) - - # calc spline coefficient b and d - for i in range(self.nx - 1): - d = (self.c[i + 1] - self.c[i]) / (3.0 * h[i]) - b = 1.0 / h[i] * (self.a[i + 1] - self.a[i]) \ - - h[i] / 3.0 * (2.0 * self.c[i] + self.c[i + 1]) - self.d.append(d) - self.b.append(b) - - def calc_position(self, x): - """ - Calc `y` position for given `x`. - - if `x` is outside the data point's `x` range, return None. - - Parameters - ---------- - x : float - x position to calculate y. - - Returns - ------- - y : float - y position for given x. - """ - if x < self.x[0]: - return None - elif x > self.x[-1]: - return None - - i = self.__search_index(x) - dx = x - self.x[i] - position = self.a[i] + self.b[i] * dx + \ - self.c[i] * dx ** 2.0 + self.d[i] * dx ** 3.0 - - return position - - def calc_first_derivative(self, x): - """ - Calc first derivative at given x. - - if x is outside the input x, return None - - Parameters - ---------- - x : float - x position to calculate first derivative. - - Returns - ------- - dy : float - first derivative for given x. - """ - - if x < self.x[0]: - return None - elif x > self.x[-1]: - return None - - i = self.__search_index(x) - dx = x - self.x[i] - dy = self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx ** 2.0 - return dy - - def calc_second_derivative(self, x): - """ - Calc second derivative at given x. - - if x is outside the input x, return None - - Parameters - ---------- - x : float - x position to calculate second derivative. - - Returns - ------- - ddy : float - second derivative for given x. - """ - - if x < self.x[0]: - return None - elif x > self.x[-1]: - return None - - i = self.__search_index(x) - dx = x - self.x[i] - ddy = 2.0 * self.c[i] + 6.0 * self.d[i] * dx - return ddy - - def calc_third_derivative(self, x): - """ - Calc third derivative at given x. - - if x is outside the input x, return None - - Parameters - ---------- - x : float - x position to calculate third derivative. - - Returns - ------- - dddy : float - third derivative for given x. - """ - if x < self.x[0]: - return None - elif x > self.x[-1]: - return None - - i = self.__search_index(x) - dddy = 6.0 * self.d[i] - return dddy - - def __search_index(self, x): - """ - search data segment index - """ - return bisect.bisect(self.x, x) - 1 - - def __calc_A(self, h): - """ - calc matrix A for spline coefficient c - """ - A = np.zeros((self.nx, self.nx)) - A[0, 0] = 1.0 - for i in range(self.nx - 1): - if i != (self.nx - 2): - A[i + 1, i + 1] = 2.0 * (h[i] + h[i + 1]) - A[i + 1, i] = h[i] - A[i, i + 1] = h[i] - - A[0, 1] = 0.0 - A[self.nx - 1, self.nx - 2] = 0.0 - A[self.nx - 1, self.nx - 1] = 1.0 - return A - - def __calc_B(self, h, a): - """ - calc matrix B for spline coefficient c - """ - B = np.zeros(self.nx) - for i in range(self.nx - 2): - B[i + 1] = 3.0 * (a[i + 2] - a[i + 1]) / h[i + 1]\ - - 3.0 * (a[i + 1] - a[i]) / h[i] - return B - - -class CubicSpline2D: - """ - Cubic CubicSpline2D class - - Parameters - ---------- - x : list - x coordinates for data points. - y : list - y coordinates for data points. - - Examples - -------- - You can interpolate a 2D data points. - - >>> import matplotlib.pyplot as plt - >>> x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] - >>> y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0] - >>> ds = 0.1 # [m] distance of each interpolated points - >>> sp = CubicSpline2D(x, y) - >>> s = np.arange(0, sp.s[-1], ds) - >>> rx, ry, ryaw, rk = [], [], [], [] - >>> for i_s in s: - ... ix, iy = sp.calc_position(i_s) - ... rx.append(ix) - ... ry.append(iy) - ... ryaw.append(sp.calc_yaw(i_s)) - ... rk.append(sp.calc_curvature(i_s)) - >>> plt.subplots(1) - >>> plt.plot(x, y, "xb", label="Data points") - >>> plt.plot(rx, ry, "-r", label="Cubic spline path") - >>> plt.grid(True) - >>> plt.axis("equal") - >>> plt.xlabel("x[m]") - >>> plt.ylabel("y[m]") - >>> plt.legend() - >>> plt.show() - - .. image:: cubic_spline_2d_path.png - - >>> plt.subplots(1) - >>> plt.plot(s, [np.rad2deg(iyaw) for iyaw in ryaw], "-r", label="yaw") - >>> plt.grid(True) - >>> plt.legend() - >>> plt.xlabel("line length[m]") - >>> plt.ylabel("yaw angle[deg]") - - .. image:: cubic_spline_2d_yaw.png - - >>> plt.subplots(1) - >>> plt.plot(s, rk, "-r", label="curvature") - >>> plt.grid(True) - >>> plt.legend() - >>> plt.xlabel("line length[m]") - >>> plt.ylabel("curvature [1/m]") - - .. image:: cubic_spline_2d_curvature.png - """ - - def __init__(self, x, y): - self.s = self.__calc_s(x, y) - self.sx = CubicSpline1D(self.s, x) - self.sy = CubicSpline1D(self.s, y) - - def __calc_s(self, x, y): - dx = np.diff(x) - dy = np.diff(y) - self.ds = np.hypot(dx, dy) - s = [0] - s.extend(np.cumsum(self.ds)) - return s - - def calc_position(self, s): - """ - calc position - - Parameters - ---------- - s : float - distance from the start point. if `s` is outside the data point's - range, return None. - - Returns - ------- - x : float - x position for given s. - y : float - y position for given s. - """ - x = self.sx.calc_position(s) - y = self.sy.calc_position(s) - - return x, y - - def calc_curvature(self, s): - """ - calc curvature - - Parameters - ---------- - s : float - distance from the start point. if `s` is outside the data point's - range, return None. - - Returns - ------- - k : float - curvature for given s. - """ - dx = self.sx.calc_first_derivative(s) - ddx = self.sx.calc_second_derivative(s) - dy = self.sy.calc_first_derivative(s) - ddy = self.sy.calc_second_derivative(s) - k = (ddy * dx - ddx * dy) / ((dx ** 2 + dy ** 2)**(3 / 2)) - return k - - def calc_curvature_rate(self, s): - """ - calc curvature rate - - Parameters - ---------- - s : float - distance from the start point. if `s` is outside the data point's - range, return None. - - Returns - ------- - k : float - curvature rate for given s. - """ - dx = self.sx.calc_first_derivative(s) - dy = self.sy.calc_first_derivative(s) - ddx = self.sx.calc_second_derivative(s) - ddy = self.sy.calc_second_derivative(s) - dddx = self.sx.calc_third_derivative(s) - dddy = self.sy.calc_third_derivative(s) - a = dx * ddy - dy * ddx - b = dx * dddy - dy * dddx - c = dx * ddx + dy * ddy - d = dx * dx + dy * dy - return (b * d - 3.0 * a * c) / (d * d * d) - - def calc_yaw(self, s): - """ - calc yaw - - Parameters - ---------- - s : float - distance from the start point. if `s` is outside the data point's - range, return None. - - Returns - ------- - yaw : float - yaw angle (tangent vector) for given s. - """ - dx = self.sx.calc_first_derivative(s) - dy = self.sy.calc_first_derivative(s) - yaw = math.atan2(dy, dx) - return yaw - - -def calc_spline_course(x, y, ds=0.1): - sp = CubicSpline2D(x, y) - s = list(np.arange(0, sp.s[-1], ds)) - - rx, ry, ryaw, rk = [], [], [], [] - for i_s in s: - ix, iy = sp.calc_position(i_s) - rx.append(ix) - ry.append(iy) - ryaw.append(sp.calc_yaw(i_s)) - rk.append(sp.calc_curvature(i_s)) - - return rx, ry, ryaw, rk, s - - -def main_1d(): - print("CubicSpline1D test") - import matplotlib.pyplot as plt - x = np.arange(5) - y = [1.7, -6, 5, 6.5, 0.0] - sp = CubicSpline1D(x, y) - xi = np.linspace(0.0, 5.0) - - plt.plot(x, y, "xb", label="Data points") - plt.plot(xi, [sp.calc_position(x) for x in xi], "r", - label="Cubic spline interpolation") - plt.grid(True) - plt.legend() - plt.show() - - -def main_2d(): # pragma: no cover - print("CubicSpline1D 2D test") - import matplotlib.pyplot as plt - x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] - y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0] - ds = 0.1 # [m] distance of each interpolated points - - sp = CubicSpline2D(x, y) - s = np.arange(0, sp.s[-1], ds) - - rx, ry, ryaw, rk = [], [], [], [] - for i_s in s: - ix, iy = sp.calc_position(i_s) - rx.append(ix) - ry.append(iy) - ryaw.append(sp.calc_yaw(i_s)) - rk.append(sp.calc_curvature(i_s)) - - plt.subplots(1) - plt.plot(x, y, "xb", label="Data points") - plt.plot(rx, ry, "-r", label="Cubic spline path") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - - plt.subplots(1) - plt.plot(s, [np.rad2deg(iyaw) for iyaw in ryaw], "-r", label="yaw") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("yaw angle[deg]") - - plt.subplots(1) - plt.plot(s, rk, "-r", label="curvature") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("curvature [1/m]") - - plt.show() - - -if __name__ == '__main__': - # main_1d() - main_2d() diff --git a/PathPlanning/CubicSpline/spline_continuity.py b/PathPlanning/CubicSpline/spline_continuity.py deleted file mode 100644 index ea85b37f7c..0000000000 --- a/PathPlanning/CubicSpline/spline_continuity.py +++ /dev/null @@ -1,55 +0,0 @@ - -import numpy as np -import matplotlib.pyplot as plt -from scipy import interpolate - - -class Spline2D: - - def __init__(self, x, y, kind="cubic"): - self.s = self.__calc_s(x, y) - self.sx = interpolate.interp1d(self.s, x, kind=kind) - self.sy = interpolate.interp1d(self.s, y, kind=kind) - - def __calc_s(self, x, y): - self.ds = np.hypot(np.diff(x), np.diff(y)) - s = [0.0] - s.extend(np.cumsum(self.ds)) - return s - - def calc_position(self, s): - x = self.sx(s) - y = self.sy(s) - return x, y - - -def main(): - x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] - y = [0.7, -6, -5, -3.5, 0.0, 5.0, -2.0] - ds = 0.1 # [m] distance of each interpolated points - - plt.subplots(1) - plt.plot(x, y, "xb", label="Data points") - - for (kind, label) in [("linear", "C0 (Linear spline)"), - ("quadratic", "C0 & C1 (Quadratic spline)"), - ("cubic", "C0 & C1 & C2 (Cubic spline)")]: - rx, ry = [], [] - sp = Spline2D(x, y, kind=kind) - s = np.arange(0, sp.s[-1], ds) - for i_s in s: - ix, iy = sp.calc_position(i_s) - rx.append(ix) - ry.append(iy) - plt.plot(rx, ry, "-", label=label) - - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/DStar/dstar.py b/PathPlanning/DStar/dstar.py deleted file mode 100644 index b62b939f54..0000000000 --- a/PathPlanning/DStar/dstar.py +++ /dev/null @@ -1,253 +0,0 @@ -""" - -D* grid planning - -author: Nirnay Roy - -See Wikipedia article (https://en.wikipedia.org/wiki/D*) - -""" -import math - - -from sys import maxsize - -import matplotlib.pyplot as plt - -show_animation = True - - -class State: - - def __init__(self, x, y): - self.x = x - self.y = y - self.parent = None - self.state = "." - self.t = "new" # tag for state - self.h = 0 - self.k = 0 - - def cost(self, state): - if self.state == "#" or state.state == "#": - return maxsize - - return math.sqrt(math.pow((self.x - state.x), 2) + - math.pow((self.y - state.y), 2)) - - def set_state(self, state): - """ - .: new - #: obstacle - e: oparent of current state - *: closed state - s: current state - """ - if state not in ["s", ".", "#", "e", "*"]: - return - self.state = state - - -class Map: - - def __init__(self, row, col): - self.row = row - self.col = col - self.map = self.init_map() - - def init_map(self): - map_list = [] - for i in range(self.row): - tmp = [] - for j in range(self.col): - tmp.append(State(i, j)) - map_list.append(tmp) - return map_list - - def get_neighbors(self, state): - state_list = [] - for i in [-1, 0, 1]: - for j in [-1, 0, 1]: - if i == 0 and j == 0: - continue - if state.x + i < 0 or state.x + i >= self.row: - continue - if state.y + j < 0 or state.y + j >= self.col: - continue - state_list.append(self.map[state.x + i][state.y + j]) - return state_list - - def set_obstacle(self, point_list): - for x, y in point_list: - if x < 0 or x >= self.row or y < 0 or y >= self.col: - continue - - self.map[x][y].set_state("#") - - -class Dstar: - def __init__(self, maps): - self.map = maps - self.open_list = set() - - def process_state(self): - x = self.min_state() - - if x is None: - return -1 - - k_old = self.get_kmin() - self.remove(x) - - if k_old < x.h: - for y in self.map.get_neighbors(x): - if y.h <= k_old and x.h > y.h + x.cost(y): - x.parent = y - x.h = y.h + x.cost(y) - if k_old == x.h: - for y in self.map.get_neighbors(x): - if y.t == "new" or y.parent == x and y.h != x.h + x.cost(y) \ - or y.parent != x and y.h > x.h + x.cost(y): - y.parent = x - self.insert(y, x.h + x.cost(y)) - else: - for y in self.map.get_neighbors(x): - if y.t == "new" or y.parent == x and y.h != x.h + x.cost(y): - y.parent = x - self.insert(y, x.h + x.cost(y)) - else: - if y.parent != x and y.h > x.h + x.cost(y): - self.insert(x, x.h) - else: - if y.parent != x and x.h > y.h + x.cost(y) \ - and y.t == "close" and y.h > k_old: - self.insert(y, y.h) - return self.get_kmin() - - def min_state(self): - if not self.open_list: - return None - min_state = min(self.open_list, key=lambda x: x.k) - return min_state - - def get_kmin(self): - if not self.open_list: - return -1 - k_min = min([x.k for x in self.open_list]) - return k_min - - def insert(self, state, h_new): - if state.t == "new": - state.k = h_new - elif state.t == "open": - state.k = min(state.k, h_new) - elif state.t == "close": - state.k = min(state.h, h_new) - state.h = h_new - state.t = "open" - self.open_list.add(state) - - def remove(self, state): - if state.t == "open": - state.t = "close" - self.open_list.remove(state) - - def modify_cost(self, x): - if x.t == "close": - self.insert(x, x.parent.h + x.cost(x.parent)) - - def run(self, start, end): - - rx = [] - ry = [] - - self.insert(end, 0.0) - - while True: - self.process_state() - if start.t == "close": - break - - start.set_state("s") - s = start - s = s.parent - s.set_state("e") - tmp = start - - AddNewObstacle(self.map) # add new obstacle after the first search finished - - while tmp != end: - tmp.set_state("*") - rx.append(tmp.x) - ry.append(tmp.y) - if show_animation: - plt.plot(rx, ry, "-r") - plt.pause(0.01) - if tmp.parent.state == "#": - self.modify(tmp) - continue - tmp = tmp.parent - tmp.set_state("e") - - return rx, ry - - def modify(self, state): - self.modify_cost(state) - while True: - k_min = self.process_state() - if k_min >= state.h: - break - -def AddNewObstacle(map:Map): - ox, oy = [], [] - for i in range(5, 21): - ox.append(i) - oy.append(40) - map.set_obstacle([(i, j) for i, j in zip(ox, oy)]) - if show_animation: - plt.pause(0.001) - plt.plot(ox, oy, ".g") - -def main(): - m = Map(100, 100) - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10) - for i in range(-10, 60): - ox.append(60) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60) - for i in range(-10, 61): - ox.append(-10) - oy.append(i) - for i in range(-10, 40): - ox.append(20) - oy.append(i) - for i in range(0, 40): - ox.append(40) - oy.append(60 - i) - m.set_obstacle([(i, j) for i, j in zip(ox, oy)]) - - start = [10, 10] - goal = [50, 50] - if show_animation: - plt.plot(ox, oy, ".k") - plt.plot(start[0], start[1], "og") - plt.plot(goal[0], goal[1], "xb") - plt.axis("equal") - - start = m.map[start[0]][start[1]] - end = m.map[goal[0]][goal[1]] - dstar = Dstar(m) - rx, ry = dstar.run(start, end) - - if show_animation: - plt.plot(rx, ry, "-r") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/DStarLite/d_star_lite.py b/PathPlanning/DStarLite/d_star_lite.py deleted file mode 100644 index 1a44d84fa5..0000000000 --- a/PathPlanning/DStarLite/d_star_lite.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -D* Lite grid planning -author: vss2sn (28676655+vss2sn@users.noreply.github.com) -Link to papers: -D* Lite (Link: http://idm-lab.org/bib/abstracts/papers/aaai02b.pdf) -Improved Fast Replanning for Robot Navigation in Unknown Terrain -(Link: http://www.cs.cmu.edu/~maxim/files/dlite_icra02.pdf) -Implemented maintaining similarity with the pseudocode for understanding. -Code can be significantly optimized by using a priority queue for U, etc. -Avoiding additional imports based on repository philosophy. -""" -import math -import matplotlib.pyplot as plt -import random -import numpy as np - -show_animation = True -pause_time = 0.001 -p_create_random_obstacle = 0 - - -class Node: - def __init__(self, x: int = 0, y: int = 0, cost: float = 0.0): - self.x = x - self.y = y - self.cost = cost - - -def add_coordinates(node1: Node, node2: Node): - new_node = Node() - new_node.x = node1.x + node2.x - new_node.y = node1.y + node2.y - new_node.cost = node1.cost + node2.cost - return new_node - - -def compare_coordinates(node1: Node, node2: Node): - return node1.x == node2.x and node1.y == node2.y - - -class DStarLite: - - # Please adjust the heuristic function (h) if you change the list of - # possible motions - motions = [ - Node(1, 0, 1), - Node(0, 1, 1), - Node(-1, 0, 1), - Node(0, -1, 1), - Node(1, 1, math.sqrt(2)), - Node(1, -1, math.sqrt(2)), - Node(-1, 1, math.sqrt(2)), - Node(-1, -1, math.sqrt(2)) - ] - - def __init__(self, ox: list, oy: list): - # Ensure that within the algorithm implementation all node coordinates - # are indices in the grid and extend - # from 0 to abs(_max - _min) - self.x_min_world = int(min(ox)) - self.y_min_world = int(min(oy)) - self.x_max = int(abs(max(ox) - self.x_min_world)) - self.y_max = int(abs(max(oy) - self.y_min_world)) - self.obstacles = [Node(x - self.x_min_world, y - self.y_min_world) - for x, y in zip(ox, oy)] - self.obstacles_xy = {(obstacle.x, obstacle.y) for obstacle in self.obstacles} - self.start = Node(0, 0) - self.goal = Node(0, 0) - self.U = list() # type: ignore - self.km = 0.0 - self.kold = 0.0 - self.rhs = self.create_grid(float("inf")) - self.g = self.create_grid(float("inf")) - self.detected_obstacles_xy: set[tuple[int, int]] = set() - self.xy = np.empty((0, 2)) - if show_animation: - self.detected_obstacles_for_plotting_x = list() # type: ignore - self.detected_obstacles_for_plotting_y = list() # type: ignore - self.initialized = False - - def create_grid(self, val: float): - return np.full((self.x_max, self.y_max), val) - - def is_obstacle(self, node: Node): - is_in_obstacles = (node.x, node.y) in self.obstacles_xy - is_in_detected_obstacles = (node.x, node.y) in self.detected_obstacles_xy - return is_in_obstacles or is_in_detected_obstacles - - def c(self, node1: Node, node2: Node): - if self.is_obstacle(node2): - # Attempting to move from or to an obstacle - return math.inf - new_node = Node(node1.x-node2.x, node1.y-node2.y) - detected_motion = list(filter(lambda motion: - compare_coordinates(motion, new_node), - self.motions)) - return detected_motion[0].cost - - def h(self, s: Node): - # Cannot use the 2nd euclidean norm as this might sometimes generate - # heuristics that overestimate the cost, making them inadmissible, - # due to rounding errors etc (when combined with calculate_key) - # To be admissible heuristic should - # never overestimate the cost of a move - # hence not using the line below - # return math.hypot(self.start.x - s.x, self.start.y - s.y) - - # Below is the same as 1; modify if you modify the cost of each move in - # motion - # return max(abs(self.start.x - s.x), abs(self.start.y - s.y)) - return 1 - - def calculate_key(self, s: Node): - return (min(self.g[s.x][s.y], self.rhs[s.x][s.y]) + self.h(s) - + self.km, min(self.g[s.x][s.y], self.rhs[s.x][s.y])) - - def is_valid(self, node: Node): - if 0 <= node.x < self.x_max and 0 <= node.y < self.y_max: - return True - return False - - def get_neighbours(self, u: Node): - return [add_coordinates(u, motion) for motion in self.motions - if self.is_valid(add_coordinates(u, motion))] - - def pred(self, u: Node): - # Grid, so each vertex is connected to the ones around it - return self.get_neighbours(u) - - def succ(self, u: Node): - # Grid, so each vertex is connected to the ones around it - return self.get_neighbours(u) - - def initialize(self, start: Node, goal: Node): - self.start.x = start.x - self.x_min_world - self.start.y = start.y - self.y_min_world - self.goal.x = goal.x - self.x_min_world - self.goal.y = goal.y - self.y_min_world - if not self.initialized: - self.initialized = True - print('Initializing') - self.U = list() # Would normally be a priority queue - self.km = 0.0 - self.rhs = self.create_grid(math.inf) - self.g = self.create_grid(math.inf) - self.rhs[self.goal.x][self.goal.y] = 0 - self.U.append((self.goal, self.calculate_key(self.goal))) - self.detected_obstacles_xy = set() - - def update_vertex(self, u: Node): - if not compare_coordinates(u, self.goal): - self.rhs[u.x][u.y] = min([self.c(u, sprime) + - self.g[sprime.x][sprime.y] - for sprime in self.succ(u)]) - if any([compare_coordinates(u, node) for node, key in self.U]): - self.U = [(node, key) for node, key in self.U - if not compare_coordinates(node, u)] - self.U.sort(key=lambda x: x[1]) - if self.g[u.x][u.y] != self.rhs[u.x][u.y]: - self.U.append((u, self.calculate_key(u))) - self.U.sort(key=lambda x: x[1]) - - def compare_keys(self, key_pair1: tuple[float, float], - key_pair2: tuple[float, float]): - return key_pair1[0] < key_pair2[0] or \ - (key_pair1[0] == key_pair2[0] and key_pair1[1] < key_pair2[1]) - - def compute_shortest_path(self): - self.U.sort(key=lambda x: x[1]) - has_elements = len(self.U) > 0 - start_key_not_updated = self.compare_keys( - self.U[0][1], self.calculate_key(self.start) - ) - rhs_not_equal_to_g = self.rhs[self.start.x][self.start.y] != \ - self.g[self.start.x][self.start.y] - while has_elements and start_key_not_updated or rhs_not_equal_to_g: - self.kold = self.U[0][1] - u = self.U[0][0] - self.U.pop(0) - if self.compare_keys(self.kold, self.calculate_key(u)): - self.U.append((u, self.calculate_key(u))) - self.U.sort(key=lambda x: x[1]) - elif (self.g[u.x, u.y] > self.rhs[u.x, u.y]).any(): - self.g[u.x, u.y] = self.rhs[u.x, u.y] - for s in self.pred(u): - self.update_vertex(s) - else: - self.g[u.x, u.y] = math.inf - for s in self.pred(u) + [u]: - self.update_vertex(s) - self.U.sort(key=lambda x: x[1]) - start_key_not_updated = self.compare_keys( - self.U[0][1], self.calculate_key(self.start) - ) - rhs_not_equal_to_g = self.rhs[self.start.x][self.start.y] != \ - self.g[self.start.x][self.start.y] - - def detect_changes(self): - changed_vertices = list() - if len(self.spoofed_obstacles) > 0: - for spoofed_obstacle in self.spoofed_obstacles[0]: - if compare_coordinates(spoofed_obstacle, self.start) or \ - compare_coordinates(spoofed_obstacle, self.goal): - continue - changed_vertices.append(spoofed_obstacle) - self.detected_obstacles_xy.add((spoofed_obstacle.x, spoofed_obstacle.y)) - if show_animation: - self.detected_obstacles_for_plotting_x.append( - spoofed_obstacle.x + self.x_min_world) - self.detected_obstacles_for_plotting_y.append( - spoofed_obstacle.y + self.y_min_world) - plt.plot(self.detected_obstacles_for_plotting_x, - self.detected_obstacles_for_plotting_y, ".k") - plt.pause(pause_time) - self.spoofed_obstacles.pop(0) - - # Allows random generation of obstacles - random.seed() - if random.random() > 1 - p_create_random_obstacle: - x = random.randint(0, self.x_max - 1) - y = random.randint(0, self.y_max - 1) - new_obs = Node(x, y) - if compare_coordinates(new_obs, self.start) or \ - compare_coordinates(new_obs, self.goal): - return changed_vertices - changed_vertices.append(Node(x, y)) - self.detected_obstacles_xy.add((x, y)) - if show_animation: - self.detected_obstacles_for_plotting_x.append(x + - self.x_min_world) - self.detected_obstacles_for_plotting_y.append(y + - self.y_min_world) - plt.plot(self.detected_obstacles_for_plotting_x, - self.detected_obstacles_for_plotting_y, ".k") - plt.pause(pause_time) - return changed_vertices - - def compute_current_path(self): - path = list() - current_point = Node(self.start.x, self.start.y) - while not compare_coordinates(current_point, self.goal): - path.append(current_point) - current_point = min(self.succ(current_point), - key=lambda sprime: - self.c(current_point, sprime) + - self.g[sprime.x][sprime.y]) - path.append(self.goal) - return path - - def compare_paths(self, path1: list, path2: list): - if len(path1) != len(path2): - return False - for node1, node2 in zip(path1, path2): - if not compare_coordinates(node1, node2): - return False - return True - - def display_path(self, path: list, colour: str, alpha: float = 1.0): - px = [(node.x + self.x_min_world) for node in path] - py = [(node.y + self.y_min_world) for node in path] - drawing = plt.plot(px, py, colour, alpha=alpha) - plt.pause(pause_time) - return drawing - - def main(self, start: Node, goal: Node, - spoofed_ox: list, spoofed_oy: list): - self.spoofed_obstacles = [[Node(x - self.x_min_world, - y - self.y_min_world) - for x, y in zip(rowx, rowy)] - for rowx, rowy in zip(spoofed_ox, spoofed_oy) - ] - pathx = [] - pathy = [] - self.initialize(start, goal) - last = self.start - self.compute_shortest_path() - pathx.append(self.start.x + self.x_min_world) - pathy.append(self.start.y + self.y_min_world) - - if show_animation: - current_path = self.compute_current_path() - previous_path = current_path.copy() - previous_path_image = self.display_path(previous_path, ".c", - alpha=0.3) - current_path_image = self.display_path(current_path, ".c") - - while not compare_coordinates(self.goal, self.start): - if self.g[self.start.x][self.start.y] == math.inf: - print("No path possible") - return False, pathx, pathy - self.start = min(self.succ(self.start), - key=lambda sprime: - self.c(self.start, sprime) + - self.g[sprime.x][sprime.y]) - pathx.append(self.start.x + self.x_min_world) - pathy.append(self.start.y + self.y_min_world) - if show_animation: - current_path.pop(0) - plt.plot(pathx, pathy, "-r") - plt.pause(pause_time) - changed_vertices = self.detect_changes() - if len(changed_vertices) != 0: - print("New obstacle detected") - self.km += self.h(last) - last = self.start - for u in changed_vertices: - if compare_coordinates(u, self.start): - continue - self.rhs[u.x][u.y] = math.inf - self.g[u.x][u.y] = math.inf - self.update_vertex(u) - self.compute_shortest_path() - - if show_animation: - new_path = self.compute_current_path() - if not self.compare_paths(current_path, new_path): - current_path_image[0].remove() - previous_path_image[0].remove() - previous_path = current_path.copy() - current_path = new_path.copy() - previous_path_image = self.display_path(previous_path, - ".c", - alpha=0.3) - current_path_image = self.display_path(current_path, - ".c") - plt.pause(pause_time) - print("Path found") - return True, pathx, pathy - - -def main(): - - # start and goal position - sx = 10 # [m] - sy = 10 # [m] - gx = 50 # [m] - gy = 50 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - label_column = ['Start', 'Goal', 'Path taken', - 'Current computed path', 'Previous computed path', - 'Obstacles'] - columns = [plt.plot([], [], symbol, color=colour, alpha=alpha)[0] - for symbol, colour, alpha in [['o', 'g', 1], - ['x', 'b', 1], - ['-', 'r', 1], - ['.', 'c', 1], - ['.', 'c', 0.3], - ['.', 'k', 1]]] - plt.legend(columns, label_column, bbox_to_anchor=(1, 1), title="Key:", - fontsize="xx-small") - plt.plot() - plt.pause(pause_time) - - # Obstacles discovered at time = row - # time = 1, obstacles discovered at (0, 2), (9, 2), (4, 0) - # time = 2, obstacles discovered at (0, 1), (7, 7) - # ... - # when the spoofed obstacles are: - # spoofed_ox = [[0, 9, 4], [0, 7], [], [], [], [], [], [5]] - # spoofed_oy = [[2, 2, 0], [1, 7], [], [], [], [], [], [4]] - - # Reroute - # spoofed_ox = [[], [], [], [], [], [], [], [40 for _ in range(10, 21)]] - # spoofed_oy = [[], [], [], [], [], [], [], [i for i in range(10, 21)]] - - # Obstacles that demostrate large rerouting - spoofed_ox = [[], [], [], - [i for i in range(0, 21)] + [0 for _ in range(0, 20)]] - spoofed_oy = [[], [], [], - [20 for _ in range(0, 21)] + [i for i in range(0, 20)]] - - dstarlite = DStarLite(ox, oy) - dstarlite.main(Node(x=sx, y=sy), Node(x=gx, y=gy), - spoofed_ox=spoofed_ox, spoofed_oy=spoofed_oy) - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/DepthFirstSearch/depth_first_search.py b/PathPlanning/DepthFirstSearch/depth_first_search.py deleted file mode 100644 index 6922b8cbad..0000000000 --- a/PathPlanning/DepthFirstSearch/depth_first_search.py +++ /dev/null @@ -1,255 +0,0 @@ -""" - -Depth-First grid planning - -author: Erwin Lejeune (@spida_rwin) - -See Wikipedia article (https://en.wikipedia.org/wiki/Depth-first_search) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class DepthFirstSearchPlanner: - - def __init__(self, ox, oy, reso, rr): - """ - Initialize grid map for Depth-First planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.reso = reso - self.rr = rr - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, parent_index, parent): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index - self.parent = parent - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - Depth First search - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - nstart = self.Node(self.calc_xyindex(sx, self.minx), - self.calc_xyindex(sy, self.miny), 0.0, -1, None) - ngoal = self.Node(self.calc_xyindex(gx, self.minx), - self.calc_xyindex(gy, self.miny), 0.0, -1, None) - - open_set, closed_set = dict(), dict() - open_set[self.calc_grid_index(nstart)] = nstart - - while True: - if len(open_set) == 0: - print("Open set is empty..") - break - - current = open_set.pop(list(open_set.keys())[-1]) - c_id = self.calc_grid_index(current) - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current.x, self.minx), - self.calc_grid_position(current.y, self.miny), "xc") - # 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]) - plt.pause(0.01) - - if current.x == ngoal.x and current.y == ngoal.y: - print("Find goal") - ngoal.parent_index = current.parent_index - ngoal.cost = current.cost - break - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - node = self.Node(current.x + self.motion[i][0], - current.y + self.motion[i][1], - current.cost + self.motion[i][2], c_id, None) - n_id = self.calc_grid_index(node) - - # If the node is not safe, do nothing - if not self.verify_node(node): - continue - - if n_id not in closed_set: - open_set[n_id] = node - closed_set[n_id] = node - node.parent = current - - rx, ry = self.calc_final_path(ngoal, closed_set) - return rx, ry - - def calc_final_path(self, ngoal, closedset): - # generate final course - rx, ry = [self.calc_grid_position(ngoal.x, self.minx)], [ - self.calc_grid_position(ngoal.y, self.miny)] - n = closedset[ngoal.parent_index] - while n is not None: - rx.append(self.calc_grid_position(n.x, self.minx)) - ry.append(self.calc_grid_position(n.y, self.miny)) - n = n.parent - - return rx, ry - - def calc_grid_position(self, index, minp): - """ - calc grid position - - :param index: - :param minp: - :return: - """ - pos = index * self.reso + minp - return pos - - def calc_xyindex(self, position, min_pos): - return round((position - min_pos) / self.reso) - - def calc_grid_index(self, node): - return (node.y - self.miny) * self.xwidth + (node.x - self.minx) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.minx) - py = self.calc_grid_position(node.y, self.miny) - - if px < self.minx: - return False - elif py < self.miny: - return False - elif px >= self.maxx: - return False - elif py >= self.maxy: - return False - - # collision check - if self.obmap[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.minx = round(min(ox)) - self.miny = round(min(oy)) - self.maxx = round(max(ox)) - self.maxy = round(max(oy)) - print("min_x:", self.minx) - print("min_y:", self.miny) - print("max_x:", self.maxx) - print("max_y:", self.maxy) - - self.xwidth = round((self.maxx - self.minx) / self.reso) - self.ywidth = round((self.maxy - self.miny) / self.reso) - print("x_width:", self.xwidth) - print("y_width:", self.ywidth) - - # obstacle map generation - self.obmap = [[False for _ in range(self.ywidth)] - for _ in range(self.xwidth)] - for ix in range(self.xwidth): - x = self.calc_grid_position(ix, self.minx) - for iy in range(self.ywidth): - y = self.calc_grid_position(iy, self.miny) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obmap[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - - dfs = DepthFirstSearchPlanner(ox, oy, grid_size, robot_radius) - rx, ry = dfs.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/Dijkstra/dijkstra.py b/PathPlanning/Dijkstra/dijkstra.py deleted file mode 100644 index f5a4703910..0000000000 --- a/PathPlanning/Dijkstra/dijkstra.py +++ /dev/null @@ -1,259 +0,0 @@ -""" - -Grid based Dijkstra planning - -author: Atsushi Sakai(@Atsushi_twi) - -""" - -import matplotlib.pyplot as plt -import math - -show_animation = True - - -class DijkstraPlanner: - - def __init__(self, ox, oy, resolution, robot_radius): - """ - Initialize map for a star planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.min_x = None - self.min_y = None - self.max_x = None - self.max_y = None - self.x_width = None - self.y_width = None - self.obstacle_map = None - - self.resolution = resolution - self.robot_radius = robot_radius - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, parent_index): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.parent_index = parent_index # index of previous Node - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - def planning(self, sx, sy, gx, gy): - """ - dijkstra path search - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gx: goal x position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - start_node = self.Node(self.calc_xy_index(sx, self.min_x), - self.calc_xy_index(sy, self.min_y), 0.0, -1) - goal_node = self.Node(self.calc_xy_index(gx, self.min_x), - self.calc_xy_index(gy, self.min_y), 0.0, -1) - - open_set, closed_set = dict(), dict() - open_set[self.calc_index(start_node)] = start_node - - while True: - c_id = min(open_set, key=lambda o: open_set[o].cost) - current = open_set[c_id] - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_position(current.x, self.min_x), - self.calc_position(current.y, self.min_y), "xc") - # 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]) - if len(closed_set.keys()) % 10 == 0: - plt.pause(0.001) - - if current.x == goal_node.x and current.y == goal_node.y: - print("Find goal") - goal_node.parent_index = current.parent_index - goal_node.cost = current.cost - break - - # Remove the item from the open set - del open_set[c_id] - - # Add it to the closed set - closed_set[c_id] = current - - # expand search grid based on motion model - for move_x, move_y, move_cost in self.motion: - node = self.Node(current.x + move_x, - current.y + move_y, - current.cost + move_cost, c_id) - n_id = self.calc_index(node) - - if n_id in closed_set: - continue - - if not self.verify_node(node): - continue - - if n_id not in open_set: - open_set[n_id] = node # Discover a new node - else: - if open_set[n_id].cost >= node.cost: - # This path is the best until now. record it! - open_set[n_id] = node - - rx, ry = self.calc_final_path(goal_node, closed_set) - - return rx, ry - - def calc_final_path(self, goal_node, closed_set): - # generate final course - rx, ry = [self.calc_position(goal_node.x, self.min_x)], [ - self.calc_position(goal_node.y, self.min_y)] - parent_index = goal_node.parent_index - while parent_index != -1: - n = closed_set[parent_index] - rx.append(self.calc_position(n.x, self.min_x)) - ry.append(self.calc_position(n.y, self.min_y)) - parent_index = n.parent_index - - return rx, ry - - def calc_position(self, index, minp): - pos = index * self.resolution + minp - return pos - - def calc_xy_index(self, position, minp): - return round((position - minp) / self.resolution) - - def calc_index(self, node): - return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) - - def verify_node(self, node): - px = self.calc_position(node.x, self.min_x) - py = self.calc_position(node.y, self.min_y) - - if px < self.min_x: - return False - if py < self.min_y: - return False - if px >= self.max_x: - return False - if py >= self.max_y: - return False - - if self.obstacle_map[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.min_x = round(min(ox)) - self.min_y = round(min(oy)) - self.max_x = round(max(ox)) - self.max_y = round(max(oy)) - print("min_x:", self.min_x) - print("min_y:", self.min_y) - print("max_x:", self.max_x) - print("max_y:", self.max_y) - - self.x_width = round((self.max_x - self.min_x) / self.resolution) - self.y_width = round((self.max_y - self.min_y) / self.resolution) - print("x_width:", self.x_width) - print("y_width:", self.y_width) - - # obstacle map generation - self.obstacle_map = [[False for _ in range(self.y_width)] - for _ in range(self.x_width)] - for ix in range(self.x_width): - x = self.calc_position(ix, self.min_x) - for iy in range(self.y_width): - y = self.calc_position(iy, self.min_y) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.robot_radius: - self.obstacle_map[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = -5.0 # [m] - sy = -5.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(float(i)) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(float(i)) - for i in range(-10, 61): - ox.append(float(i)) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(float(i)) - for i in range(-10, 40): - ox.append(20.0) - oy.append(float(i)) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - - dijkstra = DijkstraPlanner(ox, oy, grid_size, robot_radius) - rx, ry = dijkstra.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/DubinsPath/__init__.py b/PathPlanning/DubinsPath/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/DubinsPath/dubins_path_planner.py b/PathPlanning/DubinsPath/dubins_path_planner.py deleted file mode 100644 index a7e8a100cc..0000000000 --- a/PathPlanning/DubinsPath/dubins_path_planner.py +++ /dev/null @@ -1,317 +0,0 @@ -""" - -Dubins path planner sample code - -author Atsushi Sakai(@Atsushi_twi) - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -from math import sin, cos, atan2, sqrt, acos, pi, hypot -import numpy as np -from utils.angle import angle_mod, rot_mat_2d - -show_animation = True - - -def plan_dubins_path(s_x, s_y, s_yaw, g_x, g_y, g_yaw, curvature, - step_size=0.1, selected_types=None): - """ - Plan dubins path - - Parameters - ---------- - s_x : float - x position of the start point [m] - s_y : float - y position of the start point [m] - s_yaw : float - yaw angle of the start point [rad] - g_x : float - x position of the goal point [m] - g_y : float - y position of the end point [m] - g_yaw : float - yaw angle of the end point [rad] - curvature : float - curvature for curve [1/m] - step_size : float (optional) - step size between two path points [m]. Default is 0.1 - selected_types : a list of string or None - selected path planning types. If None, all types are used for - path planning, and minimum path length result is returned. - You can select used path plannings types by a string list. - e.g.: ["RSL", "RSR"] - - Returns - ------- - x_list: array - x positions of the path - y_list: array - y positions of the path - yaw_list: array - yaw angles of the path - modes: array - mode list of the path - lengths: array - arrow_length list of the path segments. - - Examples - -------- - You can generate a dubins path. - - >>> start_x = 1.0 # [m] - >>> start_y = 1.0 # [m] - >>> start_yaw = np.deg2rad(45.0) # [rad] - >>> end_x = -3.0 # [m] - >>> end_y = -3.0 # [m] - >>> end_yaw = np.deg2rad(-45.0) # [rad] - >>> curvature = 1.0 - >>> path_x, path_y, path_yaw, mode, _ = plan_dubins_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature) - >>> plt.plot(path_x, path_y, label="final course " + "".join(mode)) - >>> plot_arrow(start_x, start_y, start_yaw) - >>> plot_arrow(end_x, end_y, end_yaw) - >>> plt.legend() - >>> plt.grid(True) - >>> plt.axis("equal") - >>> plt.show() - - .. image:: dubins_path.jpg - """ - if selected_types is None: - planning_funcs = _PATH_TYPE_MAP.values() - else: - planning_funcs = [_PATH_TYPE_MAP[ptype] for ptype in selected_types] - - # calculate local goal x, y, yaw - l_rot = rot_mat_2d(s_yaw) - le_xy = np.stack([g_x - s_x, g_y - s_y]).T @ l_rot - local_goal_x = le_xy[0] - local_goal_y = le_xy[1] - local_goal_yaw = g_yaw - s_yaw - - lp_x, lp_y, lp_yaw, modes, lengths = _dubins_path_planning_from_origin( - local_goal_x, local_goal_y, local_goal_yaw, curvature, step_size, - planning_funcs) - - # Convert a local coordinate path to the global coordinate - rot = rot_mat_2d(-s_yaw) - converted_xy = np.stack([lp_x, lp_y]).T @ rot - x_list = converted_xy[:, 0] + s_x - y_list = converted_xy[:, 1] + s_y - yaw_list = angle_mod(np.array(lp_yaw) + s_yaw) - - return x_list, y_list, yaw_list, modes, lengths - - -def _mod2pi(theta): - return angle_mod(theta, zero_2_2pi=True) - - -def _calc_trig_funcs(alpha, beta): - sin_a = sin(alpha) - sin_b = sin(beta) - cos_a = cos(alpha) - cos_b = cos(beta) - cos_ab = cos(alpha - beta) - return sin_a, sin_b, cos_a, cos_b, cos_ab - - -def _LSL(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - mode = ["L", "S", "L"] - p_squared = 2 + d ** 2 - (2 * cos_ab) + (2 * d * (sin_a - sin_b)) - if p_squared < 0: # invalid configuration - return None, None, None, mode - tmp = atan2((cos_b - cos_a), d + sin_a - sin_b) - d1 = _mod2pi(-alpha + tmp) - d2 = sqrt(p_squared) - d3 = _mod2pi(beta - tmp) - return d1, d2, d3, mode - - -def _RSR(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - mode = ["R", "S", "R"] - p_squared = 2 + d ** 2 - (2 * cos_ab) + (2 * d * (sin_b - sin_a)) - if p_squared < 0: - return None, None, None, mode - tmp = atan2((cos_a - cos_b), d - sin_a + sin_b) - d1 = _mod2pi(alpha - tmp) - d2 = sqrt(p_squared) - d3 = _mod2pi(-beta + tmp) - return d1, d2, d3, mode - - -def _LSR(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - p_squared = -2 + d ** 2 + (2 * cos_ab) + (2 * d * (sin_a + sin_b)) - mode = ["L", "S", "R"] - if p_squared < 0: - return None, None, None, mode - d1 = sqrt(p_squared) - tmp = atan2((-cos_a - cos_b), (d + sin_a + sin_b)) - atan2(-2.0, d1) - d2 = _mod2pi(-alpha + tmp) - d3 = _mod2pi(-_mod2pi(beta) + tmp) - return d2, d1, d3, mode - - -def _RSL(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - p_squared = d ** 2 - 2 + (2 * cos_ab) - (2 * d * (sin_a + sin_b)) - mode = ["R", "S", "L"] - if p_squared < 0: - return None, None, None, mode - d1 = sqrt(p_squared) - tmp = atan2((cos_a + cos_b), (d - sin_a - sin_b)) - atan2(2.0, d1) - d2 = _mod2pi(alpha - tmp) - d3 = _mod2pi(beta - tmp) - return d2, d1, d3, mode - - -def _RLR(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - mode = ["R", "L", "R"] - tmp = (6.0 - d ** 2 + 2.0 * cos_ab + 2.0 * d * (sin_a - sin_b)) / 8.0 - if abs(tmp) > 1.0: - return None, None, None, mode - d2 = _mod2pi(2 * pi - acos(tmp)) - d1 = _mod2pi(alpha - atan2(cos_a - cos_b, d - sin_a + sin_b) + d2 / 2.0) - d3 = _mod2pi(alpha - beta - d1 + d2) - return d1, d2, d3, mode - - -def _LRL(alpha, beta, d): - sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) - mode = ["L", "R", "L"] - tmp = (6.0 - d ** 2 + 2.0 * cos_ab + 2.0 * d * (- sin_a + sin_b)) / 8.0 - if abs(tmp) > 1.0: - return None, None, None, mode - d2 = _mod2pi(2 * pi - acos(tmp)) - d1 = _mod2pi(-alpha - atan2(cos_a - cos_b, d + sin_a - sin_b) + d2 / 2.0) - d3 = _mod2pi(_mod2pi(beta) - alpha - d1 + _mod2pi(d2)) - return d1, d2, d3, mode - - -_PATH_TYPE_MAP = {"LSL": _LSL, "RSR": _RSR, "LSR": _LSR, "RSL": _RSL, - "RLR": _RLR, "LRL": _LRL, } - - -def _dubins_path_planning_from_origin(end_x, end_y, end_yaw, curvature, - step_size, planning_funcs): - dx = end_x - dy = end_y - d = hypot(dx, dy) * curvature - - theta = _mod2pi(atan2(dy, dx)) - alpha = _mod2pi(-theta) - beta = _mod2pi(end_yaw - theta) - - best_cost = float("inf") - b_d1, b_d2, b_d3, b_mode = None, None, None, None - - for planner in planning_funcs: - d1, d2, d3, mode = planner(alpha, beta, d) - if d1 is None: - continue - - cost = (abs(d1) + abs(d2) + abs(d3)) - if best_cost > cost: # Select minimum length one. - b_d1, b_d2, b_d3, b_mode, best_cost = d1, d2, d3, mode, cost - - lengths = [b_d1, b_d2, b_d3] - x_list, y_list, yaw_list = _generate_local_course(lengths, b_mode, - curvature, step_size) - - lengths = [length / curvature for length in lengths] - - return x_list, y_list, yaw_list, b_mode, lengths - - -def _interpolate(length, mode, max_curvature, origin_x, origin_y, - origin_yaw, path_x, path_y, path_yaw): - if mode == "S": - path_x.append(origin_x + length / max_curvature * cos(origin_yaw)) - path_y.append(origin_y + length / max_curvature * sin(origin_yaw)) - path_yaw.append(origin_yaw) - else: # curve - ldx = sin(length) / max_curvature - ldy = 0.0 - if mode == "L": # left turn - ldy = (1.0 - cos(length)) / max_curvature - elif mode == "R": # right turn - ldy = (1.0 - cos(length)) / -max_curvature - gdx = cos(-origin_yaw) * ldx + sin(-origin_yaw) * ldy - gdy = -sin(-origin_yaw) * ldx + cos(-origin_yaw) * ldy - path_x.append(origin_x + gdx) - path_y.append(origin_y + gdy) - - if mode == "L": # left turn - path_yaw.append(origin_yaw + length) - elif mode == "R": # right turn - path_yaw.append(origin_yaw - length) - - return path_x, path_y, path_yaw - - -def _generate_local_course(lengths, modes, max_curvature, step_size): - p_x, p_y, p_yaw = [0.0], [0.0], [0.0] - - for (mode, length) in zip(modes, lengths): - if length == 0.0: - continue - - # set origin state - origin_x, origin_y, origin_yaw = p_x[-1], p_y[-1], p_yaw[-1] - - current_length = step_size - while abs(current_length + step_size) <= abs(length): - p_x, p_y, p_yaw = _interpolate(current_length, mode, max_curvature, - origin_x, origin_y, origin_yaw, - p_x, p_y, p_yaw) - current_length += step_size - - p_x, p_y, p_yaw = _interpolate(length, mode, max_curvature, origin_x, - origin_y, origin_yaw, p_x, p_y, p_yaw) - - return p_x, p_y, p_yaw - - -def main(): - print("Dubins path planner sample start!!") - import matplotlib.pyplot as plt - from utils.plot import plot_arrow - - start_x = 1.0 # [m] - start_y = 1.0 # [m] - start_yaw = np.deg2rad(45.0) # [rad] - - end_x = -3.0 # [m] - end_y = -3.0 # [m] - end_yaw = np.deg2rad(-45.0) # [rad] - - curvature = 1.0 - - path_x, path_y, path_yaw, mode, lengths = plan_dubins_path(start_x, - start_y, - start_yaw, - end_x, - end_y, - end_yaw, - curvature) - - if show_animation: - plt.plot(path_x, path_y, label="".join(mode)) - plot_arrow(start_x, start_y, start_yaw) - plot_arrow(end_x, end_y, end_yaw) - plt.legend() - plt.grid(True) - plt.axis("equal") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/DynamicMovementPrimitives/dynamic_movement_primitives.py b/PathPlanning/DynamicMovementPrimitives/dynamic_movement_primitives.py deleted file mode 100644 index 9ccd18b7c2..0000000000 --- a/PathPlanning/DynamicMovementPrimitives/dynamic_movement_primitives.py +++ /dev/null @@ -1,260 +0,0 @@ -""" -Author: Jonathan Schwartz (github.com/SchwartzCode) - -This code provides a simple implementation of Dynamic Movement -Primitives, which is an approach to learning curves by modelling -them as a weighted sum of gaussian distributions. This approach -can be used to dampen noise in a curve, and can also be used to -stretch a curve by adjusting its start and end points. - -More information on Dynamic Movement Primitives available at: -https://arxiv.org/abs/2102.03861 -https://www.frontiersin.org/journals/computational-neuroscience/articles/10.3389/fncom.2013.00138/full - -""" - - -from matplotlib import pyplot as plt -import numpy as np - - -class DMP: - - def __init__(self, training_data, data_period, K=156.25, B=25): - """ - Arguments: - training_data - input data of form [N, dim] - data_period - amount of time training data covers - K and B - spring and damper constants to define - DMP behavior - """ - - self.K = K # virtual spring constant - self.B = B # virtual damper coefficient - - self.timesteps = training_data.shape[0] - self.dt = data_period / self.timesteps - - self.weights = None # weights used to generate DMP trajectories - - self.T_orig = data_period - - self.training_data = training_data - self.find_basis_functions_weights(training_data, data_period) - - def find_basis_functions_weights(self, training_data, data_period, - num_weights=10): - """ - Arguments: - data [(steps x spacial dim) np array] - data to replicate with DMP - data_period [float] - time duration of data - """ - - if not isinstance(training_data, np.ndarray): - print("Warning: you should input training data as an np.ndarray") - elif training_data.shape[0] < training_data.shape[1]: - print("Warning: you probably need to transpose your training data") - - dt = data_period / len(training_data) - - init_state = training_data[0] - goal_state = training_data[-1] - - # means (C) and std devs (H) of gaussian basis functions - C = np.linspace(0, 1, num_weights) - H = (0.65*(1./(num_weights-1))**2) - - for dim, _ in enumerate(training_data[0]): - - dimension_data = training_data[:, dim] - - q0 = init_state[dim] - g = goal_state[dim] - - q = q0 - qd_last = 0 - - phi_vals = [] - f_vals = [] - - for i, _ in enumerate(dimension_data): - if i + 1 == len(dimension_data): - qd = 0 - else: - qd = (dimension_data[i+1] - dimension_data[i]) / dt - - phi = [np.exp(-0.5 * ((i * dt / data_period) - c)**2 / H) - for c in C] - phi = phi/np.sum(phi) - - qdd = (qd - qd_last)/dt - - f = (qdd * data_period**2 - self.K * (g - q) + self.B * qd - * data_period) / (g - q0) - - phi_vals.append(phi) - f_vals.append(f) - - qd_last = qd - q += qd * dt - - phi_vals = np.asarray(phi_vals) - f_vals = np.asarray(f_vals) - - w = np.linalg.lstsq(phi_vals, f_vals, rcond=None) - - if self.weights is None: - self.weights = np.asarray(w[0]) - else: - self.weights = np.vstack([self.weights, w[0]]) - - def recreate_trajectory(self, init_state, goal_state, T): - """ - init_state - initial state/position - goal_state - goal state/position - T - amount of time to travel q0 -> g - """ - - nrBasis = len(self.weights[0]) # number of gaussian basis functions - - # means (C) and std devs (H) of gaussian basis functions - C = np.linspace(0, 1, nrBasis) - H = (0.65*(1./(nrBasis-1))**2) - - # initialize virtual system - time = 0 - - q = init_state - dimensions = self.weights.shape[0] - qd = np.zeros(dimensions) - - positions = np.array([]) - for k in range(self.timesteps): - time = time + self.dt - - qdd = np.zeros(dimensions) - - for dim in range(dimensions): - - if time <= T: - phi = [np.exp(-0.5 * ((time / T) - c)**2 / H) for c in C] - phi = phi / np.sum(phi) - f = np.dot(phi, self.weights[dim]) - else: - f = 0 - - # simulate dynamics - qdd[dim] = (self.K*(goal_state[dim] - q[dim])/T**2 - - self.B*qd[dim]/T - + (goal_state[dim] - init_state[dim])*f/T**2) - - qd = qd + qdd * self.dt - q = q + qd * self.dt - - if positions.size == 0: - positions = q - else: - positions = np.vstack([positions, q]) - - t = np.arange(0, self.timesteps * self.dt, self.dt) - return t, positions - - @staticmethod - def dist_between(p1, p2): - return np.linalg.norm(p1 - p2) - - def view_trajectory(self, path, title=None, demo=False): - - path = np.asarray(path) - - plt.cla() - plt.plot(self.training_data[:, 0], self.training_data[:, 1], - label="Training Data") - plt.plot(path[:, 0], path[:, 1], - linewidth=2, label="DMP Approximation") - - plt.xlabel("X Position") - plt.ylabel("Y Position") - plt.legend() - - if title is not None: - plt.title(title) - - if demo: - plt.xlim([-0.5, 5]) - plt.ylim([-2, 2]) - plt.draw() - plt.pause(0.02) - else: - plt.show() - - def show_DMP_purpose(self): - """ - This function conveys the purpose of DMPs: - to capture a trajectory and be able to stretch - and squeeze it in terms of start and stop position - or time - """ - - q0_orig = self.training_data[0] - g_orig = self.training_data[-1] - T_orig = self.T_orig - - data_range = (np.amax(self.training_data[:, 0]) - - np.amin(self.training_data[:, 0])) / 4 - - q0_right = q0_orig + np.array([data_range, 0]) - q0_up = q0_orig + np.array([0, data_range/2]) - g_left = g_orig - np.array([data_range, 0]) - g_down = g_orig - np.array([0, data_range/2]) - - q0_vals = np.vstack([np.linspace(q0_orig, q0_right, 20), - np.linspace(q0_orig, q0_up, 20)]) - g_vals = np.vstack([np.linspace(g_orig, g_left, 20), - np.linspace(g_orig, g_down, 20)]) - T_vals = np.linspace(T_orig, 2*T_orig, 20) - - for new_q0_value in q0_vals: - plot_title = (f"Initial Position = [{round(new_q0_value[0], 2)}," - f" {round(new_q0_value[1], 2)}]") - - _, path = self.recreate_trajectory(new_q0_value, g_orig, T_orig) - self.view_trajectory(path, title=plot_title, demo=True) - - for new_g_value in g_vals: - plot_title = (f"Goal Position = [{round(new_g_value[0], 2)}," - f" {round(new_g_value[1], 2)}]") - - _, path = self.recreate_trajectory(q0_orig, new_g_value, T_orig) - self.view_trajectory(path, title=plot_title, demo=True) - - for new_T_value in T_vals: - plot_title = f"Period = {round(new_T_value, 2)} [sec]" - - _, path = self.recreate_trajectory(q0_orig, g_orig, new_T_value) - self.view_trajectory(path, title=plot_title, demo=True) - - -def example_DMP(): - """ - Creates a noisy trajectory, fits weights to it, and then adjusts the - trajectory by moving its start position, goal position, or period - """ - t = np.arange(0, 3*np.pi/2, 0.01) - t1 = np.arange(3*np.pi/2, 2*np.pi, 0.01)[:-1] - t2 = np.arange(0, np.pi/2, 0.01)[:-1] - t3 = np.arange(np.pi, 3*np.pi/2, 0.01) - data_x = t + 0.02*np.random.rand(t.shape[0]) - data_y = np.concatenate([np.cos(t1) + 0.1*np.random.rand(t1.shape[0]), - np.cos(t2) + 0.1*np.random.rand(t2.shape[0]), - np.sin(t3) + 0.1*np.random.rand(t3.shape[0])]) - training_data = np.vstack([data_x, data_y]).T - - period = 3*np.pi/2 - DMP_controller = DMP(training_data, period) - - DMP_controller.show_DMP_purpose() - - -if __name__ == '__main__': - example_DMP() diff --git a/PathPlanning/DynamicWindowApproach/dynamic_window_approach.py b/PathPlanning/DynamicWindowApproach/dynamic_window_approach.py deleted file mode 100644 index 8664ec1745..0000000000 --- a/PathPlanning/DynamicWindowApproach/dynamic_window_approach.py +++ /dev/null @@ -1,308 +0,0 @@ -""" - -Mobile robot motion planning sample with Dynamic Window Approach - -author: Atsushi Sakai (@Atsushi_twi), Göktuğ Karakaşlı - -""" - -import math -from enum import Enum - -import matplotlib.pyplot as plt -import numpy as np - -show_animation = True - - -def dwa_control(x, config, goal, ob): - """ - Dynamic Window Approach control - """ - dw = calc_dynamic_window(x, config) - - u, trajectory = calc_control_and_trajectory(x, dw, config, goal, ob) - - return u, trajectory - - -class RobotType(Enum): - circle = 0 - rectangle = 1 - - -class Config: - """ - simulation parameter class - """ - - def __init__(self): - # robot parameter - self.max_speed = 1.0 # [m/s] - self.min_speed = -0.5 # [m/s] - self.max_yaw_rate = 40.0 * math.pi / 180.0 # [rad/s] - self.max_accel = 0.2 # [m/ss] - self.max_delta_yaw_rate = 40.0 * math.pi / 180.0 # [rad/ss] - self.v_resolution = 0.01 # [m/s] - self.yaw_rate_resolution = 0.1 * math.pi / 180.0 # [rad/s] - self.dt = 0.1 # [s] Time tick for motion prediction - self.predict_time = 3.0 # [s] - self.to_goal_cost_gain = 0.15 - self.speed_cost_gain = 1.0 - self.obstacle_cost_gain = 1.0 - self.robot_stuck_flag_cons = 0.001 # constant to prevent robot stucked - self.robot_type = RobotType.circle - - # if robot_type == RobotType.circle - # Also used to check if goal is reached in both types - self.robot_radius = 1.0 # [m] for collision check - - # if robot_type == RobotType.rectangle - self.robot_width = 0.5 # [m] for collision check - self.robot_length = 1.2 # [m] for collision check - # obstacles [x(m) y(m), ....] - self.ob = np.array([[-1, -1], - [0, 2], - [4.0, 2.0], - [5.0, 4.0], - [5.0, 5.0], - [5.0, 6.0], - [5.0, 9.0], - [8.0, 9.0], - [7.0, 9.0], - [8.0, 10.0], - [9.0, 11.0], - [12.0, 13.0], - [12.0, 12.0], - [15.0, 15.0], - [13.0, 13.0] - ]) - - @property - def robot_type(self): - return self._robot_type - - @robot_type.setter - def robot_type(self, value): - if not isinstance(value, RobotType): - raise TypeError("robot_type must be an instance of RobotType") - self._robot_type = value - - -config = Config() - - -def motion(x, u, dt): - """ - motion model - """ - - x[2] += u[1] * dt - x[0] += u[0] * math.cos(x[2]) * dt - x[1] += u[0] * math.sin(x[2]) * dt - x[3] = u[0] - x[4] = u[1] - - return x - - -def calc_dynamic_window(x, config): - """ - calculation dynamic window based on current state x - """ - - # Dynamic window from robot specification - Vs = [config.min_speed, config.max_speed, - -config.max_yaw_rate, config.max_yaw_rate] - - # Dynamic window from motion model - Vd = [x[3] - config.max_accel * config.dt, - x[3] + config.max_accel * config.dt, - x[4] - config.max_delta_yaw_rate * config.dt, - x[4] + config.max_delta_yaw_rate * config.dt] - - # [v_min, v_max, yaw_rate_min, yaw_rate_max] - dw = [max(Vs[0], Vd[0]), min(Vs[1], Vd[1]), - max(Vs[2], Vd[2]), min(Vs[3], Vd[3])] - - return dw - - -def predict_trajectory(x_init, v, y, config): - """ - predict trajectory with an input - """ - - x = np.array(x_init) - trajectory = np.array(x) - time = 0 - while time <= config.predict_time: - x = motion(x, [v, y], config.dt) - trajectory = np.vstack((trajectory, x)) - time += config.dt - - return trajectory - - -def calc_control_and_trajectory(x, dw, config, goal, ob): - """ - calculation final input with dynamic window - """ - - x_init = x[:] - min_cost = float("inf") - best_u = [0.0, 0.0] - best_trajectory = np.array([x]) - - # evaluate all trajectory with sampled input in dynamic window - for v in np.arange(dw[0], dw[1], config.v_resolution): - for y in np.arange(dw[2], dw[3], config.yaw_rate_resolution): - - trajectory = predict_trajectory(x_init, v, y, config) - # calc cost - to_goal_cost = config.to_goal_cost_gain * calc_to_goal_cost(trajectory, goal) - speed_cost = config.speed_cost_gain * (config.max_speed - trajectory[-1, 3]) - ob_cost = config.obstacle_cost_gain * calc_obstacle_cost(trajectory, ob, config) - - final_cost = to_goal_cost + speed_cost + ob_cost - - # search minimum trajectory - if min_cost >= final_cost: - min_cost = final_cost - best_u = [v, y] - best_trajectory = trajectory - if abs(best_u[0]) < config.robot_stuck_flag_cons \ - and abs(x[3]) < config.robot_stuck_flag_cons: - # to ensure the robot do not get stuck in - # best v=0 m/s (in front of an obstacle) and - # best omega=0 rad/s (heading to the goal with - # angle difference of 0) - best_u[1] = -config.max_delta_yaw_rate - return best_u, best_trajectory - - -def calc_obstacle_cost(trajectory, ob, config): - """ - calc obstacle cost inf: collision - """ - ox = ob[:, 0] - oy = ob[:, 1] - dx = trajectory[:, 0] - ox[:, None] - dy = trajectory[:, 1] - oy[:, None] - r = np.hypot(dx, dy) - - if config.robot_type == RobotType.rectangle: - yaw = trajectory[:, 2] - rot = np.array([[np.cos(yaw), -np.sin(yaw)], [np.sin(yaw), np.cos(yaw)]]) - rot = np.transpose(rot, [2, 0, 1]) - local_ob = ob[:, None] - trajectory[:, 0:2] - local_ob = local_ob.reshape(-1, local_ob.shape[-1]) - local_ob = np.array([local_ob @ x for x in rot]) - local_ob = local_ob.reshape(-1, local_ob.shape[-1]) - upper_check = local_ob[:, 0] <= config.robot_length / 2 - right_check = local_ob[:, 1] <= config.robot_width / 2 - bottom_check = local_ob[:, 0] >= -config.robot_length / 2 - left_check = local_ob[:, 1] >= -config.robot_width / 2 - if (np.logical_and(np.logical_and(upper_check, right_check), - np.logical_and(bottom_check, left_check))).any(): - return float("Inf") - elif config.robot_type == RobotType.circle: - if np.array(r <= config.robot_radius).any(): - return float("Inf") - - min_r = np.min(r) - return 1.0 / min_r # OK - - -def calc_to_goal_cost(trajectory, goal): - """ - calc to goal cost with angle difference - """ - - dx = goal[0] - trajectory[-1, 0] - dy = goal[1] - trajectory[-1, 1] - error_angle = math.atan2(dy, dx) - cost_angle = error_angle - trajectory[-1, 2] - cost = abs(math.atan2(math.sin(cost_angle), math.cos(cost_angle))) - - return cost - - -def plot_arrow(x, y, yaw, length=0.5, width=0.1): # pragma: no cover - plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw), - head_length=width, head_width=width) - plt.plot(x, y) - - -def plot_robot(x, y, yaw, config): # pragma: no cover - if config.robot_type == RobotType.rectangle: - outline = np.array([[-config.robot_length / 2, config.robot_length / 2, - (config.robot_length / 2), -config.robot_length / 2, - -config.robot_length / 2], - [config.robot_width / 2, config.robot_width / 2, - - config.robot_width / 2, -config.robot_width / 2, - config.robot_width / 2]]) - Rot1 = np.array([[math.cos(yaw), math.sin(yaw)], - [-math.sin(yaw), math.cos(yaw)]]) - outline = (outline.T.dot(Rot1)).T - outline[0, :] += x - outline[1, :] += y - plt.plot(np.array(outline[0, :]).flatten(), - np.array(outline[1, :]).flatten(), "-k") - elif config.robot_type == RobotType.circle: - circle = plt.Circle((x, y), config.robot_radius, color="b") - plt.gcf().gca().add_artist(circle) - out_x, out_y = (np.array([x, y]) + - np.array([np.cos(yaw), np.sin(yaw)]) * config.robot_radius) - plt.plot([x, out_x], [y, out_y], "-k") - - -def main(gx=10.0, gy=10.0, robot_type=RobotType.circle): - print(__file__ + " start!!") - # initial state [x(m), y(m), yaw(rad), v(m/s), omega(rad/s)] - x = np.array([0.0, 0.0, math.pi / 8.0, 0.0, 0.0]) - # goal position [x(m), y(m)] - goal = np.array([gx, gy]) - - # input [forward speed, yaw_rate] - - config.robot_type = robot_type - trajectory = np.array(x) - ob = config.ob - while True: - u, predicted_trajectory = dwa_control(x, config, goal, ob) - x = motion(x, u, config.dt) # simulate robot - trajectory = np.vstack((trajectory, x)) # store state history - - if show_animation: - 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]) - plt.plot(predicted_trajectory[:, 0], predicted_trajectory[:, 1], "-g") - plt.plot(x[0], x[1], "xr") - plt.plot(goal[0], goal[1], "xb") - plt.plot(ob[:, 0], ob[:, 1], "ok") - plot_robot(x[0], x[1], x[2], config) - plot_arrow(x[0], x[1], x[2]) - plt.axis("equal") - plt.grid(True) - plt.pause(0.0001) - - # check reaching goal - dist_to_goal = math.hypot(x[0] - goal[0], x[1] - goal[1]) - if dist_to_goal <= config.robot_radius: - print("Goal!!") - break - - print("Done") - if show_animation: - plt.plot(trajectory[:, 0], trajectory[:, 1], "-r") - plt.pause(0.0001) - plt.show() - - -if __name__ == '__main__': - main(robot_type=RobotType.rectangle) - # main(robot_type=RobotType.circle) diff --git a/PathPlanning/ElasticBands/elastic_bands.py b/PathPlanning/ElasticBands/elastic_bands.py deleted file mode 100644 index 77d4e6e399..0000000000 --- a/PathPlanning/ElasticBands/elastic_bands.py +++ /dev/null @@ -1,300 +0,0 @@ -""" -Elastic Bands - -author: Wang Zheng (@Aglargil) - -Reference: - -- [Elastic Bands: Connecting Path Planning and Control] -(http://www8.cs.umu.se/research/ifor/dl/Control/elastic%20bands.pdf) -""" - -import numpy as np -import sys -import pathlib -import matplotlib.pyplot as plt -from matplotlib.patches import Circle - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -from Mapping.DistanceMap.distance_map import compute_sdf_scipy - -# Elastic Bands Params -MAX_BUBBLE_RADIUS = 100 -MIN_BUBBLE_RADIUS = 10 -RHO0 = 20.0 # Maximum distance for applying repulsive force -KC = 0.05 # Contraction force gain -KR = -0.1 # Repulsive force gain -LAMBDA = 0.7 # Overlap constraint factor -STEP_SIZE = 3.0 # Step size for calculating gradient - -# Visualization Params -ENABLE_PLOT = True -# ENABLE_INTERACTIVE is True allows user to add obstacles by left clicking -# and add path points by right clicking and start planning by middle clicking -ENABLE_INTERACTIVE = False -# ENABLE_SAVE_DATA is True allows saving the path and obstacles which added -# by user in interactive mode to file -ENABLE_SAVE_DATA = False -MAX_ITER = 50 - - -class Bubble: - def __init__(self, position, radius): - self.pos = np.array(position) # Bubble center coordinates [x, y] - self.radius = radius # Safety distance radius ρ(b) - if self.radius > MAX_BUBBLE_RADIUS: - self.radius = MAX_BUBBLE_RADIUS - if self.radius < MIN_BUBBLE_RADIUS: - self.radius = MIN_BUBBLE_RADIUS - - -class ElasticBands: - def __init__( - self, - initial_path, - obstacles, - rho0=RHO0, - kc=KC, - kr=KR, - lambda_=LAMBDA, - step_size=STEP_SIZE, - ): - self.distance_map = compute_sdf_scipy(obstacles) - self.bubbles = [ - Bubble(p, self.compute_rho(p)) for p in initial_path - ] # Initialize bubble chain - self.kc = kc # Contraction force gain - self.kr = kr # Repulsive force gain - self.rho0 = rho0 # Maximum distance for applying repulsive force - self.lambda_ = lambda_ # Overlap constraint factor - self.step_size = step_size # Step size for calculating gradient - self._maintain_overlap() - - def compute_rho(self, position): - """Compute the distance field value at the position""" - return self.distance_map[int(position[0]), int(position[1])] - - def contraction_force(self, i): - """Calculate internal contraction force for the i-th bubble""" - if i == 0 or i == len(self.bubbles) - 1: - return np.zeros(2) - - prev = self.bubbles[i - 1].pos - next_ = self.bubbles[i + 1].pos - current = self.bubbles[i].pos - - # f_c = kc * ( (prev-current)/|prev-current| + (next-current)/|next-current| ) - dir_prev = (prev - current) / (np.linalg.norm(prev - current) + 1e-6) - dir_next = (next_ - current) / (np.linalg.norm(next_ - current) + 1e-6) - return self.kc * (dir_prev + dir_next) - - def repulsive_force(self, i): - """Calculate external repulsive force for the i-th bubble""" - h = self.step_size # Step size - b = self.bubbles[i].pos - rho = self.bubbles[i].radius - - if rho >= self.rho0: - return np.zeros(2) - - # Finite difference approximation of the gradient ∂ρ/∂b - dx = np.array([h, 0]) - dy = np.array([0, h]) - grad_x = (self.compute_rho(b - dx) - self.compute_rho(b + dx)) / (2 * h) - grad_y = (self.compute_rho(b - dy) - self.compute_rho(b + dy)) / (2 * h) - grad = np.array([grad_x, grad_y]) - - return self.kr * (self.rho0 - rho) * grad - - def update_bubbles(self): - """Update bubble positions""" - new_bubbles = [] - for i in range(len(self.bubbles)): - if i == 0 or i == len(self.bubbles) - 1: - new_bubbles.append(self.bubbles[i]) # Fixed start and end points - continue - - f_total = self.contraction_force(i) + self.repulsive_force(i) - v = self.bubbles[i - 1].pos - self.bubbles[i + 1].pos - - # Remove tangential component - f_star = f_total - f_total * v * v / (np.linalg.norm(v) ** 2 + 1e-6) - - alpha = self.bubbles[i].radius # Adaptive step size - new_pos = self.bubbles[i].pos + alpha * f_star - new_pos = np.clip(new_pos, 0, 499) - new_radius = self.compute_rho(new_pos) - - # Update bubble and maintain overlap constraint - new_bubble = Bubble(new_pos, new_radius) - new_bubbles.append(new_bubble) - - self.bubbles = new_bubbles - self._maintain_overlap() - - def _maintain_overlap(self): - """Maintain bubble chain continuity (simplified insertion/deletion mechanism)""" - # Insert bubbles - i = 0 - while i < len(self.bubbles) - 1: - bi, bj = self.bubbles[i], self.bubbles[i + 1] - dist = np.linalg.norm(bi.pos - bj.pos) - if dist > self.lambda_ * (bi.radius + bj.radius): - new_pos = (bi.pos + bj.pos) / 2 - rho = self.compute_rho( - new_pos - ) # Calculate new radius using environment model - self.bubbles.insert(i + 1, Bubble(new_pos, rho)) - i += 2 # Skip the processed region - else: - i += 1 - - # Delete redundant bubbles - i = 1 - while i < len(self.bubbles) - 1: - prev = self.bubbles[i - 1] - next_ = self.bubbles[i + 1] - dist = np.linalg.norm(prev.pos - next_.pos) - if dist <= self.lambda_ * (prev.radius + next_.radius): - del self.bubbles[i] # Delete if redundant - else: - i += 1 - - -class ElasticBandsVisualizer: - def __init__(self): - self.obstacles = np.zeros((500, 500)) - self.obstacles_points = [] - self.path_points = [] - self.elastic_band = None - self.running = True - - if ENABLE_PLOT: - self.fig, self.ax = plt.subplots(figsize=(8, 8)) - self.fig.canvas.mpl_connect("close_event", self.on_close) - self.ax.set_xlim(0, 500) - self.ax.set_ylim(0, 500) - - if ENABLE_INTERACTIVE: - self.path_points = [] # Add a list to store path points - # Connect mouse events - self.fig.canvas.mpl_connect("button_press_event", self.on_click) - else: - self.path_points = np.load(pathlib.Path(__file__).parent / "path.npy") - self.obstacles_points = np.load( - pathlib.Path(__file__).parent / "obstacles.npy" - ) - for x, y in self.obstacles_points: - self.add_obstacle(x, y) - self.plan_path() - - self.plot_background() - - def on_close(self, event): - """Handle window close event""" - self.running = False - plt.close("all") # Close all figure windows - - def plot_background(self): - """Plot the background grid""" - if not ENABLE_PLOT or not self.running: - return - - self.ax.cla() - self.ax.set_xlim(0, 500) - self.ax.set_ylim(0, 500) - self.ax.grid(True) - - if ENABLE_INTERACTIVE: - self.ax.set_title( - "Elastic Bands Path Planning\n" - "Left click: Add obstacles\n" - "Right click: Add path points\n" - "Middle click: Start planning", - pad=20, - ) - else: - self.ax.set_title("Elastic Bands Path Planning", pad=20) - - if self.path_points: - self.ax.plot( - [p[0] for p in self.path_points], - [p[1] for p in self.path_points], - "yo", - markersize=8, - ) - - self.ax.imshow(self.obstacles.T, origin="lower", cmap="binary", alpha=0.8) - self.ax.plot([], [], color="black", label="obstacles") - if self.elastic_band is not None: - path = [b.pos.tolist() for b in self.elastic_band.bubbles] - path = np.array(path) - self.ax.plot(path[:, 0], path[:, 1], "b-", linewidth=2, label="path") - - for bubble in self.elastic_band.bubbles: - circle = Circle( - bubble.pos, bubble.radius, fill=False, color="g", alpha=0.3 - ) - self.ax.add_patch(circle) - self.ax.plot(bubble.pos[0], bubble.pos[1], "bo", markersize=10) - self.ax.plot([], [], color="green", label="bubbles") - - self.ax.legend(loc="upper right") - plt.draw() - plt.pause(0.01) - - def add_obstacle(self, x, y): - """Add an obstacle at the given coordinates""" - size = 30 # Side length of the square - half_size = size // 2 - x_start = max(0, x - half_size) - x_end = min(self.obstacles.shape[0], x + half_size) - y_start = max(0, y - half_size) - y_end = min(self.obstacles.shape[1], y + half_size) - self.obstacles[x_start:x_end, y_start:y_end] = 1 - - def on_click(self, event): - """Handle mouse click events""" - if event.inaxes != self.ax: - return - - x, y = int(event.xdata), int(event.ydata) - - if event.button == 1: # Left click to add obstacles - self.add_obstacle(x, y) - self.obstacles_points.append([x, y]) - - elif event.button == 3: # Right click to add path points - self.path_points.append([x, y]) - - elif event.button == 2: # Middle click to end path input and start planning - if len(self.path_points) >= 2: - if ENABLE_SAVE_DATA: - np.save( - pathlib.Path(__file__).parent / "path.npy", self.path_points - ) - np.save( - pathlib.Path(__file__).parent / "obstacles.npy", - self.obstacles_points, - ) - self.plan_path() - - self.plot_background() - - def plan_path(self): - """Plan the path""" - - initial_path = self.path_points - # Create an elastic band object and optimize - self.elastic_band = ElasticBands(initial_path, self.obstacles) - for _ in range(MAX_ITER): - self.elastic_band.update_bubbles() - self.path_points = [b.pos for b in self.elastic_band.bubbles] - self.plot_background() - - -if __name__ == "__main__": - _ = ElasticBandsVisualizer() - if ENABLE_PLOT: - plt.show(block=True) diff --git a/PathPlanning/ElasticBands/obstacles.npy b/PathPlanning/ElasticBands/obstacles.npy deleted file mode 100644 index af4376afcf..0000000000 Binary files a/PathPlanning/ElasticBands/obstacles.npy and /dev/null differ diff --git a/PathPlanning/ElasticBands/path.npy b/PathPlanning/ElasticBands/path.npy deleted file mode 100644 index be7c253d65..0000000000 Binary files a/PathPlanning/ElasticBands/path.npy and /dev/null differ diff --git a/PathPlanning/Eta3SplinePath/eta3_spline_path.py b/PathPlanning/Eta3SplinePath/eta3_spline_path.py deleted file mode 100644 index 3f685e512f..0000000000 --- a/PathPlanning/Eta3SplinePath/eta3_spline_path.py +++ /dev/null @@ -1,361 +0,0 @@ -""" - -eta^3 polynomials planner - -author: Joe Dinius, Ph.D (https://jwdinius.github.io) - Atsushi Sakai (@Atsushi_twi) - -Reference: -- [eta^3-Splines for the Smooth Path Generation of Wheeled Mobile Robots] -(https://ieeexplore.ieee.org/document/4339545/) - -""" - -import numpy as np -import matplotlib.pyplot as plt -from scipy.integrate import quad - -# NOTE: *_pose is a 3-array: -# 0 - x coord, 1 - y coord, 2 - orientation angle \theta - -show_animation = True - - -class Eta3Path(object): - """ - Eta3Path - - input - segments: a list of `Eta3PathSegment` instances - defining a continuous path - """ - - def __init__(self, segments): - # ensure input has the correct form - assert(isinstance(segments, list) and isinstance( - segments[0], Eta3PathSegment)) - # ensure that each segment begins from the previous segment's end (continuity) - for r, s in zip(segments[:-1], segments[1:]): - assert(np.array_equal(r.end_pose, s.start_pose)) - self.segments = segments - - def calc_path_point(self, u): - """ - Eta3Path::calc_path_point - - input - normalized interpolation point along path object, 0 <= u <= len(self.segments) - returns - 2d (x,y) position vector - """ - - assert(0 <= u <= len(self.segments)) - if np.isclose(u, len(self.segments)): - segment_idx = len(self.segments) - 1 - u = 1. - else: - segment_idx = int(np.floor(u)) - u -= segment_idx - return self.segments[segment_idx].calc_point(u) - - -class Eta3PathSegment(object): - """ - Eta3PathSegment - constructs an eta^3 path segment based on desired - shaping, eta, and curvature vector, kappa. If either, or both, - of eta and kappa are not set during initialization, - they will default to zeros. - - input - start_pose - starting pose array (x, y, \theta) - end_pose - ending pose array (x, y, \theta) - eta - shaping parameters, default=None - kappa - curvature parameters, default=None - """ - - def __init__(self, start_pose, end_pose, eta=None, kappa=None): - # make sure inputs are of the correct size - assert(len(start_pose) == 3 and len(start_pose) == len(end_pose)) - self.start_pose = start_pose - self.end_pose = end_pose - # if no eta is passed, initialize it to array of zeros - if not eta: - eta = np.zeros((6,)) - else: - # make sure that eta has correct size - assert(len(eta) == 6) - # if no kappa is passed, initialize to array of zeros - if not kappa: - kappa = np.zeros((4,)) - else: - assert(len(kappa) == 4) - # set up angle cosines and sines for simpler computations below - ca = np.cos(start_pose[2]) - sa = np.sin(start_pose[2]) - cb = np.cos(end_pose[2]) - sb = np.sin(end_pose[2]) - # 2 dimensions (x,y) x 8 coefficients per dimension - self.coeffs = np.empty((2, 8)) - # constant terms (u^0) - self.coeffs[0, 0] = start_pose[0] - self.coeffs[1, 0] = start_pose[1] - # linear (u^1) - self.coeffs[0, 1] = eta[0] * ca - self.coeffs[1, 1] = eta[0] * sa - # quadratic (u^2) - self.coeffs[0, 2] = 1. / 2 * eta[2] * \ - ca - 1. / 2 * eta[0]**2 * kappa[0] * sa - self.coeffs[1, 2] = 1. / 2 * eta[2] * \ - sa + 1. / 2 * eta[0]**2 * kappa[0] * ca - # cubic (u^3) - self.coeffs[0, 3] = 1. / 6 * eta[4] * ca - 1. / 6 * \ - (eta[0]**3 * kappa[1] + 3. * eta[0] * eta[2] * kappa[0]) * sa - self.coeffs[1, 3] = 1. / 6 * eta[4] * sa + 1. / 6 * \ - (eta[0]**3 * kappa[1] + 3. * eta[0] * eta[2] * kappa[0]) * ca - # quartic (u^4) - tmp1 = 35. * (end_pose[0] - start_pose[0]) - tmp2 = (20. * eta[0] + 5 * eta[2] + 2. / 3 * eta[4]) * ca - tmp3 = (5. * eta[0] ** 2 * kappa[0] + 2. / 3 * eta[0] ** 3 * kappa[1] - + 2. * eta[0] * eta[2] * kappa[0]) * sa - tmp4 = (15. * eta[1] - 5. / 2 * eta[3] + 1. / 6 * eta[5]) * cb - tmp5 = (5. / 2 * eta[1] ** 2 * kappa[2] - 1. / 6 * eta[1] ** 3 * - kappa[3] - 1. / 2 * eta[1] * eta[3] * kappa[2]) * sb - self.coeffs[0, 4] = tmp1 - tmp2 + tmp3 - tmp4 - tmp5 - tmp1 = 35. * (end_pose[1] - start_pose[1]) - tmp2 = (20. * eta[0] + 5. * eta[2] + 2. / 3 * eta[4]) * sa - tmp3 = (5. * eta[0] ** 2 * kappa[0] + 2. / 3 * eta[0] ** 3 * kappa[1] - + 2. * eta[0] * eta[2] * kappa[0]) * ca - tmp4 = (15. * eta[1] - 5. / 2 * eta[3] + 1. / 6 * eta[5]) * sb - tmp5 = (5. / 2 * eta[1] ** 2 * kappa[2] - 1. / 6 * eta[1] ** 3 * - kappa[3] - 1. / 2 * eta[1] * eta[3] * kappa[2]) * cb - self.coeffs[1, 4] = tmp1 - tmp2 - tmp3 - tmp4 + tmp5 - # quintic (u^5) - tmp1 = -84. * (end_pose[0] - start_pose[0]) - tmp2 = (45. * eta[0] + 10. * eta[2] + eta[4]) * ca - tmp3 = (10. * eta[0] ** 2 * kappa[0] + eta[0] ** 3 * kappa[1] + 3. * - eta[0] * eta[2] * kappa[0]) * sa - tmp4 = (39. * eta[1] - 7. * eta[3] + 1. / 2 * eta[5]) * cb - tmp5 = + (7. * eta[1] ** 2 * kappa[2] - 1. / 2 * eta[1] ** 3 * kappa[3] - - 3. / 2 * eta[1] * eta[3] * kappa[2]) * sb - self.coeffs[0, 5] = tmp1 + tmp2 - tmp3 + tmp4 + tmp5 - tmp1 = -84. * (end_pose[1] - start_pose[1]) - tmp2 = (45. * eta[0] + 10. * eta[2] + eta[4]) * sa - tmp3 = (10. * eta[0] ** 2 * kappa[0] + eta[0] ** 3 * kappa[1] + 3. * - eta[0] * eta[2] * kappa[0]) * ca - tmp4 = (39. * eta[1] - 7. * eta[3] + 1. / 2 * eta[5]) * sb - tmp5 = - (7. * eta[1] ** 2 * kappa[2] - 1. / 2 * eta[1] ** 3 * kappa[3] - - 3. / 2 * eta[1] * eta[3] * kappa[2]) * cb - self.coeffs[1, 5] = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 - # sextic (u^6) - tmp1 = 70. * (end_pose[0] - start_pose[0]) - tmp2 = (36. * eta[0] + 15. / 2 * eta[2] + 2. / 3 * eta[4]) * ca - tmp3 = + (15. / 2 * eta[0] ** 2 * kappa[0] + 2. / 3 * eta[0] ** 3 * - kappa[1] + 2. * eta[0] * eta[2] * kappa[0]) * sa - tmp4 = (34. * eta[1] - 13. / 2 * eta[3] + 1. / 2 * eta[5]) * cb - tmp5 = - (13. / 2 * eta[1] ** 2 * kappa[2] - 1. / 2 * eta[1] ** 3 * - kappa[3] - 3. / 2 * eta[1] * eta[3] * kappa[2]) * sb - self.coeffs[0, 6] = tmp1 - tmp2 + tmp3 - tmp4 + tmp5 - tmp1 = 70. * (end_pose[1] - start_pose[1]) - tmp2 = - (36. * eta[0] + 15. / 2 * eta[2] + 2. / 3 * eta[4]) * sa - tmp3 = - (15. / 2 * eta[0] ** 2 * kappa[0] + 2. / 3 * eta[0] ** 3 * - kappa[1] + 2. * eta[0] * eta[2] * kappa[0]) * ca - tmp4 = - (34. * eta[1] - 13. / 2 * eta[3] + 1. / 2 * eta[5]) * sb - tmp5 = + (13. / 2 * eta[1] ** 2 * kappa[2] - 1. / 2 * eta[1] ** 3 * - kappa[3] - 3. / 2 * eta[1] * eta[3] * kappa[2]) * cb - self.coeffs[1, 6] = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 - # septic (u^7) - tmp1 = -20. * (end_pose[0] - start_pose[0]) - tmp2 = (10. * eta[0] + 2. * eta[2] + 1. / 6 * eta[4]) * ca - tmp3 = - (2. * eta[0] ** 2 * kappa[0] + 1. / 6 * eta[0] ** 3 * kappa[1] - + 1. / 2 * eta[0] * eta[2] * kappa[0]) * sa - tmp4 = (10. * eta[1] - 2. * eta[3] + 1. / 6 * eta[5]) * cb - tmp5 = (2. * eta[1] ** 2 * kappa[2] - 1. / 6 * eta[1] ** 3 * kappa[3] - - 1. / 2 * eta[1] * eta[3] * kappa[2]) * sb - self.coeffs[0, 7] = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 - - tmp1 = -20. * (end_pose[1] - start_pose[1]) - tmp2 = (10. * eta[0] + 2. * eta[2] + 1. / 6 * eta[4]) * sa - tmp3 = (2. * eta[0] ** 2 * kappa[0] + 1. / 6 * eta[0] ** 3 * kappa[1] - + 1. / 2 * eta[0] * eta[2] * kappa[0]) * ca - tmp4 = (10. * eta[1] - 2. * eta[3] + 1. / 6 * eta[5]) * sb - tmp5 = - (2. * eta[1] ** 2 * kappa[2] - 1. / 6 * eta[1] ** 3 * kappa[3] - - 1. / 2 * eta[1] * eta[3] * kappa[2]) * cb - self.coeffs[1, 7] = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 - self.s_dot = lambda u: max(np.linalg.norm( - self.coeffs[:, 1:].dot(np.array( - [1, 2. * u, 3. * u**2, 4. * u**3, - 5. * u**4, 6. * u**5, 7. * u**6]))), 1e-6) - self.f_length = lambda ue: quad(lambda u: self.s_dot(u), 0, ue) - self.segment_length = self.f_length(1)[0] - - def calc_point(self, u): - """ - Eta3PathSegment::calc_point - - input - u - parametric representation of a point along the segment, 0 <= u <= 1 - returns - (x,y) of point along the segment - """ - assert(0 <= u <= 1) - return self.coeffs.dot(np.array([1, u, u**2, u**3, u**4, u**5, u**6, u**7])) - - def calc_deriv(self, u, order=1): - """ - Eta3PathSegment::calc_deriv - - input - u - parametric representation of a point along the segment, 0 <= u <= 1 - returns - (d^nx/du^n,d^ny/du^n) of point along the segment, for 0 < n <= 2 - """ - - assert(0 <= u <= 1) - assert(0 < order <= 2) - if order == 1: - return self.coeffs[:, 1:].dot(np.array([1, 2. * u, 3. * u**2, 4. * u**3, 5. * u**4, 6. * u**5, 7. * u**6])) - - return self.coeffs[:, 2:].dot(np.array([2, 6. * u, 12. * u**2, 20. * u**3, 30. * u**4, 42. * u**5])) - - -def test1(): - - for i in range(10): - path_segments = [] - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 3.0, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - eta = [i, i, 0, 0, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - path = Eta3Path(path_segments) - - # interpolate at several points along the path - ui = np.linspace(0, len(path_segments), 1001) - pos = np.empty((2, ui.size)) - for j, u in enumerate(ui): - pos[:, j] = path.calc_path_point(u) - - if show_animation: - # plot the path - plt.plot(pos[0, :], pos[1, :]) - # 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]) - plt.pause(1.0) - - if show_animation: - plt.close("all") - - -def test2(): - - for i in range(10): - path_segments = [] - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 3.0, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - eta = [0, 0, (i - 5) * 20, (5 - i) * 20, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - path = Eta3Path(path_segments) - - # interpolate at several points along the path - ui = np.linspace(0, len(path_segments), 1001) - pos = np.empty((2, ui.size)) - for j, u in enumerate(ui): - pos[:, j] = path.calc_path_point(u) - - if show_animation: - # plot the path - plt.plot(pos[0, :], pos[1, :]) - plt.pause(1.0) - - if show_animation: - plt.close("all") - - -def test3(): - path_segments = [] - - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 1.5, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - eta = [4.27, 4.27, 0, 0, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 2: line segment - start_pose = [4, 1.5, 0] - end_pose = [5.5, 1.5, 0] - kappa = [0, 0, 0, 0] - eta = [0, 0, 0, 0, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 3: cubic spiral - start_pose = [5.5, 1.5, 0] - end_pose = [7.4377, 1.8235, 0.6667] - kappa = [0, 0, 1, 1] - eta = [1.88, 1.88, 0, 0, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 4: generic twirl arc - start_pose = [7.4377, 1.8235, 0.6667] - end_pose = [7.8, 4.3, 1.8] - kappa = [1, 1, 0.5, 0] - eta = [7, 10, 10, -10, 4, 4] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 5: circular arc - start_pose = [7.8, 4.3, 1.8] - end_pose = [5.4581, 5.8064, 3.3416] - kappa = [0.5, 0, 0.5, 0] - eta = [2.98, 2.98, 0, 0, 0, 0] - path_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # construct the whole path - path = Eta3Path(path_segments) - - # interpolate at several points along the path - ui = np.linspace(0, len(path_segments), 1001) - pos = np.empty((2, ui.size)) - for i, u in enumerate(ui): - pos[:, i] = path.calc_path_point(u) - - # plot the path - - if show_animation: - plt.figure('Path from Reference') - plt.plot(pos[0, :], pos[1, :]) - plt.xlabel('x') - plt.ylabel('y') - plt.title('Path') - plt.pause(1.0) - - plt.show() - - -def main(): - """ - recreate path from reference (see Table 1) - """ - test1() - test2() - test3() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/Eta3SplineTrajectory/eta3_spline_trajectory.py b/PathPlanning/Eta3SplineTrajectory/eta3_spline_trajectory.py deleted file mode 100644 index e72d33261e..0000000000 --- a/PathPlanning/Eta3SplineTrajectory/eta3_spline_trajectory.py +++ /dev/null @@ -1,456 +0,0 @@ -""" - -eta^3 polynomials trajectory planner - -author: Joe Dinius, Ph.D (https://jwdinius.github.io) - Atsushi Sakai (@Atsushi_twi) - -Refs: -- https://jwdinius.github.io/blog/2018/eta3traj/ -- [eta^3-Splines for the Smooth Path Generation of Wheeled Mobile Robots] -(https://ieeexplore.ieee.org/document/4339545/) - -""" - -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.collections import LineCollection -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from Eta3SplinePath.eta3_spline_path import Eta3Path, Eta3PathSegment - -show_animation = True - - -class MaxVelocityNotReached(Exception): - def __init__(self, actual_vel, max_vel): - self.message = f'Actual velocity {actual_vel} does not equal desired max velocity {max_vel}!' - - -class Eta3SplineTrajectory(Eta3Path): - """ - eta3_trajectory - - input - segments: list of `eta3_trajectory_segment` instances defining a continuous trajectory - """ - - def __init__(self, segments, max_vel, v0=0.0, a0=0.0, max_accel=2.0, max_jerk=5.0): - # ensure that all inputs obey the assumptions of the model - assert max_vel > 0 and v0 >= 0 and a0 >= 0 and max_accel > 0 and max_jerk > 0 \ - and a0 <= max_accel and v0 <= max_vel - super(__class__, self).__init__(segments=segments) - self.total_length = sum([s.segment_length for s in self.segments]) - self.max_vel = float(max_vel) - self.v0 = float(v0) - self.a0 = float(a0) - self.max_accel = float(max_accel) - self.max_jerk = float(max_jerk) - length_array = np.array([s.segment_length for s in self.segments]) - # add a zero to the beginning for finding the correct segment_id - self.cum_lengths = np.concatenate( - (np.array([0]), np.cumsum(length_array))) - # compute velocity profile on top of the path - self.velocity_profile() - self.ui_prev = 0 - self.prev_seg_id = 0 - - def velocity_profile(self): - r""" /~~~~~----------------\ - / \ - / \ - / \ - / \ - (v=v0, a=a0) ~~~~~ \ - \ - \ ~~~~~ (vf=0, af=0) - pos.|pos.|neg.| cruise at |neg.| neg. |neg. - max |max.|max.| max. |max.| max. |max. - jerk|acc.|jerk| velocity |jerk| acc. |jerk - index 0 1 2 3 (optional) 4 5 6 - """ - # delta_a: accel change from initial position to end of maximal jerk section - delta_a = self.max_accel - self.a0 - # t_s1: time of traversal of maximal jerk section - t_s1 = delta_a / self.max_jerk - # v_s1: velocity at the end of the maximal jerk section - v_s1 = self.v0 + self.a0 * t_s1 + self.max_jerk * t_s1**2 / 2. - # s_s1: length of the maximal jerk section - s_s1 = self.v0 * t_s1 + self.a0 * t_s1**2 / 2. + self.max_jerk * t_s1**3 / 6. - # t_sf: time of traversal of final section, which is also maximal jerk, but has final velocity 0 - t_sf = self.max_accel / self.max_jerk - # v_sf: velocity at beginning of final section - v_sf = self.max_jerk * t_sf**2 / 2. - # s_sf: length of final section - s_sf = self.max_jerk * t_sf**3 / 6. - # solve for the maximum achievable velocity based on the kinematic limits imposed by max_accel and max_jerk - # this leads to a quadratic equation in v_max: a*v_max**2 + b*v_max + c = 0 - a = 1 / self.max_accel - b = 3. * self.max_accel / (2. * self.max_jerk) + v_s1 / self.max_accel - ( - self.max_accel**2 / self.max_jerk + v_s1) / self.max_accel - c = s_s1 + s_sf - self.total_length - 7. * self.max_accel**3 / (3. * self.max_jerk**2) \ - - v_s1 * (self.max_accel / self.max_jerk + v_s1 / self.max_accel) \ - + (self.max_accel**2 / self.max_jerk + v_s1 / - self.max_accel)**2 / (2. * self.max_accel) - v_max = (-b + np.sqrt(b**2 - 4. * a * c)) / (2. * a) - - # v_max represents the maximum velocity that could be attained if there was no cruise period - # (i.e. driving at constant speed without accelerating or jerking) - # if this velocity is less than our desired max velocity, the max velocity needs to be updated - if self.max_vel > v_max: - # when this condition is tripped, there will be no cruise period (s_cruise=0) - self.max_vel = v_max - - # setup arrays to store values at END of trajectory sections - self.times = np.zeros((7,)) - self.vels = np.zeros((7,)) - self.seg_lengths = np.zeros((7,)) - - # Section 0: max jerk up to max acceleration - self.times[0] = t_s1 - self.vels[0] = v_s1 - self.seg_lengths[0] = s_s1 - - # Section 1: accelerate at max_accel - index = 1 - # compute change in velocity over the section - delta_v = (self.max_vel - self.max_jerk * (self.max_accel / - self.max_jerk)**2 / 2.) - self.vels[index - 1] - self.times[index] = delta_v / self.max_accel - self.vels[index] = self.vels[index - 1] + \ - self.max_accel * self.times[index] - self.seg_lengths[index] = self.vels[index - 1] * \ - self.times[index] + self.max_accel * self.times[index]**2 / 2. - - # Section 2: decrease acceleration (down to 0) until max speed is hit - index = 2 - self.times[index] = self.max_accel / self.max_jerk - self.vels[index] = self.vels[index - 1] + self.max_accel * self.times[index] \ - - self.max_jerk * self.times[index]**2 / 2. - - # as a check, the velocity at the end of the section should be self.max_vel - if not np.isclose(self.vels[index], self.max_vel): - raise MaxVelocityNotReached(self.vels[index], self.max_vel) - - self.seg_lengths[index] = self.vels[index - 1] * self.times[index] + self.max_accel * self.times[index]**2 / 2. \ - - self.max_jerk * self.times[index]**3 / 6. - - # Section 3: will be done last - - # Section 4: apply min jerk until min acceleration is hit - index = 4 - self.times[index] = self.max_accel / self.max_jerk - self.vels[index] = self.max_vel - \ - self.max_jerk * self.times[index]**2 / 2. - self.seg_lengths[index] = self.max_vel * self.times[index] - \ - self.max_jerk * self.times[index]**3 / 6. - - # Section 5: continue deceleration at max rate - index = 5 - # compute velocity change over sections - delta_v = self.vels[index - 1] - v_sf - self.times[index] = delta_v / self.max_accel - self.vels[index] = self.vels[index - 1] - \ - self.max_accel * self.times[index] - self.seg_lengths[index] = self.vels[index - 1] * \ - self.times[index] - self.max_accel * self.times[index]**2 / 2. - - # Section 6(final): max jerk to get to zero velocity and zero acceleration simultaneously - index = 6 - self.times[index] = t_sf - self.vels[index] = self.vels[index - 1] - self.max_jerk * t_sf**2 / 2. - - try: - assert np.isclose(self.vels[index], 0) - except AssertionError as e: - print(f'The final velocity {self.vels[index]} is not zero') - raise e - - self.seg_lengths[index] = s_sf - - if self.seg_lengths.sum() < self.total_length: - index = 3 - # the length of the cruise section is whatever length hasn't already been accounted for - # NOTE: the total array self.seg_lengths is summed because the entry for the cruise segment is - # initialized to 0! - self.seg_lengths[index] = self.total_length - \ - self.seg_lengths.sum() - self.vels[index] = self.max_vel - self.times[index] = self.seg_lengths[index] / self.max_vel - - # make sure that all of the times are positive, otherwise the kinematic limits - # chosen cannot be enforced on the path - assert(np.all(self.times >= 0)) - self.total_time = self.times.sum() - - def get_interp_param(self, seg_id, s, ui, tol=0.001): - def f(u): - return self.segments[seg_id].f_length(u)[0] - s - - def fprime(u): - return self.segments[seg_id].s_dot(u) - while (0 <= ui <= 1) and abs(f(ui)) > tol: - ui -= f(ui) / fprime(ui) - ui = max(0, min(ui, 1)) - return ui - - def calc_traj_point(self, time): - # compute velocity at time - if time <= self.times[0]: - linear_velocity = self.v0 + self.max_jerk * time**2 / 2. - s = self.v0 * time + self.max_jerk * time**3 / 6 - linear_accel = self.max_jerk * time - elif time <= self.times[:2].sum(): - delta_t = time - self.times[0] - linear_velocity = self.vels[0] + self.max_accel * delta_t - s = self.seg_lengths[0] + self.vels[0] * \ - delta_t + self.max_accel * delta_t**2 / 2. - linear_accel = self.max_accel - elif time <= self.times[:3].sum(): - delta_t = time - self.times[:2].sum() - linear_velocity = self.vels[1] + self.max_accel * \ - delta_t - self.max_jerk * delta_t**2 / 2. - s = self.seg_lengths[:2].sum() + self.vels[1] * delta_t + self.max_accel * delta_t**2 / 2. \ - - self.max_jerk * delta_t**3 / 6. - linear_accel = self.max_accel - self.max_jerk * delta_t - elif time <= self.times[:4].sum(): - delta_t = time - self.times[:3].sum() - linear_velocity = self.vels[3] - s = self.seg_lengths[:3].sum() + self.vels[3] * delta_t - linear_accel = 0. - elif time <= self.times[:5].sum(): - delta_t = time - self.times[:4].sum() - linear_velocity = self.vels[3] - self.max_jerk * delta_t**2 / 2. - s = self.seg_lengths[:4].sum() + self.vels[3] * \ - delta_t - self.max_jerk * delta_t**3 / 6. - linear_accel = -self.max_jerk * delta_t - elif time <= self.times[:-1].sum(): - delta_t = time - self.times[:5].sum() - linear_velocity = self.vels[4] - self.max_accel * delta_t - s = self.seg_lengths[:5].sum() + self.vels[4] * \ - delta_t - self.max_accel * delta_t**2 / 2. - linear_accel = -self.max_accel - elif time < self.times.sum(): - delta_t = time - self.times[:-1].sum() - linear_velocity = self.vels[5] - self.max_accel * \ - delta_t + self.max_jerk * delta_t**2 / 2. - s = self.seg_lengths[:-1].sum() + self.vels[5] * delta_t - self.max_accel * delta_t**2 / 2. \ - + self.max_jerk * delta_t**3 / 6. - linear_accel = -self.max_accel + self.max_jerk * delta_t - else: - linear_velocity = 0. - s = self.total_length - linear_accel = 0. - seg_id = np.max(np.argwhere(self.cum_lengths <= s)) - - # will happen at the end of the segment - if seg_id == len(self.segments): - seg_id -= 1 - ui = 1 - else: - # compute interpolation parameter using length from current segment's starting point - curr_segment_length = s - self.cum_lengths[seg_id] - ui = self.get_interp_param( - seg_id=seg_id, s=curr_segment_length, ui=self.ui_prev) - - if not seg_id == self.prev_seg_id: - self.ui_prev = 0 - else: - self.ui_prev = ui - - self.prev_seg_id = seg_id - # compute angular velocity of current point= (ydd*xd - xdd*yd) / (xd**2 + yd**2) - d = self.segments[seg_id].calc_deriv(ui, order=1) - dd = self.segments[seg_id].calc_deriv(ui, order=2) - # su - the rate of change of arclength wrt u - su = self.segments[seg_id].s_dot(ui) - if not np.isclose(su, 0.) and not np.isclose(linear_velocity, 0.): - # ut - time-derivative of interpolation parameter u - ut = linear_velocity / su - # utt - time-derivative of ut - utt = linear_accel / su - \ - (d[0] * dd[0] + d[1] * dd[1]) / su**2 * ut - xt = d[0] * ut - yt = d[1] * ut - xtt = dd[0] * ut**2 + d[0] * utt - ytt = dd[1] * ut**2 + d[1] * utt - angular_velocity = (ytt * xt - xtt * yt) / linear_velocity**2 - else: - angular_velocity = 0. - - # combine path point with orientation and velocities - pos = self.segments[seg_id].calc_point(ui) - state = np.array([pos[0], pos[1], np.arctan2( - d[1], d[0]), linear_velocity, angular_velocity]) - return state - - -def test1(max_vel=0.5): - - for i in range(10): - trajectory_segments = [] - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 3.0, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - eta = [i, i, 0, 0, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - traj = Eta3SplineTrajectory(trajectory_segments, - max_vel=max_vel, max_accel=0.5) - - # interpolate at several points along the path - times = np.linspace(0, traj.total_time, 101) - state = np.empty((5, times.size)) - for j, t in enumerate(times): - state[:, j] = traj.calc_traj_point(t) - - if show_animation: # pragma: no cover - # plot the path - plt.plot(state[0, :], state[1, :]) - plt.pause(1.0) - - plt.show() - if show_animation: - plt.close("all") - - -def test2(max_vel=0.5): - - for i in range(10): - trajectory_segments = [] - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 3.0, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - # NOTE: INTEGRATOR ERROR EXPLODES WHEN eta[:1] IS ZERO! - # was: eta = [0, 0, (i - 5) * 20, (5 - i) * 20, 0, 0], now is: - eta = [0.1, 0.1, (i - 5) * 20, (5 - i) * 20, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - traj = Eta3SplineTrajectory(trajectory_segments, - max_vel=max_vel, max_accel=0.5) - - # interpolate at several points along the path - times = np.linspace(0, traj.total_time, 101) - state = np.empty((5, times.size)) - for j, t in enumerate(times): - state[:, j] = traj.calc_traj_point(t) - - if show_animation: - # plot the path - plt.plot(state[0, :], state[1, :]) - plt.pause(1.0) - - plt.show() - if show_animation: - plt.close("all") - - -def test3(max_vel=2.0): - trajectory_segments = [] - - # segment 1: lane-change curve - start_pose = [0, 0, 0] - end_pose = [4, 1.5, 0] - # NOTE: The ordering on kappa is [kappa_A, kappad_A, kappa_B, kappad_B], with kappad_* being the curvature derivative - kappa = [0, 0, 0, 0] - eta = [4.27, 4.27, 0, 0, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 2: line segment - start_pose = [4, 1.5, 0] - end_pose = [5.5, 1.5, 0] - kappa = [0, 0, 0, 0] - # NOTE: INTEGRATOR ERROR EXPLODES WHEN eta[:1] IS ZERO! - # was: eta = [0, 0, 0, 0, 0, 0], now is: - eta = [0.5, 0.5, 0, 0, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 3: cubic spiral - start_pose = [5.5, 1.5, 0] - end_pose = [7.4377, 1.8235, 0.6667] - kappa = [0, 0, 1, 1] - eta = [1.88, 1.88, 0, 0, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 4: generic twirl arc - start_pose = [7.4377, 1.8235, 0.6667] - end_pose = [7.8, 4.3, 1.8] - kappa = [1, 1, 0.5, 0] - eta = [7, 10, 10, -10, 4, 4] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # segment 5: circular arc - start_pose = [7.8, 4.3, 1.8] - end_pose = [5.4581, 5.8064, 3.3416] - kappa = [0.5, 0, 0.5, 0] - eta = [2.98, 2.98, 0, 0, 0, 0] - trajectory_segments.append(Eta3PathSegment( - start_pose=start_pose, end_pose=end_pose, eta=eta, kappa=kappa)) - - # construct the whole path - traj = Eta3SplineTrajectory(trajectory_segments, - max_vel=max_vel, max_accel=0.5, max_jerk=1) - - # interpolate at several points along the path - times = np.linspace(0, traj.total_time, 1001) - state = np.empty((5, times.size)) - for i, t in enumerate(times): - state[:, i] = traj.calc_traj_point(t) - - # plot the path - - if show_animation: - fig, ax = plt.subplots() - x, y = state[0, :], state[1, :] - points = np.array([x, y]).T.reshape(-1, 1, 2) - segs = np.concatenate([points[:-1], points[1:]], axis=1) - lc = LineCollection(segs, cmap=plt.get_cmap('inferno')) - ax.set_xlim(np.min(x) - 1, np.max(x) + 1) - ax.set_ylim(np.min(y) - 1, np.max(y) + 1) - lc.set_array(state[3, :]) - lc.set_linewidth(3) - ax.add_collection(lc) - axcb = fig.colorbar(lc) - axcb.set_label('velocity(m/s)') - ax.set_title('Trajectory') - plt.xlabel('x') - plt.ylabel('y') - plt.pause(1.0) - - fig1, ax1 = plt.subplots() - ax1.plot(times, state[3, :], 'b-') - ax1.set_xlabel('time(s)') - ax1.set_ylabel('velocity(m/s)', color='b') - ax1.tick_params('y', colors='b') - ax1.set_title('Control') - ax2 = ax1.twinx() - ax2.plot(times, state[4, :], 'r-') - ax2.set_ylabel('angular velocity(rad/s)', color='r') - ax2.tick_params('y', colors='r') - fig.tight_layout() - plt.show() - - -def main(): - """ - recreate path from reference (see Table 1) - """ - # test1() - # test2() - test3() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/FlowField/flowfield.py b/PathPlanning/FlowField/flowfield.py deleted file mode 100644 index e50430de3c..0000000000 --- a/PathPlanning/FlowField/flowfield.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -flowfield pathfinding -author: Sarim Mehdi (muhammadsarim.mehdi@studio.unibo.it) -Source: https://leifnode.com/2013/12/flow-field-pathfinding/ -""" - -import numpy as np -import matplotlib.pyplot as plt - -show_animation = True - - -def draw_horizontal_line(start_x, start_y, length, o_x, o_y, o_dict, path): - for i in range(start_x, start_x + length): - for j in range(start_y, start_y + 2): - o_x.append(i) - o_y.append(j) - o_dict[(i, j)] = path - - -def draw_vertical_line(start_x, start_y, length, o_x, o_y, o_dict, path): - for i in range(start_x, start_x + 2): - for j in range(start_y, start_y + length): - o_x.append(i) - o_y.append(j) - o_dict[(i, j)] = path - - -class FlowField: - def __init__(self, obs_grid, goal_x, goal_y, start_x, start_y, - limit_x, limit_y): - self.start_pt = [start_x, start_y] - self.goal_pt = [goal_x, goal_y] - self.obs_grid = obs_grid - self.limit_x, self.limit_y = limit_x, limit_y - self.cost_field = {} - self.integration_field = {} - self.vector_field = {} - - def find_path(self): - self.create_cost_field() - self.create_integration_field() - self.assign_vectors() - self.follow_vectors() - - def create_cost_field(self): - """Assign cost to each grid which defines the energy - it would take to get there.""" - for i in range(self.limit_x): - for j in range(self.limit_y): - if self.obs_grid[(i, j)] == 'free': - self.cost_field[(i, j)] = 1 - elif self.obs_grid[(i, j)] == 'medium': - self.cost_field[(i, j)] = 7 - elif self.obs_grid[(i, j)] == 'hard': - self.cost_field[(i, j)] = 20 - elif self.obs_grid[(i, j)] == 'obs': - continue - - if [i, j] == self.goal_pt: - self.cost_field[(i, j)] = 0 - - def create_integration_field(self): - """Start from the goal node and calculate the value - of the integration field at each node. Start by - assigning a value of infinity to every node except - the goal node which is assigned a value of 0. Put the - goal node in the open list and then get its neighbors - (must not be obstacles). For each neighbor, the new - cost is equal to the cost of the current node in the - integration field (in the beginning, this will simply - be the goal node) + the cost of the neighbor in the - cost field + the extra cost (optional). The new cost - is only assigned if it is less than the previously - assigned cost of the node in the integration field and, - when that happens, the neighbor is put on the open list. - This process continues until the open list is empty.""" - for i in range(self.limit_x): - for j in range(self.limit_y): - if self.obs_grid[(i, j)] == 'obs': - continue - self.integration_field[(i, j)] = np.inf - if [i, j] == self.goal_pt: - self.integration_field[(i, j)] = 0 - - open_list = [(self.goal_pt, 0)] - while open_list: - curr_pos, curr_cost = open_list[0] - curr_x, curr_y = curr_pos - for i in range(-1, 2): - for j in range(-1, 2): - x, y = curr_x + i, curr_y + j - if self.obs_grid[(x, y)] == 'obs': - continue - if (i, j) in [(1, 0), (0, 1), (-1, 0), (0, -1)]: - e_cost = 10 - else: - e_cost = 14 - neighbor_energy = self.cost_field[(x, y)] - neighbor_old_cost = self.integration_field[(x, y)] - neighbor_new_cost = curr_cost + neighbor_energy + e_cost - if neighbor_new_cost < neighbor_old_cost: - self.integration_field[(x, y)] = neighbor_new_cost - open_list.append(([x, y], neighbor_new_cost)) - del open_list[0] - - def assign_vectors(self): - """For each node, assign a vector from itself to the node with - the lowest cost in the integration field. An agent will simply - follow this vector field to the goal""" - for i in range(self.limit_x): - for j in range(self.limit_y): - if self.obs_grid[(i, j)] == 'obs': - continue - if [i, j] == self.goal_pt: - self.vector_field[(i, j)] = (None, None) - continue - offset_list = [(i + a, j + b) - for a in range(-1, 2) - for b in range(-1, 2)] - neighbor_list = [{'loc': pt, - 'cost': self.integration_field[pt]} - for pt in offset_list - if self.obs_grid[pt] != 'obs'] - neighbor_list = sorted(neighbor_list, key=lambda x: x['cost']) - best_neighbor = neighbor_list[0]['loc'] - self.vector_field[(i, j)] = best_neighbor - - def follow_vectors(self): - curr_x, curr_y = self.start_pt - while curr_x is not None and curr_y is not None: - curr_x, curr_y = self.vector_field[(curr_x, curr_y)] - - if show_animation: - plt.plot(curr_x, curr_y, "b*") - plt.pause(0.001) - - if show_animation: - plt.show() - - -def main(): - # set obstacle positions - obs_dict = {} - for i in range(51): - for j in range(51): - obs_dict[(i, j)] = 'free' - o_x, o_y, m_x, m_y, h_x, h_y = [], [], [], [], [], [] - - s_x = 5.0 - s_y = 5.0 - g_x = 35.0 - g_y = 45.0 - - # draw outer border of maze - draw_vertical_line(0, 0, 50, o_x, o_y, obs_dict, 'obs') - draw_vertical_line(48, 0, 50, o_x, o_y, obs_dict, 'obs') - draw_horizontal_line(0, 0, 50, o_x, o_y, obs_dict, 'obs') - draw_horizontal_line(0, 48, 50, o_x, o_y, obs_dict, 'obs') - - # draw inner walls - all_x = [10, 10, 10, 15, 20, 20, 30, 30, 35, 30, 40, 45] - all_y = [10, 30, 45, 20, 5, 40, 10, 40, 5, 40, 10, 25] - all_len = [10, 10, 5, 10, 10, 5, 20, 10, 25, 10, 35, 15] - for x, y, l in zip(all_x, all_y, all_len): - draw_vertical_line(x, y, l, o_x, o_y, obs_dict, 'obs') - - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [35, 40, 15, 10, 45, 20, 10, 15, 25, 45, 10, 30, 10, 40] - all_y = [5, 10, 15, 20, 20, 25, 30, 35, 35, 35, 40, 40, 45, 45] - all_len = [10, 5, 10, 10, 5, 5, 10, 5, 10, 5, 10, 5, 5, 5] - for x, y, l in zip(all_x, all_y, all_len): - draw_horizontal_line(x, y, l, o_x, o_y, obs_dict, 'obs') - - # Some points are assigned a slightly higher energy value in the cost - # field. For example, if an agent wishes to go to a point, it might - # encounter different kind of terrain like grass and dirt. Grass is - # assigned medium difficulty of passage (color coded as green on the - # map here). Dirt is assigned hard difficulty of passage (color coded - # as brown here). Hence, this algorithm will take into account how - # difficult it is to go through certain areas of a map when deciding - # the shortest path. - - # draw paths that have medium difficulty (in terms of going through them) - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [10, 45] - all_y = [22, 20] - all_len = [8, 5] - for x, y, l in zip(all_x, all_y, all_len): - draw_vertical_line(x, y, l, m_x, m_y, obs_dict, 'medium') - - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [20, 30, 42] + [47] * 5 - all_y = [35, 30, 38] + [37 + i for i in range(2)] - all_len = [5, 7, 3] + [1] * 3 - for x, y, l in zip(all_x, all_y, all_len): - draw_horizontal_line(x, y, l, m_x, m_y, obs_dict, 'medium') - - # draw paths that have hard difficulty (in terms of going through them) - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [15, 20, 35] - all_y = [45, 20, 35] - all_len = [3, 5, 7] - for x, y, l in zip(all_x, all_y, all_len): - draw_vertical_line(x, y, l, h_x, h_y, obs_dict, 'hard') - - all_x[:], all_y[:], all_len[:] = [], [], [] - all_x = [30] + [47] * 5 - all_y = [10] + [37 + i for i in range(2)] - all_len = [5] + [1] * 3 - for x, y, l in zip(all_x, all_y, all_len): - draw_horizontal_line(x, y, l, h_x, h_y, obs_dict, 'hard') - - if show_animation: - plt.plot(o_x, o_y, "sr") - plt.plot(m_x, m_y, "sg") - plt.plot(h_x, h_y, "sy") - plt.plot(s_x, s_y, "og") - plt.plot(g_x, g_y, "o") - plt.grid(True) - - flow_obj = FlowField(obs_dict, g_x, g_y, s_x, s_y, 50, 50) - flow_obj.find_path() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/FrenetOptimalTrajectory/cartesian_frenet_converter.py b/PathPlanning/FrenetOptimalTrajectory/cartesian_frenet_converter.py deleted file mode 100644 index 482712ceaf..0000000000 --- a/PathPlanning/FrenetOptimalTrajectory/cartesian_frenet_converter.py +++ /dev/null @@ -1,144 +0,0 @@ -""" - -A converter between Cartesian and Frenet coordinate systems - -author: Wang Zheng (@Aglargil) - -Reference: - -- [Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame] -(https://www.researchgate.net/profile/Moritz_Werling/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame/links/54f749df0cf210398e9277af.pdf) - -""" - -import math - - -class CartesianFrenetConverter: - """ - A class for converting states between Cartesian and Frenet coordinate systems - """ - - @ staticmethod - def cartesian_to_frenet(rs, rx, ry, rtheta, rkappa, rdkappa, x, y, v, a, theta, kappa): - """ - Convert state from Cartesian coordinate to Frenet coordinate - - Parameters - ---------- - rs: reference line s-coordinate - rx, ry: reference point coordinates - rtheta: reference point heading - rkappa: reference point curvature - rdkappa: reference point curvature rate - x, y: current position - v: velocity - a: acceleration - theta: heading angle - kappa: curvature - - Returns - ------- - s_condition: [s(t), s'(t), s''(t)] - d_condition: [d(s), d'(s), d''(s)] - """ - dx = x - rx - dy = y - ry - - cos_theta_r = math.cos(rtheta) - sin_theta_r = math.sin(rtheta) - - cross_rd_nd = cos_theta_r * dy - sin_theta_r * dx - d = math.copysign(math.hypot(dx, dy), cross_rd_nd) - - delta_theta = theta - rtheta - tan_delta_theta = math.tan(delta_theta) - cos_delta_theta = math.cos(delta_theta) - - one_minus_kappa_r_d = 1 - rkappa * d - d_dot = one_minus_kappa_r_d * tan_delta_theta - - kappa_r_d_prime = rdkappa * d + rkappa * d_dot - - d_ddot = (-kappa_r_d_prime * tan_delta_theta + - one_minus_kappa_r_d / (cos_delta_theta * cos_delta_theta) * - (kappa * one_minus_kappa_r_d / cos_delta_theta - rkappa)) - - s = rs - s_dot = v * cos_delta_theta / one_minus_kappa_r_d - - delta_theta_prime = one_minus_kappa_r_d / cos_delta_theta * kappa - rkappa - s_ddot = (a * cos_delta_theta - - s_dot * s_dot * - (d_dot * delta_theta_prime - kappa_r_d_prime)) / one_minus_kappa_r_d - - return [s, s_dot, s_ddot], [d, d_dot, d_ddot] - - @ staticmethod - def frenet_to_cartesian(rs, rx, ry, rtheta, rkappa, rdkappa, s_condition, d_condition): - """ - Convert state from Frenet coordinate to Cartesian coordinate - - Parameters - ---------- - rs: reference line s-coordinate - rx, ry: reference point coordinates - rtheta: reference point heading - rkappa: reference point curvature - rdkappa: reference point curvature rate - s_condition: [s(t), s'(t), s''(t)] - d_condition: [d(s), d'(s), d''(s)] - - Returns - ------- - x, y: position - theta: heading angle - kappa: curvature - v: velocity - a: acceleration - """ - if abs(rs - s_condition[0]) >= 1.0e-6: - raise ValueError( - "The reference point s and s_condition[0] don't match") - - cos_theta_r = math.cos(rtheta) - sin_theta_r = math.sin(rtheta) - - x = rx - sin_theta_r * d_condition[0] - y = ry + cos_theta_r * d_condition[0] - - one_minus_kappa_r_d = 1 - rkappa * d_condition[0] - - tan_delta_theta = d_condition[1] / one_minus_kappa_r_d - delta_theta = math.atan2(d_condition[1], one_minus_kappa_r_d) - cos_delta_theta = math.cos(delta_theta) - - theta = CartesianFrenetConverter.normalize_angle(delta_theta + rtheta) - - kappa_r_d_prime = rdkappa * d_condition[0] + rkappa * d_condition[1] - - kappa = (((d_condition[2] + kappa_r_d_prime * tan_delta_theta) * - cos_delta_theta * cos_delta_theta) / one_minus_kappa_r_d + rkappa) * \ - cos_delta_theta / one_minus_kappa_r_d - - d_dot = d_condition[1] * s_condition[1] - v = math.sqrt(one_minus_kappa_r_d * one_minus_kappa_r_d * - s_condition[1] * s_condition[1] + d_dot * d_dot) - - delta_theta_prime = one_minus_kappa_r_d / cos_delta_theta * kappa - rkappa - - a = (s_condition[2] * one_minus_kappa_r_d / cos_delta_theta + - s_condition[1] * s_condition[1] / cos_delta_theta * - (d_condition[1] * delta_theta_prime - kappa_r_d_prime)) - - return x, y, theta, kappa, v, a - - @ staticmethod - def normalize_angle(angle): - """ - Normalize angle to [-pi, pi] - """ - a = math.fmod(angle + math.pi, 2.0 * math.pi) - if a < 0.0: - a += 2.0 * math.pi - return a - math.pi diff --git a/PathPlanning/FrenetOptimalTrajectory/frenet_optimal_trajectory.py b/PathPlanning/FrenetOptimalTrajectory/frenet_optimal_trajectory.py deleted file mode 100644 index 4b82fb70fd..0000000000 --- a/PathPlanning/FrenetOptimalTrajectory/frenet_optimal_trajectory.py +++ /dev/null @@ -1,568 +0,0 @@ -""" - -Frenet optimal trajectory generator - -author: Atsushi Sakai (@Atsushi_twi) - -Reference: - -- [Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame] -(https://www.researchgate.net/profile/Moritz_Werling/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame/links/54f749df0cf210398e9277af.pdf) - -- [Optimal trajectory generation for dynamic street scenarios in a Frenet Frame] -(https://www.youtube.com/watch?v=Cj6tAQe7UCY) - -""" - -import numpy as np -import matplotlib.pyplot as plt -import copy -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from QuinticPolynomialsPlanner.quintic_polynomials_planner import QuinticPolynomial -from CubicSpline import cubic_spline_planner - -from enum import Enum, auto -from FrenetOptimalTrajectory.cartesian_frenet_converter import ( - CartesianFrenetConverter, -) - - -class LateralMovement(Enum): - HIGH_SPEED = auto() - LOW_SPEED = auto() - - -class LongitudinalMovement(Enum): - MERGING_AND_STOPPING = auto() - VELOCITY_KEEPING = auto() - - -# Default Parameters - -LATERAL_MOVEMENT = LateralMovement.HIGH_SPEED -LONGITUDINAL_MOVEMENT = LongitudinalMovement.VELOCITY_KEEPING - -MAX_SPEED = 50.0 / 3.6 # maximum speed [m/s] -MAX_ACCEL = 5.0 # maximum acceleration [m/ss] -MAX_CURVATURE = 1.0 # maximum curvature [1/m] -DT = 0.2 # time tick [s] -MAX_T = 5.0 # max prediction time [m] -MIN_T = 4.0 # min prediction time [m] -N_S_SAMPLE = 1 # sampling number of target speed - -# cost weights -K_J = 0.1 -K_T = 0.1 -K_S_DOT = 1.0 -K_D = 1.0 -K_S = 1.0 -K_LAT = 1.0 -K_LON = 1.0 - -SIM_LOOP = 500 -show_animation = True - - -if LATERAL_MOVEMENT == LateralMovement.LOW_SPEED: - MAX_ROAD_WIDTH = 1.0 # maximum road width [m] - D_ROAD_W = 0.2 # road width sampling length [m] - TARGET_SPEED = 3.0 / 3.6 # maximum speed [m/s] - D_T_S = 0.5 / 3.6 # target speed sampling length [m/s] - # Waypoints - WX = [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] - WY = [0.0, 0.0, 1.0, 0.0, -1.0, -2.0] - OBSTACLES = np.array([[3.0, 1.0], [5.0, -0.0], [6.0, 0.5], [8.0, -1.5]]) - ROBOT_RADIUS = 0.5 # robot radius [m] - - # Initial state parameters - INITIAL_SPEED = 1.0 / 3.6 # current speed [m/s] - INITIAL_ACCEL = 0.0 # current acceleration [m/ss] - INITIAL_LAT_POSITION = 0.5 # current lateral position [m] - INITIAL_LAT_SPEED = 0.0 # current lateral speed [m/s] - INITIAL_LAT_ACCELERATION = 0.0 # current lateral acceleration [m/s] - INITIAL_COURSE_POSITION = 0.0 # current course position - - ANIMATION_AREA = 5.0 # Animation area length [m] - - STOP_S = 4.0 # Merge and stop distance [m] - D_S = 0.3 # Stop point sampling length [m] - N_STOP_S_SAMPLE = 3 # Stop point sampling number -else: - MAX_ROAD_WIDTH = 7.0 # maximum road width [m] - D_ROAD_W = 1.0 # road width sampling length [m] - TARGET_SPEED = 30.0 / 3.6 # target speed [m/s] - D_T_S = 5.0 / 3.6 # target speed sampling length [m/s] - # Waypoints - WX = [0.0, 10.0, 20.5, 35.0, 70.5] - WY = [0.0, -6.0, 5.0, 6.5, 0.0] - # Obstacle list - OBSTACLES = np.array( - [[20.0, 10.0], [30.0, 6.0], [30.0, 8.0], [35.0, 8.0], [50.0, 3.0]] - ) - ROBOT_RADIUS = 2.0 # robot radius [m] - - # Initial state parameters - INITIAL_SPEED = 10.0 / 3.6 # current speed [m/s] - INITIAL_ACCEL = 0.0 # current acceleration [m/ss] - INITIAL_LAT_POSITION = 2.0 # current lateral position [m] - INITIAL_LAT_SPEED = 0.0 # current lateral speed [m/s] - INITIAL_LAT_ACCELERATION = 0.0 # current lateral acceleration [m/s] - INITIAL_COURSE_POSITION = 0.0 # current course position - - ANIMATION_AREA = 20.0 # Animation area length [m] - STOP_S = 25.0 # Merge and stop distance [m] - D_S = 2 # Stop point sampling length [m] - N_STOP_S_SAMPLE = 4 # Stop point sampling number - - -class LateralMovementStrategy: - def calc_lateral_trajectory(self, fp, di, c_d, c_d_d, c_d_dd, Ti): - """ - Calculate the lateral trajectory - """ - raise NotImplementedError("calc_lateral_trajectory not implemented") - - def calc_cartesian_parameters(self, fp, csp): - """ - Calculate the cartesian parameters (x, y, yaw, curvature, v, a) - """ - raise NotImplementedError("calc_cartesian_parameters not implemented") - - -class HighSpeedLateralMovementStrategy(LateralMovementStrategy): - def calc_lateral_trajectory(self, fp, di, c_d, c_d_d, c_d_dd, Ti): - tp = copy.deepcopy(fp) - s0_d = fp.s_d[0] - s0_dd = fp.s_dd[0] - # d'(t) = d'(s) * s'(t) - # d''(t) = d''(s) * s'(t)^2 + d'(s) * s''(t) - lat_qp = QuinticPolynomial( - c_d, c_d_d * s0_d, c_d_dd * s0_d**2 + c_d_d * s0_dd, di, 0.0, 0.0, Ti - ) - - tp.d = [] - tp.d_d = [] - tp.d_dd = [] - tp.d_ddd = [] - - # Calculate all derivatives in a single loop to reduce iterations - for i in range(len(fp.t)): - t = fp.t[i] - s_d = fp.s_d[i] - s_dd = fp.s_dd[i] - - s_d_inv = 1.0 / (s_d + 1e-6) + 1e-6 # Avoid division by zero - s_d_inv_sq = s_d_inv * s_d_inv # Square of inverse - - d = lat_qp.calc_point(t) - d_d = lat_qp.calc_first_derivative(t) - d_dd = lat_qp.calc_second_derivative(t) - d_ddd = lat_qp.calc_third_derivative(t) - - tp.d.append(d) - # d'(s) = d'(t) / s'(t) - tp.d_d.append(d_d * s_d_inv) - # d''(s) = (d''(t) - d'(s) * s''(t)) / s'(t)^2 - tp.d_dd.append((d_dd - tp.d_d[i] * s_dd) * s_d_inv_sq) - tp.d_ddd.append(d_ddd) - - return tp - - def calc_cartesian_parameters(self, fp, csp): - # calc global positions - for i in range(len(fp.s)): - ix, iy = csp.calc_position(fp.s[i]) - if ix is None: - break - i_yaw = csp.calc_yaw(fp.s[i]) - i_kappa = csp.calc_curvature(fp.s[i]) - i_dkappa = csp.calc_curvature_rate(fp.s[i]) - s_condition = [fp.s[i], fp.s_d[i], fp.s_dd[i]] - d_condition = [ - fp.d[i], - fp.d_d[i], - fp.d_dd[i], - ] - x, y, theta, kappa, v, a = CartesianFrenetConverter.frenet_to_cartesian( - fp.s[i], ix, iy, i_yaw, i_kappa, i_dkappa, s_condition, d_condition - ) - fp.x.append(x) - fp.y.append(y) - fp.yaw.append(theta) - fp.c.append(kappa) - fp.v.append(v) - fp.a.append(a) - return fp - - -class LowSpeedLateralMovementStrategy(LateralMovementStrategy): - def calc_lateral_trajectory(self, fp, di, c_d, c_d_d, c_d_dd, Ti): - s0 = fp.s[0] - s1 = fp.s[-1] - tp = copy.deepcopy(fp) - # d = d(s), d_d = d'(s), d_dd = d''(s) - # * shift s range from [s0, s1] to [0, s1 - s0] - lat_qp = QuinticPolynomial(c_d, c_d_d, c_d_dd, di, 0.0, 0.0, s1 - s0) - - tp.d = [lat_qp.calc_point(s - s0) for s in fp.s] - tp.d_d = [lat_qp.calc_first_derivative(s - s0) for s in fp.s] - tp.d_dd = [lat_qp.calc_second_derivative(s - s0) for s in fp.s] - tp.d_ddd = [lat_qp.calc_third_derivative(s - s0) for s in fp.s] - return tp - - def calc_cartesian_parameters(self, fp, csp): - # calc global positions - for i in range(len(fp.s)): - ix, iy = csp.calc_position(fp.s[i]) - if ix is None: - break - i_yaw = csp.calc_yaw(fp.s[i]) - i_kappa = csp.calc_curvature(fp.s[i]) - i_dkappa = csp.calc_curvature_rate(fp.s[i]) - s_condition = [fp.s[i], fp.s_d[i], fp.s_dd[i]] - d_condition = [fp.d[i], fp.d_d[i], fp.d_dd[i]] - x, y, theta, kappa, v, a = CartesianFrenetConverter.frenet_to_cartesian( - fp.s[i], ix, iy, i_yaw, i_kappa, i_dkappa, s_condition, d_condition - ) - fp.x.append(x) - fp.y.append(y) - fp.yaw.append(theta) - fp.c.append(kappa) - fp.v.append(v) - fp.a.append(a) - return fp - - -class LongitudinalMovementStrategy: - def calc_longitudinal_trajectory(self, c_speed, c_accel, Ti, s0): - """ - Calculate the longitudinal trajectory - """ - raise NotImplementedError("calc_longitudinal_trajectory not implemented") - - def get_d_arrange(self, s0): - """ - Get the d sample range - """ - raise NotImplementedError("get_d_arrange not implemented") - - def calc_destination_cost(self, fp): - """ - Calculate the destination cost - """ - raise NotImplementedError("calc_destination_cost not implemented") - - -class VelocityKeepingLongitudinalMovementStrategy(LongitudinalMovementStrategy): - def calc_longitudinal_trajectory(self, c_speed, c_accel, Ti, s0): - fplist = [] - for tv in np.arange( - TARGET_SPEED - D_T_S * N_S_SAMPLE, TARGET_SPEED + D_T_S * N_S_SAMPLE, D_T_S - ): - fp = FrenetPath() - lon_qp = QuarticPolynomial(s0, c_speed, c_accel, tv, 0.0, Ti) - fp.t = [t for t in np.arange(0.0, Ti, DT)] - fp.s = [lon_qp.calc_point(t) for t in fp.t] - fp.s_d = [lon_qp.calc_first_derivative(t) for t in fp.t] - fp.s_dd = [lon_qp.calc_second_derivative(t) for t in fp.t] - fp.s_ddd = [lon_qp.calc_third_derivative(t) for t in fp.t] - fplist.append(fp) - return fplist - - def get_d_arrange(self, s0): - return np.arange(-MAX_ROAD_WIDTH, MAX_ROAD_WIDTH, D_ROAD_W) - - def calc_destination_cost(self, fp): - ds = (TARGET_SPEED - fp.s_d[-1]) ** 2 - return K_S_DOT * ds - - -class MergingAndStoppingLongitudinalMovementStrategy(LongitudinalMovementStrategy): - def calc_longitudinal_trajectory(self, c_speed, c_accel, Ti, s0): - if s0 >= STOP_S: - return [] - fplist = [] - for s in np.arange( - STOP_S - D_S * N_STOP_S_SAMPLE, STOP_S + D_S * N_STOP_S_SAMPLE, D_S - ): - fp = FrenetPath() - lon_qp = QuinticPolynomial(s0, c_speed, c_accel, s, 0.0, 0.0, Ti) - fp.t = [t for t in np.arange(0.0, Ti, DT)] - fp.s = [lon_qp.calc_point(t) for t in fp.t] - fp.s_d = [lon_qp.calc_first_derivative(t) for t in fp.t] - fp.s_dd = [lon_qp.calc_second_derivative(t) for t in fp.t] - fp.s_ddd = [lon_qp.calc_third_derivative(t) for t in fp.t] - fplist.append(fp) - return fplist - - def get_d_arrange(self, s0): - # Only if s0 is less than STOP_S / 3, then we sample the road width - if s0 < STOP_S / 3: - return np.arange(-MAX_ROAD_WIDTH, MAX_ROAD_WIDTH, D_ROAD_W) - else: - return [0.0] - - def calc_destination_cost(self, fp): - ds = (STOP_S - fp.s[-1]) ** 2 - return K_S * ds - -LATERAL_MOVEMENT_STRATEGY: LateralMovementStrategy -LONGITUDINAL_MOVEMENT_STRATEGY: LongitudinalMovementStrategy - -if LATERAL_MOVEMENT == LateralMovement.HIGH_SPEED: - LATERAL_MOVEMENT_STRATEGY = HighSpeedLateralMovementStrategy() -else: - LATERAL_MOVEMENT_STRATEGY = LowSpeedLateralMovementStrategy() - -if LONGITUDINAL_MOVEMENT == LongitudinalMovement.VELOCITY_KEEPING: - LONGITUDINAL_MOVEMENT_STRATEGY = VelocityKeepingLongitudinalMovementStrategy() -else: - LONGITUDINAL_MOVEMENT_STRATEGY = MergingAndStoppingLongitudinalMovementStrategy() - - -class QuarticPolynomial: - def __init__(self, xs, vxs, axs, vxe, axe, time): - # calc coefficient of quartic polynomial - - self.a0 = xs - self.a1 = vxs - self.a2 = axs / 2.0 - - A = np.array([[3 * time**2, 4 * time**3], [6 * time, 12 * time**2]]) - b = np.array([vxe - self.a1 - 2 * self.a2 * time, axe - 2 * self.a2]) - x = np.linalg.solve(A, b) - - self.a3 = x[0] - self.a4 = x[1] - - def calc_point(self, t): - xt = self.a0 + self.a1 * t + self.a2 * t**2 + self.a3 * t**3 + self.a4 * t**4 - - return xt - - def calc_first_derivative(self, t): - xt = self.a1 + 2 * self.a2 * t + 3 * self.a3 * t**2 + 4 * self.a4 * t**3 - - return xt - - def calc_second_derivative(self, t): - xt = 2 * self.a2 + 6 * self.a3 * t + 12 * self.a4 * t**2 - - return xt - - def calc_third_derivative(self, t): - xt = 6 * self.a3 + 24 * self.a4 * t - - return xt - - -class FrenetPath: - def __init__(self): - self.t = [] - self.d = [] - self.d_d = [] # d'(s) - self.d_dd = [] # d''(s) - self.d_ddd = [] # d'''(t) in low speed / d'''(s) in high speed - self.s = [] - self.s_d = [] # s'(t) - self.s_dd = [] # s''(t) - self.s_ddd = [] # s'''(t) - self.cf = 0.0 - - self.x = [] - self.y = [] - self.yaw = [] - self.v = [] - self.a = [] - self.ds = [] - self.c = [] - - def pop_front(self): - self.x.pop(0) - self.y.pop(0) - self.yaw.pop(0) - self.v.pop(0) - self.a.pop(0) - self.s.pop(0) - self.s_d.pop(0) - self.s_dd.pop(0) - self.s_ddd.pop(0) - self.d.pop(0) - self.d_d.pop(0) - self.d_dd.pop(0) - self.d_ddd.pop(0) - - -def calc_frenet_paths(c_s_d, c_s_dd, c_d, c_d_d, c_d_dd, s0): - frenet_paths = [] - - for Ti in np.arange(MIN_T, MAX_T, DT): - lon_paths = LONGITUDINAL_MOVEMENT_STRATEGY.calc_longitudinal_trajectory( - c_s_d, c_s_dd, Ti, s0 - ) - - for fp in lon_paths: - for di in LONGITUDINAL_MOVEMENT_STRATEGY.get_d_arrange(s0): - tp = LATERAL_MOVEMENT_STRATEGY.calc_lateral_trajectory( - fp, di, c_d, c_d_d, c_d_dd, Ti - ) - - Jp = sum(np.power(tp.d_ddd, 2)) # square of jerk - Js = sum(np.power(tp.s_ddd, 2)) # square of jerk - - lat_cost = K_J * Jp + K_T * Ti + K_D * tp.d[-1] ** 2 - lon_cost = ( - K_J * Js - + K_T * Ti - + LONGITUDINAL_MOVEMENT_STRATEGY.calc_destination_cost(tp) - ) - tp.cf = K_LAT * lat_cost + K_LON * lon_cost - frenet_paths.append(tp) - - return frenet_paths - - -def calc_global_paths(fplist, csp): - return [ - LATERAL_MOVEMENT_STRATEGY.calc_cartesian_parameters(fp, csp) for fp in fplist - ] - - -def check_collision(fp, ob): - for i in range(len(ob[:, 0])): - d = [ - ((ix - ob[i, 0]) ** 2 + (iy - ob[i, 1]) ** 2) - for (ix, iy) in zip(fp.x, fp.y) - ] - - collision = any([di <= ROBOT_RADIUS**2 for di in d]) - - if collision: - return False - - return True - - -def check_paths(fplist, ob): - path_dict = { - "max_speed_error": [], - "max_accel_error": [], - "max_curvature_error": [], - "collision_error": [], - "ok": [], - } - for i, _ in enumerate(fplist): - if any([v > MAX_SPEED for v in fplist[i].v]): # Max speed check - path_dict["max_speed_error"].append(fplist[i]) - elif any([abs(a) > MAX_ACCEL for a in fplist[i].a]): # Max accel check - path_dict["max_accel_error"].append(fplist[i]) - elif any([abs(c) > MAX_CURVATURE for c in fplist[i].c]): # Max curvature check - path_dict["max_curvature_error"].append(fplist[i]) - elif not check_collision(fplist[i], ob): - path_dict["collision_error"].append(fplist[i]) - else: - path_dict["ok"].append(fplist[i]) - return path_dict - - -def frenet_optimal_planning(csp, s0, c_s_d, c_s_dd, c_d, c_d_d, c_d_dd, ob): - fplist = calc_frenet_paths(c_s_d, c_s_dd, c_d, c_d_d, c_d_dd, s0) - fplist = calc_global_paths(fplist, csp) - fpdict = check_paths(fplist, ob) - - # find minimum cost path - min_cost = float("inf") - best_path = None - for fp in fpdict["ok"]: - if min_cost >= fp.cf: - min_cost = fp.cf - best_path = fp - - return [best_path, fpdict] - - -def generate_target_course(x, y): - csp = cubic_spline_planner.CubicSpline2D(x, y) - s = np.arange(0, csp.s[-1], 0.1) - - rx, ry, ryaw, rk = [], [], [], [] - for i_s in s: - ix, iy = csp.calc_position(i_s) - rx.append(ix) - ry.append(iy) - ryaw.append(csp.calc_yaw(i_s)) - rk.append(csp.calc_curvature(i_s)) - - return rx, ry, ryaw, rk, csp - - -def main(): - print(__file__ + " start!!") - - tx, ty, tyaw, tc, csp = generate_target_course(WX, WY) - - # Initialize state using global parameters - c_s_d = INITIAL_SPEED - c_s_dd = INITIAL_ACCEL - c_d = INITIAL_LAT_POSITION - c_d_d = INITIAL_LAT_SPEED - c_d_dd = INITIAL_LAT_ACCELERATION - s0 = INITIAL_COURSE_POSITION - - area = ANIMATION_AREA - - last_path = None - - for i in range(SIM_LOOP): - [path, fpdict] = frenet_optimal_planning( - csp, s0, c_s_d, c_s_dd, c_d, c_d_d, c_d_dd, OBSTACLES - ) - - if path is None: - path = copy.deepcopy(last_path) - path.pop_front() - if len(path.x) <= 1: - print("Finish") - break - - last_path = path - s0 = path.s[1] - c_d = path.d[1] - c_d_d = path.d_d[1] - c_d_dd = path.d_dd[1] - c_s_d = path.s_d[1] - c_s_dd = path.s_dd[1] - if np.hypot(path.x[1] - tx[-1], path.y[1] - ty[-1]) <= 1.0: - print("Goal") - break - - if show_animation: # pragma: no cover - 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], - ) - plt.plot(tx, ty) - plt.plot(OBSTACLES[:, 0], OBSTACLES[:, 1], "xk") - plt.plot(path.x[1:], path.y[1:], "-or") - plt.plot(path.x[1], path.y[1], "vc") - plt.xlim(path.x[1] - area, path.x[1] + area) - plt.ylim(path.y[1] - area, path.y[1] + area) - plt.title("v[km/h]:" + str(path.v[1] * 3.6)[0:4]) - plt.grid(True) - plt.pause(0.0001) - - print("Finish") - if show_animation: # pragma: no cover - plt.grid(True) - plt.pause(0.0001) - plt.show() - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/GreedyBestFirstSearch/greedy_best_first_search.py b/PathPlanning/GreedyBestFirstSearch/greedy_best_first_search.py deleted file mode 100644 index b259beb870..0000000000 --- a/PathPlanning/GreedyBestFirstSearch/greedy_best_first_search.py +++ /dev/null @@ -1,278 +0,0 @@ -""" - -Greedy Best-First grid planning - -author: Erwin Lejeune (@spida_rwin) - -See Wikipedia article (https://en.wikipedia.org/wiki/Best-first_search) - -""" - -import math - -import matplotlib.pyplot as plt - -show_animation = True - - -class BestFirstSearchPlanner: - - def __init__(self, ox, oy, reso, rr): - """ - Initialize grid map for greedy best-first planning - - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - self.reso = reso - self.rr = rr - self.calc_obstacle_map(ox, oy) - self.motion = self.get_motion_model() - - class Node: - def __init__(self, x, y, cost, pind, parent): - self.x = x # index of grid - self.y = y # index of grid - self.cost = cost - self.pind = pind - self.parent = parent - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.pind) - - def planning(self, sx, sy, gx, gy): - """ - Greedy Best-First search - - input: - s_x: start x position [m] - s_y: start y position [m] - gx: goal x position [m] - gy: goal y position [m] - - output: - rx: x position list of the final path - ry: y position list of the final path - """ - - nstart = self.Node(self.calc_xyindex(sx, self.minx), - self.calc_xyindex(sy, self.miny), 0.0, -1, None) - ngoal = self.Node(self.calc_xyindex(gx, self.minx), - self.calc_xyindex(gy, self.miny), 0.0, -1, None) - - open_set, closed_set = dict(), dict() - open_set[self.calc_grid_index(nstart)] = nstart - - while True: - if len(open_set) == 0: - print("Open set is empty..") - break - - c_id = min( - open_set, - key=lambda o: self.calc_heuristic(ngoal, open_set[o])) - - current = open_set[c_id] - - # show graph - if show_animation: # pragma: no cover - plt.plot(self.calc_grid_position(current.x, self.minx), - self.calc_grid_position(current.y, self.miny), "xc") - # 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]) - if len(closed_set.keys()) % 10 == 0: - plt.pause(0.001) - - # Remove the item from the open set - del open_set[c_id] - - # Add it to the closed set - closed_set[c_id] = current - - if current.x == ngoal.x and current.y == ngoal.y: - print("Found goal") - ngoal.pind = current.pind - ngoal.cost = current.cost - break - - # expand_grid search grid based on motion model - for i, _ in enumerate(self.motion): - node = self.Node(current.x + self.motion[i][0], - current.y + self.motion[i][1], - current.cost + self.motion[i][2], - c_id, current) - - n_id = self.calc_grid_index(node) - - # If the node is not safe, do nothing - if not self.verify_node(node): - continue - - if n_id in closed_set: - continue - - if n_id not in open_set: - open_set[n_id] = node - else: - if open_set[n_id].cost > node.cost: - open_set[n_id] = node - closed_set[ngoal.pind] = current - rx, ry = self.calc_final_path(ngoal, closed_set) - return rx, ry - - def calc_final_path(self, ngoal, closedset): - # generate final course - rx, ry = [self.calc_grid_position(ngoal.x, self.minx)], [ - self.calc_grid_position(ngoal.y, self.miny)] - n = closedset[ngoal.pind] - while n is not None: - rx.append(self.calc_grid_position(n.x, self.minx)) - ry.append(self.calc_grid_position(n.y, self.miny)) - n = n.parent - - return rx, ry - - @staticmethod - def calc_heuristic(n1, n2): - w = 1.0 # weight of heuristic - d = w * math.hypot(n1.x - n2.x, n1.y - n2.y) - return d - - def calc_grid_position(self, index, minp): - """ - calc grid position - - :param index: - :param minp: - :return: - """ - pos = index * self.reso + minp - return pos - - def calc_xyindex(self, position, min_pos): - return round((position - min_pos) / self.reso) - - def calc_grid_index(self, node): - return (node.y - self.miny) * self.xwidth + (node.x - self.minx) - - def verify_node(self, node): - px = self.calc_grid_position(node.x, self.minx) - py = self.calc_grid_position(node.y, self.miny) - - if px < self.minx: - return False - elif py < self.miny: - return False - elif px >= self.maxx: - return False - elif py >= self.maxy: - return False - - # collision check - if self.obmap[node.x][node.y]: - return False - - return True - - def calc_obstacle_map(self, ox, oy): - - self.minx = round(min(ox)) - self.miny = round(min(oy)) - self.maxx = round(max(ox)) - self.maxy = round(max(oy)) - print("min_x:", self.minx) - print("min_y:", self.miny) - print("max_x:", self.maxx) - print("max_y:", self.maxy) - - self.xwidth = round((self.maxx - self.minx) / self.reso) - self.ywidth = round((self.maxy - self.miny) / self.reso) - print("x_width:", self.xwidth) - print("y_width:", self.ywidth) - - # obstacle map generation - self.obmap = [[False for _ in range(self.ywidth)] - for _ in range(self.xwidth)] - for ix in range(self.xwidth): - x = self.calc_grid_position(ix, self.minx) - for iy in range(self.ywidth): - y = self.calc_grid_position(iy, self.miny) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= self.rr: - self.obmap[ix][iy] = True - break - - @staticmethod - def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - grid_size = 2.0 # [m] - robot_radius = 1.0 # [m] - - # set obstacle positions - ox, oy = [], [] - for i in range(-10, 60): - ox.append(i) - oy.append(-10.0) - for i in range(-10, 60): - ox.append(60.0) - oy.append(i) - for i in range(-10, 61): - ox.append(i) - oy.append(60.0) - for i in range(-10, 61): - ox.append(-10.0) - oy.append(i) - for i in range(-10, 40): - ox.append(20.0) - oy.append(i) - for i in range(0, 40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "og") - plt.plot(gx, gy, "xb") - plt.grid(True) - plt.axis("equal") - - greedybestfirst = BestFirstSearchPlanner(ox, oy, grid_size, robot_radius) - rx, ry = greedybestfirst.planning(sx, sy, gx, gy) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/GridBasedSweepCPP/grid_based_sweep_coverage_path_planner.py b/PathPlanning/GridBasedSweepCPP/grid_based_sweep_coverage_path_planner.py deleted file mode 100644 index 10ba98cd35..0000000000 --- a/PathPlanning/GridBasedSweepCPP/grid_based_sweep_coverage_path_planner.py +++ /dev/null @@ -1,321 +0,0 @@ -""" -Grid based sweep planner - -author: Atsushi Sakai -""" - -import math -from enum import IntEnum -import numpy as np -import matplotlib.pyplot as plt -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -from utils.angle import rot_mat_2d -from Mapping.grid_map_lib.grid_map_lib import GridMap, FloatGrid - -do_animation = True - - -class SweepSearcher: - class SweepDirection(IntEnum): - UP = 1 - DOWN = -1 - - class MovingDirection(IntEnum): - RIGHT = 1 - LEFT = -1 - - def __init__(self, - moving_direction, sweep_direction, x_inds_goal_y, goal_y): - self.moving_direction = moving_direction - self.sweep_direction = sweep_direction - self.turing_window = [] - self.update_turning_window() - self.x_indexes_goal_y = x_inds_goal_y - self.goal_y = goal_y - - def move_target_grid(self, c_x_index, c_y_index, grid_map): - n_x_index = self.moving_direction + c_x_index - n_y_index = c_y_index - - # found safe grid - if not self.check_occupied(n_x_index, n_y_index, grid_map): - return n_x_index, n_y_index - else: # occupied - next_c_x_index, next_c_y_index = self.find_safe_turning_grid( - c_x_index, c_y_index, grid_map) - if (next_c_x_index is None) and (next_c_y_index is None): - # moving backward - next_c_x_index = -self.moving_direction + c_x_index - next_c_y_index = c_y_index - if self.check_occupied(next_c_x_index, next_c_y_index, grid_map, FloatGrid(1.0)): - # moved backward, but the grid is occupied by obstacle - return None, None - else: - # keep moving until end - while not self.check_occupied(next_c_x_index + self.moving_direction, next_c_y_index, grid_map): - next_c_x_index += self.moving_direction - self.swap_moving_direction() - return next_c_x_index, next_c_y_index - - @staticmethod - def check_occupied(c_x_index, c_y_index, grid_map, occupied_val=FloatGrid(0.5)): - return grid_map.check_occupied_from_xy_index(c_x_index, c_y_index, occupied_val) - - def find_safe_turning_grid(self, c_x_index, c_y_index, grid_map): - - for (d_x_ind, d_y_ind) in self.turing_window: - - next_x_ind = d_x_ind + c_x_index - next_y_ind = d_y_ind + c_y_index - - # found safe grid - if not self.check_occupied(next_x_ind, next_y_ind, grid_map): - return next_x_ind, next_y_ind - - return None, None - - def is_search_done(self, grid_map): - for ix in self.x_indexes_goal_y: - if not self.check_occupied(ix, self.goal_y, grid_map): - return False - - # all lower grid is occupied - return True - - def update_turning_window(self): - # turning window definition - # robot can move grid based on it. - self.turing_window = [ - (self.moving_direction, 0.0), - (self.moving_direction, self.sweep_direction), - (0, self.sweep_direction), - (-self.moving_direction, self.sweep_direction), - ] - - def swap_moving_direction(self): - self.moving_direction *= -1 - self.update_turning_window() - - def search_start_grid(self, grid_map): - x_inds = [] - y_ind = 0 - if self.sweep_direction == self.SweepDirection.DOWN: - x_inds, y_ind = search_free_grid_index_at_edge_y( - grid_map, from_upper=True) - elif self.sweep_direction == self.SweepDirection.UP: - x_inds, y_ind = search_free_grid_index_at_edge_y( - grid_map, from_upper=False) - - if self.moving_direction == self.MovingDirection.RIGHT: - return min(x_inds), y_ind - elif self.moving_direction == self.MovingDirection.LEFT: - return max(x_inds), y_ind - - raise ValueError("self.moving direction is invalid ") - - -def find_sweep_direction_and_start_position(ox, oy): - # find sweep_direction - max_dist = 0.0 - vec = [0.0, 0.0] - sweep_start_pos = [0.0, 0.0] - for i in range(len(ox) - 1): - dx = ox[i + 1] - ox[i] - dy = oy[i + 1] - oy[i] - d = np.hypot(dx, dy) - - if d > max_dist: - max_dist = d - vec = [dx, dy] - sweep_start_pos = [ox[i], oy[i]] - - return vec, sweep_start_pos - - -def convert_grid_coordinate(ox, oy, sweep_vec, sweep_start_position): - tx = [ix - sweep_start_position[0] for ix in ox] - ty = [iy - sweep_start_position[1] for iy in oy] - th = math.atan2(sweep_vec[1], sweep_vec[0]) - converted_xy = np.stack([tx, ty]).T @ rot_mat_2d(th) - - return converted_xy[:, 0], converted_xy[:, 1] - - -def convert_global_coordinate(x, y, sweep_vec, sweep_start_position): - th = math.atan2(sweep_vec[1], sweep_vec[0]) - converted_xy = np.stack([x, y]).T @ rot_mat_2d(-th) - rx = [ix + sweep_start_position[0] for ix in converted_xy[:, 0]] - ry = [iy + sweep_start_position[1] for iy in converted_xy[:, 1]] - return rx, ry - - -def search_free_grid_index_at_edge_y(grid_map, from_upper=False): - y_index = None - x_indexes = [] - - if from_upper: - x_range = range(grid_map.height)[::-1] - y_range = range(grid_map.width)[::-1] - else: - x_range = range(grid_map.height) - y_range = range(grid_map.width) - - for iy in x_range: - for ix in y_range: - if not SweepSearcher.check_occupied(ix, iy, grid_map): - y_index = iy - x_indexes.append(ix) - if y_index: - break - - return x_indexes, y_index - - -def setup_grid_map(ox, oy, resolution, sweep_direction, offset_grid=10): - width = math.ceil((max(ox) - min(ox)) / resolution) + offset_grid - height = math.ceil((max(oy) - min(oy)) / resolution) + offset_grid - center_x = (np.max(ox) + np.min(ox)) / 2.0 - center_y = (np.max(oy) + np.min(oy)) / 2.0 - - grid_map = GridMap(width, height, resolution, center_x, center_y) - grid_map.print_grid_map_info() - grid_map.set_value_from_polygon(ox, oy, FloatGrid(1.0), inside=False) - grid_map.expand_grid() - - x_inds_goal_y = [] - goal_y = 0 - if sweep_direction == SweepSearcher.SweepDirection.UP: - x_inds_goal_y, goal_y = search_free_grid_index_at_edge_y( - grid_map, from_upper=True) - elif sweep_direction == SweepSearcher.SweepDirection.DOWN: - x_inds_goal_y, goal_y = search_free_grid_index_at_edge_y( - grid_map, from_upper=False) - - return grid_map, x_inds_goal_y, goal_y - - -def sweep_path_search(sweep_searcher, grid_map, grid_search_animation=False): - # search start grid - c_x_index, c_y_index = sweep_searcher.search_start_grid(grid_map) - if not grid_map.set_value_from_xy_index(c_x_index, c_y_index, FloatGrid(0.5)): - print("Cannot find start grid") - return [], [] - - x, y = grid_map.calc_grid_central_xy_position_from_xy_index(c_x_index, - c_y_index) - px, py = [x], [y] - - fig, ax = None, None - if grid_search_animation: - fig, ax = plt.subplots() - # for stopping simulation with the esc key. - fig.canvas.mpl_connect( - 'key_release_event', - lambda event: [exit(0) if event.key == 'escape' else None]) - - while True: - c_x_index, c_y_index = sweep_searcher.move_target_grid(c_x_index, - c_y_index, - grid_map) - - if sweep_searcher.is_search_done(grid_map) or ( - c_x_index is None or c_y_index is None): - print("Done") - break - - x, y = grid_map.calc_grid_central_xy_position_from_xy_index( - c_x_index, c_y_index) - - px.append(x) - py.append(y) - - grid_map.set_value_from_xy_index(c_x_index, c_y_index, FloatGrid(0.5)) - - if grid_search_animation: - grid_map.plot_grid_map(ax=ax) - plt.pause(1.0) - - return px, py - - -def planning(ox, oy, resolution, - moving_direction=SweepSearcher.MovingDirection.RIGHT, - sweeping_direction=SweepSearcher.SweepDirection.UP, - ): - sweep_vec, sweep_start_position = find_sweep_direction_and_start_position( - ox, oy) - - rox, roy = convert_grid_coordinate(ox, oy, sweep_vec, - sweep_start_position) - - grid_map, x_inds_goal_y, goal_y = setup_grid_map(rox, roy, resolution, - sweeping_direction) - - sweep_searcher = SweepSearcher(moving_direction, sweeping_direction, - x_inds_goal_y, goal_y) - - px, py = sweep_path_search(sweep_searcher, grid_map) - - rx, ry = convert_global_coordinate(px, py, sweep_vec, - sweep_start_position) - - print("Path length:", len(rx)) - - return rx, ry - - -def planning_animation(ox, oy, resolution): # pragma: no cover - px, py = planning(ox, oy, resolution) - - # animation - if do_animation: - for ipx, ipy in zip(px, py): - 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]) - plt.plot(ox, oy, "-xb") - plt.plot(px, py, "-r") - plt.plot(ipx, ipy, "or") - plt.axis("equal") - plt.grid(True) - plt.pause(0.1) - - plt.cla() - plt.plot(ox, oy, "-xb") - plt.plot(px, py, "-r") - plt.axis("equal") - plt.grid(True) - plt.pause(0.1) - plt.close() - - -def main(): # pragma: no cover - print("start!!") - - ox = [0.0, 20.0, 50.0, 100.0, 130.0, 40.0, 0.0] - oy = [0.0, -20.0, 0.0, 30.0, 60.0, 80.0, 0.0] - resolution = 5.0 - planning_animation(ox, oy, resolution) - - ox = [0.0, 50.0, 50.0, 0.0, 0.0] - oy = [0.0, 0.0, 30.0, 30.0, 0.0] - resolution = 1.3 - planning_animation(ox, oy, resolution) - - ox = [0.0, 20.0, 50.0, 200.0, 130.0, 40.0, 0.0] - oy = [0.0, -80.0, 0.0, 30.0, 60.0, 80.0, 0.0] - resolution = 5.0 - planning_animation(ox, oy, resolution) - - if do_animation: - plt.show() - print("done!!") - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/HybridAStar/__init__.py b/PathPlanning/HybridAStar/__init__.py deleted file mode 100644 index 087dab646e..0000000000 --- a/PathPlanning/HybridAStar/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import os -import sys - -sys.path.append(os.path.dirname(os.path.abspath(__file__))) diff --git a/PathPlanning/HybridAStar/car.py b/PathPlanning/HybridAStar/car.py deleted file mode 100644 index 959db74078..0000000000 --- a/PathPlanning/HybridAStar/car.py +++ /dev/null @@ -1,113 +0,0 @@ -""" - -Car model for Hybrid A* path planning - -author: Zheng Zh (@Zhengzh) - -""" - -import sys -import pathlib -root_dir = pathlib.Path(__file__).parent.parent.parent -sys.path.append(str(root_dir)) - -from math import cos, sin, tan, pi - -import matplotlib.pyplot as plt -import numpy as np - -from utils.angle import rot_mat_2d - -WB = 3.0 # rear to front wheel -W = 2.0 # width of car -LF = 3.3 # distance from rear to vehicle front end -LB = 1.0 # distance from rear to vehicle back end -MAX_STEER = 0.6 # [rad] maximum steering angle - -BUBBLE_DIST = (LF - LB) / 2.0 # distance from rear to center of vehicle. -BUBBLE_R = np.hypot((LF + LB) / 2.0, W / 2.0) # bubble radius - -# vehicle rectangle vertices -VRX = [LF, LF, -LB, -LB, LF] -VRY = [W / 2, -W / 2, -W / 2, W / 2, W / 2] - - -def check_car_collision(x_list, y_list, yaw_list, ox, oy, kd_tree): - for i_x, i_y, i_yaw in zip(x_list, y_list, yaw_list): - cx = i_x + BUBBLE_DIST * cos(i_yaw) - cy = i_y + BUBBLE_DIST * sin(i_yaw) - - ids = kd_tree.query_ball_point([cx, cy], BUBBLE_R) - - if not ids: - continue - - if not rectangle_check(i_x, i_y, i_yaw, - [ox[i] for i in ids], [oy[i] for i in ids]): - return False # collision - - return True # no collision - - -def rectangle_check(x, y, yaw, ox, oy): - # transform obstacles to base link frame - rot = rot_mat_2d(yaw) - for iox, ioy in zip(ox, oy): - tx = iox - x - ty = ioy - y - converted_xy = np.stack([tx, ty]).T @ rot - rx, ry = converted_xy[0], converted_xy[1] - - if not (rx > LF or rx < -LB or ry > W / 2.0 or ry < -W / 2.0): - return False # collision - - return True # no collision - - -def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): - """Plot arrow.""" - if not isinstance(x, float): - for (i_x, i_y, i_yaw) in zip(x, y, yaw): - plot_arrow(i_x, i_y, i_yaw) - else: - plt.arrow(x, y, length * cos(yaw), length * sin(yaw), - fc=fc, ec=ec, head_width=width, head_length=width, alpha=0.4) - - -def plot_car(x, y, yaw): - car_color = '-k' - c, s = cos(yaw), sin(yaw) - rot = rot_mat_2d(-yaw) - car_outline_x, car_outline_y = [], [] - for rx, ry in zip(VRX, VRY): - converted_xy = np.stack([rx, ry]).T @ rot - car_outline_x.append(converted_xy[0]+x) - car_outline_y.append(converted_xy[1]+y) - - arrow_x, arrow_y, arrow_yaw = c * 1.5 + x, s * 1.5 + y, yaw - plot_arrow(arrow_x, arrow_y, arrow_yaw) - - plt.plot(car_outline_x, car_outline_y, car_color) - - -def pi_2_pi(angle): - return (angle + pi) % (2 * pi) - pi - - -def move(x, y, yaw, distance, steer, L=WB): - x += distance * cos(yaw) - y += distance * sin(yaw) - yaw = pi_2_pi(yaw + distance * tan(steer) / L) # distance/2 - - return x, y, yaw - - -def main(): - x, y, yaw = 0., 0., 1. - plt.axis('equal') - plot_car(x, y, yaw) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/HybridAStar/dynamic_programming_heuristic.py b/PathPlanning/HybridAStar/dynamic_programming_heuristic.py deleted file mode 100644 index 09bcd556a6..0000000000 --- a/PathPlanning/HybridAStar/dynamic_programming_heuristic.py +++ /dev/null @@ -1,176 +0,0 @@ -""" - -A* grid based planning - -author: Nikos Kanargias (nkana@tee.gr) - -See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm) - -""" - -import heapq -import math - -import matplotlib.pyplot as plt - -show_animation = False - - -class Node: - - def __init__(self, x, y, cost, parent_index): - self.x = x - self.y = y - self.cost = cost - self.parent_index = parent_index - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent_index) - - -def calc_final_path(goal_node, closed_node_set, resolution): - # generate final course - rx, ry = [goal_node.x * resolution], [goal_node.y * resolution] - parent_index = goal_node.parent_index - while parent_index != -1: - n = closed_node_set[parent_index] - rx.append(n.x * resolution) - ry.append(n.y * resolution) - parent_index = n.parent_index - - return rx, ry - - -def calc_distance_heuristic(gx, gy, ox, oy, resolution, rr): - """ - gx: goal x position [m] - gx: goal x position [m] - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - resolution: grid resolution [m] - rr: robot radius[m] - """ - - goal_node = Node(round(gx / resolution), round(gy / resolution), 0.0, -1) - ox = [iox / resolution for iox in ox] - oy = [ioy / resolution for ioy in oy] - - obstacle_map, min_x, min_y, max_x, max_y, x_w, y_w = calc_obstacle_map( - ox, oy, resolution, rr) - - motion = get_motion_model() - - open_set, closed_set = dict(), dict() - open_set[calc_index(goal_node, x_w, min_x, min_y)] = goal_node - priority_queue = [(0, calc_index(goal_node, x_w, min_x, min_y))] - - while True: - if not priority_queue: - break - cost, c_id = heapq.heappop(priority_queue) - if c_id in open_set: - current = open_set[c_id] - closed_set[c_id] = current - open_set.pop(c_id) - else: - continue - - # show graph - if show_animation: # pragma: no cover - plt.plot(current.x * resolution, current.y * resolution, "xc") - # 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]) - if len(closed_set.keys()) % 10 == 0: - plt.pause(0.001) - - # Remove the item from the open set - - # expand search grid based on motion model - for i, _ in enumerate(motion): - node = Node(current.x + motion[i][0], - current.y + motion[i][1], - current.cost + motion[i][2], c_id) - n_id = calc_index(node, x_w, min_x, min_y) - - if n_id in closed_set: - continue - - if not verify_node(node, obstacle_map, min_x, min_y, max_x, max_y): - continue - - if n_id not in open_set: - open_set[n_id] = node # Discover a new node - heapq.heappush( - priority_queue, - (node.cost, calc_index(node, x_w, min_x, min_y))) - else: - if open_set[n_id].cost >= node.cost: - # This path is the best until now. record it! - open_set[n_id] = node - heapq.heappush( - priority_queue, - (node.cost, calc_index(node, x_w, min_x, min_y))) - - return closed_set - - -def verify_node(node, obstacle_map, min_x, min_y, max_x, max_y): - if node.x < min_x: - return False - elif node.y < min_y: - return False - elif node.x >= max_x: - return False - elif node.y >= max_y: - return False - - if obstacle_map[node.x][node.y]: - return False - - return True - - -def calc_obstacle_map(ox, oy, resolution, vr): - min_x = round(min(ox)) - min_y = round(min(oy)) - max_x = round(max(ox)) - max_y = round(max(oy)) - - x_width = round(max_x - min_x) - y_width = round(max_y - min_y) - - # obstacle map generation - obstacle_map = [[False for _ in range(y_width)] for _ in range(x_width)] - for ix in range(x_width): - x = ix + min_x - for iy in range(y_width): - y = iy + min_y - # print(x, y) - for iox, ioy in zip(ox, oy): - d = math.hypot(iox - x, ioy - y) - if d <= vr / resolution: - obstacle_map[ix][iy] = True - break - - return obstacle_map, min_x, min_y, max_x, max_y, x_width, y_width - - -def calc_index(node, x_width, x_min, y_min): - return (node.y - y_min) * x_width + (node.x - x_min) - - -def get_motion_model(): - # dx, dy, cost - motion = [[1, 0, 1], - [0, 1, 1], - [-1, 0, 1], - [0, -1, 1], - [-1, -1, math.sqrt(2)], - [-1, 1, math.sqrt(2)], - [1, -1, math.sqrt(2)], - [1, 1, math.sqrt(2)]] - - return motion diff --git a/PathPlanning/HybridAStar/hybrid_a_star.py b/PathPlanning/HybridAStar/hybrid_a_star.py deleted file mode 100644 index 0fa04189c6..0000000000 --- a/PathPlanning/HybridAStar/hybrid_a_star.py +++ /dev/null @@ -1,441 +0,0 @@ -""" - -Hybrid A* path planning - -author: Zheng Zh (@Zhengzh) - -""" - -import heapq -import math -import matplotlib.pyplot as plt -import numpy as np -from scipy.spatial import cKDTree -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from dynamic_programming_heuristic import calc_distance_heuristic -from ReedsSheppPath import reeds_shepp_path_planning as rs -from car import move, check_car_collision, MAX_STEER, WB, plot_car, BUBBLE_R - -XY_GRID_RESOLUTION = 2.0 # [m] -YAW_GRID_RESOLUTION = np.deg2rad(15.0) # [rad] -MOTION_RESOLUTION = 0.1 # [m] path interpolate resolution -N_STEER = 20 # number of steer command - -SB_COST = 100.0 # switch back penalty cost -BACK_COST = 5.0 # backward penalty cost -STEER_CHANGE_COST = 5.0 # steer angle change penalty cost -STEER_COST = 1.0 # steer angle change penalty cost -H_COST = 5.0 # Heuristic cost - -show_animation = True - - -class Node: - - def __init__(self, x_ind, y_ind, yaw_ind, direction, - x_list, y_list, yaw_list, directions, - steer=0.0, parent_index=None, cost=None): - self.x_index = x_ind - self.y_index = y_ind - self.yaw_index = yaw_ind - self.direction = direction - self.x_list = x_list - self.y_list = y_list - self.yaw_list = yaw_list - self.directions = directions - self.steer = steer - self.parent_index = parent_index - self.cost = cost - - -class Path: - - def __init__(self, x_list, y_list, yaw_list, direction_list, cost): - self.x_list = x_list - self.y_list = y_list - self.yaw_list = yaw_list - self.direction_list = direction_list - self.cost = cost - - -class Config: - - def __init__(self, ox, oy, xy_resolution, yaw_resolution): - min_x_m = min(ox) - min_y_m = min(oy) - max_x_m = max(ox) - max_y_m = max(oy) - - ox.append(min_x_m) - oy.append(min_y_m) - ox.append(max_x_m) - oy.append(max_y_m) - - self.min_x = round(min_x_m / xy_resolution) - self.min_y = round(min_y_m / xy_resolution) - self.max_x = round(max_x_m / xy_resolution) - self.max_y = round(max_y_m / xy_resolution) - - self.x_w = round(self.max_x - self.min_x) - self.y_w = round(self.max_y - self.min_y) - - self.min_yaw = round(- math.pi / yaw_resolution) - 1 - self.max_yaw = round(math.pi / yaw_resolution) - self.yaw_w = round(self.max_yaw - self.min_yaw) - - -def calc_motion_inputs(): - for steer in np.concatenate((np.linspace(-MAX_STEER, MAX_STEER, - N_STEER), [0.0])): - for d in [1, -1]: - yield [steer, d] - - -def get_neighbors(current, config, ox, oy, kd_tree): - for steer, d in calc_motion_inputs(): - node = calc_next_node(current, steer, d, config, ox, oy, kd_tree) - if node and verify_index(node, config): - yield node - - -def calc_next_node(current, steer, direction, config, ox, oy, kd_tree): - x, y, yaw = current.x_list[-1], current.y_list[-1], current.yaw_list[-1] - - arc_l = XY_GRID_RESOLUTION * 1.5 - x_list, y_list, yaw_list, direction_list = [], [], [], [] - for _ in np.arange(0, arc_l, MOTION_RESOLUTION): - x, y, yaw = move(x, y, yaw, MOTION_RESOLUTION * direction, steer) - x_list.append(x) - y_list.append(y) - yaw_list.append(yaw) - direction_list.append(direction == 1) - - if not check_car_collision(x_list, y_list, yaw_list, ox, oy, kd_tree): - return None - - d = direction == 1 - x_ind = round(x / XY_GRID_RESOLUTION) - y_ind = round(y / XY_GRID_RESOLUTION) - yaw_ind = round(yaw / YAW_GRID_RESOLUTION) - - added_cost = 0.0 - - if d != current.direction: - added_cost += SB_COST - - # steer penalty - added_cost += STEER_COST * abs(steer) - - # steer change penalty - added_cost += STEER_CHANGE_COST * abs(current.steer - steer) - - cost = current.cost + added_cost + arc_l - - node = Node(x_ind, y_ind, yaw_ind, d, x_list, - y_list, yaw_list, direction_list, - parent_index=calc_index(current, config), - cost=cost, steer=steer) - - return node - - -def is_same_grid(n1, n2): - if n1.x_index == n2.x_index \ - and n1.y_index == n2.y_index \ - and n1.yaw_index == n2.yaw_index: - return True - return False - - -def analytic_expansion(current, goal, ox, oy, kd_tree): - start_x = current.x_list[-1] - start_y = current.y_list[-1] - start_yaw = current.yaw_list[-1] - - goal_x = goal.x_list[-1] - goal_y = goal.y_list[-1] - goal_yaw = goal.yaw_list[-1] - - max_curvature = math.tan(MAX_STEER) / WB - paths = rs.calc_paths(start_x, start_y, start_yaw, - goal_x, goal_y, goal_yaw, - max_curvature, step_size=MOTION_RESOLUTION) - - if not paths: - return None - - best_path, best = None, None - - for path in paths: - if check_car_collision(path.x, path.y, path.yaw, ox, oy, kd_tree): - cost = calc_rs_path_cost(path) - if not best or best > cost: - best = cost - best_path = path - - return best_path - - -def update_node_with_analytic_expansion(current, goal, - c, ox, oy, kd_tree): - path = analytic_expansion(current, goal, ox, oy, kd_tree) - - if path: - if show_animation: - plt.plot(path.x, path.y) - f_x = path.x[1:] - f_y = path.y[1:] - f_yaw = path.yaw[1:] - - f_cost = current.cost + calc_rs_path_cost(path) - f_parent_index = calc_index(current, c) - - fd = [] - for d in path.directions[1:]: - fd.append(d >= 0) - - f_steer = 0.0 - f_path = Node(current.x_index, current.y_index, current.yaw_index, - current.direction, f_x, f_y, f_yaw, fd, - cost=f_cost, parent_index=f_parent_index, steer=f_steer) - return True, f_path - - return False, None - - -def calc_rs_path_cost(reed_shepp_path): - cost = 0.0 - for length in reed_shepp_path.lengths: - if length >= 0: # forward - cost += length - else: # back - cost += abs(length) * BACK_COST - - # switch back penalty - for i in range(len(reed_shepp_path.lengths) - 1): - # switch back - if reed_shepp_path.lengths[i] * reed_shepp_path.lengths[i + 1] < 0.0: - cost += SB_COST - - # steer penalty - for course_type in reed_shepp_path.ctypes: - if course_type != "S": # curve - cost += STEER_COST * abs(MAX_STEER) - - # ==steer change penalty - # calc steer profile - n_ctypes = len(reed_shepp_path.ctypes) - u_list = [0.0] * n_ctypes - for i in range(n_ctypes): - if reed_shepp_path.ctypes[i] == "R": - u_list[i] = - MAX_STEER - elif reed_shepp_path.ctypes[i] == "L": - u_list[i] = MAX_STEER - - for i in range(len(reed_shepp_path.ctypes) - 1): - cost += STEER_CHANGE_COST * abs(u_list[i + 1] - u_list[i]) - - return cost - - -def hybrid_a_star_planning(start, goal, ox, oy, xy_resolution, yaw_resolution): - """ - start: start node - goal: goal node - ox: x position list of Obstacles [m] - oy: y position list of Obstacles [m] - xy_resolution: grid resolution [m] - yaw_resolution: yaw angle resolution [rad] - """ - - start[2], goal[2] = rs.pi_2_pi(start[2]), rs.pi_2_pi(goal[2]) - tox, toy = ox[:], oy[:] - - obstacle_kd_tree = cKDTree(np.vstack((tox, toy)).T) - - config = Config(tox, toy, xy_resolution, yaw_resolution) - - start_node = Node(round(start[0] / xy_resolution), - round(start[1] / xy_resolution), - round(start[2] / yaw_resolution), True, - [start[0]], [start[1]], [start[2]], [True], cost=0) - goal_node = Node(round(goal[0] / xy_resolution), - round(goal[1] / xy_resolution), - round(goal[2] / yaw_resolution), True, - [goal[0]], [goal[1]], [goal[2]], [True]) - - openList, closedList = {}, {} - - h_dp = calc_distance_heuristic( - goal_node.x_list[-1], goal_node.y_list[-1], - ox, oy, xy_resolution, BUBBLE_R) - - pq = [] - openList[calc_index(start_node, config)] = start_node - heapq.heappush(pq, (calc_cost(start_node, h_dp, config), - calc_index(start_node, config))) - final_path = None - - while True: - if not openList: - print("Error: Cannot find path, No open set") - return Path([], [], [], [], 0) - - cost, c_id = heapq.heappop(pq) - if c_id in openList: - current = openList.pop(c_id) - closedList[c_id] = current - else: - continue - - if show_animation: # pragma: no cover - plt.plot(current.x_list[-1], current.y_list[-1], "xc") - # 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]) - if len(closedList.keys()) % 10 == 0: - plt.pause(0.001) - - is_updated, final_path = update_node_with_analytic_expansion( - current, goal_node, config, ox, oy, obstacle_kd_tree) - - if is_updated: - print("path found") - break - - for neighbor in get_neighbors(current, config, ox, oy, - obstacle_kd_tree): - neighbor_index = calc_index(neighbor, config) - if neighbor_index in closedList: - continue - if neighbor_index not in openList \ - or openList[neighbor_index].cost > neighbor.cost: - heapq.heappush( - pq, (calc_cost(neighbor, h_dp, config), - neighbor_index)) - openList[neighbor_index] = neighbor - - path = get_final_path(closedList, final_path) - return path - - -def calc_cost(n, h_dp, c): - ind = (n.y_index - c.min_y) * c.x_w + (n.x_index - c.min_x) - if ind not in h_dp: - return n.cost + 999999999 # collision cost - return n.cost + H_COST * h_dp[ind].cost - - -def get_final_path(closed, goal_node): - reversed_x, reversed_y, reversed_yaw = \ - list(reversed(goal_node.x_list)), list(reversed(goal_node.y_list)), \ - list(reversed(goal_node.yaw_list)) - direction = list(reversed(goal_node.directions)) - nid = goal_node.parent_index - final_cost = goal_node.cost - - while nid: - n = closed[nid] - reversed_x.extend(list(reversed(n.x_list))) - reversed_y.extend(list(reversed(n.y_list))) - reversed_yaw.extend(list(reversed(n.yaw_list))) - direction.extend(list(reversed(n.directions))) - - nid = n.parent_index - - reversed_x = list(reversed(reversed_x)) - reversed_y = list(reversed(reversed_y)) - reversed_yaw = list(reversed(reversed_yaw)) - direction = list(reversed(direction)) - - # adjust first direction - direction[0] = direction[1] - - path = Path(reversed_x, reversed_y, reversed_yaw, direction, final_cost) - - return path - - -def verify_index(node, c): - x_ind, y_ind = node.x_index, node.y_index - if c.min_x <= x_ind <= c.max_x and c.min_y <= y_ind <= c.max_y: - return True - - return False - - -def calc_index(node, c): - ind = (node.yaw_index - c.min_yaw) * c.x_w * c.y_w + \ - (node.y_index - c.min_y) * c.x_w + (node.x_index - c.min_x) - - if ind <= 0: - print("Error(calc_index):", ind) - - return ind - - -def main(): - print("Start Hybrid A* planning") - - ox, oy = [], [] - - for i in range(60): - ox.append(i) - oy.append(0.0) - for i in range(60): - ox.append(60.0) - oy.append(i) - for i in range(61): - ox.append(i) - oy.append(60.0) - for i in range(61): - ox.append(0.0) - oy.append(i) - for i in range(40): - ox.append(20.0) - oy.append(i) - for i in range(40): - ox.append(40.0) - oy.append(60.0 - i) - - # Set Initial parameters - start = [10.0, 10.0, np.deg2rad(90.0)] - goal = [50.0, 50.0, np.deg2rad(-90.0)] - - print("start : ", start) - print("goal : ", goal) - - if show_animation: - plt.plot(ox, oy, ".k") - rs.plot_arrow(start[0], start[1], start[2], fc='g') - rs.plot_arrow(goal[0], goal[1], goal[2]) - - plt.grid(True) - plt.axis("equal") - - path = hybrid_a_star_planning( - start, goal, ox, oy, XY_GRID_RESOLUTION, YAW_GRID_RESOLUTION) - - x = path.x_list - y = path.y_list - yaw = path.yaw_list - - if show_animation: - for i_x, i_y, i_yaw in zip(x, y, yaw): - plt.cla() - plt.plot(ox, oy, ".k") - plt.plot(x, y, "-r", label="Hybrid A* path") - plt.grid(True) - plt.axis("equal") - plot_car(i_x, i_y, i_yaw) - plt.pause(0.0001) - - print(__file__ + " done!!") - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/InformedRRTStar/informed_rrt_star.py b/PathPlanning/InformedRRTStar/informed_rrt_star.py deleted file mode 100644 index 0483949c99..0000000000 --- a/PathPlanning/InformedRRTStar/informed_rrt_star.py +++ /dev/null @@ -1,350 +0,0 @@ -""" -Informed RRT* path planning - -author: Karan Chawla - Atsushi Sakai(@Atsushi_twi) - -Reference: Informed RRT*: Optimal Sampling-based Path planning Focused via -Direct Sampling of an Admissible Ellipsoidal Heuristic -https://arxiv.org/pdf/1404.2334 - -""" -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import copy -import math -import random - -import matplotlib.pyplot as plt -import numpy as np - -from utils.angle import rot_mat_2d - -show_animation = True - - -class InformedRRTStar: - - def __init__(self, start, goal, obstacle_list, rand_area, expand_dis=0.5, - goal_sample_rate=10, max_iter=200): - - self.start = Node(start[0], start[1]) - self.goal = Node(goal[0], goal[1]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.expand_dis = expand_dis - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - self.node_list = None - - def informed_rrt_star_search(self, animation=True): - - self.node_list = [self.start] - # max length we expect to find in our 'informed' sample space, - # starts as infinite - c_best = float('inf') - solution_set = set() - path = None - - # Computing the sampling space - c_min = math.hypot(self.start.x - self.goal.x, - self.start.y - self.goal.y) - x_center = np.array([[(self.start.x + self.goal.x) / 2.0], - [(self.start.y + self.goal.y) / 2.0], [0]]) - a1 = np.array([[(self.goal.x - self.start.x) / c_min], - [(self.goal.y - self.start.y) / c_min], [0]]) - - e_theta = math.atan2(a1[1, 0], a1[0, 0]) - # first column of identity matrix transposed - id1_t = np.array([1.0, 0.0, 0.0]).reshape(1, 3) - m = a1 @ id1_t - u, s, vh = np.linalg.svd(m, True, True) - c = u @ np.diag( - [1.0, 1.0, - np.linalg.det(u) * np.linalg.det(np.transpose(vh))]) @ vh - - for i in range(self.max_iter): - # Sample space is defined by c_best - # c_min is the minimum distance between the start point and - # the goal x_center is the midpoint between the start and the - # goal c_best changes when a new path is found - - rnd = self.informed_sample(c_best, c_min, x_center, c) - n_ind = self.get_nearest_list_index(self.node_list, rnd) - nearest_node = self.node_list[n_ind] - # steer - theta = math.atan2(rnd[1] - nearest_node.y, - rnd[0] - nearest_node.x) - new_node = self.get_new_node(theta, n_ind, nearest_node) - d = self.line_cost(nearest_node, new_node) - - no_collision = self.check_collision(nearest_node, theta, d) - - if no_collision: - near_inds = self.find_near_nodes(new_node) - new_node = self.choose_parent(new_node, near_inds) - - self.node_list.append(new_node) - self.rewire(new_node, near_inds) - - if self.is_near_goal(new_node): - if self.check_segment_collision(new_node.x, new_node.y, - self.goal.x, self.goal.y): - solution_set.add(new_node) - last_index = len(self.node_list) - 1 - temp_path = self.get_final_course(last_index) - temp_path_len = self.get_path_len(temp_path) - if temp_path_len < c_best: - path = temp_path - c_best = temp_path_len - if animation: - self.draw_graph(x_center=x_center, c_best=c_best, c_min=c_min, - e_theta=e_theta, rnd=rnd) - - return path - - def choose_parent(self, new_node, near_inds): - if len(near_inds) == 0: - return new_node - - d_list = [] - for i in near_inds: - dx = new_node.x - self.node_list[i].x - dy = new_node.y - self.node_list[i].y - d = math.hypot(dx, dy) - theta = math.atan2(dy, dx) - if self.check_collision(self.node_list[i], theta, d): - d_list.append(self.node_list[i].cost + d) - else: - d_list.append(float('inf')) - - min_cost = min(d_list) - min_ind = near_inds[d_list.index(min_cost)] - - if min_cost == float('inf'): - print("min cost is inf") - return new_node - - new_node.cost = min_cost - new_node.parent = min_ind - - return new_node - - def find_near_nodes(self, new_node): - n_node = len(self.node_list) - r = 50.0 * math.sqrt(math.log(n_node) / n_node) - d_list = [(node.x - new_node.x) ** 2 + (node.y - new_node.y) ** 2 for - node in self.node_list] - near_inds = [d_list.index(i) for i in d_list if i <= r ** 2] - return near_inds - - def informed_sample(self, c_max, c_min, x_center, c): - if c_max < float('inf'): - r = [c_max / 2.0, math.sqrt(c_max ** 2 - c_min ** 2) / 2.0, - math.sqrt(c_max ** 2 - c_min ** 2) / 2.0] - rl = np.diag(r) - x_ball = self.sample_unit_ball() - rnd = np.dot(np.dot(c, rl), x_ball) + x_center - rnd = [rnd[(0, 0)], rnd[(1, 0)]] - else: - rnd = self.sample_free_space() - - return rnd - - @staticmethod - def sample_unit_ball(): - a = random.random() - b = random.random() - - if b < a: - a, b = b, a - - sample = (b * math.cos(2 * math.pi * a / b), - b * math.sin(2 * math.pi * a / b)) - return np.array([[sample[0]], [sample[1]], [0]]) - - def sample_free_space(self): - if random.randint(0, 100) > self.goal_sample_rate: - rnd = [random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand)] - else: - rnd = [self.goal.x, self.goal.y] - - return rnd - - @staticmethod - def get_path_len(path): - path_len = 0 - for i in range(1, len(path)): - node1_x = path[i][0] - node1_y = path[i][1] - node2_x = path[i - 1][0] - node2_y = path[i - 1][1] - path_len += math.hypot(node1_x - node2_x, node1_y - node2_y) - - return path_len - - @staticmethod - def line_cost(node1, node2): - return math.hypot(node1.x - node2.x, node1.y - node2.y) - - @staticmethod - def get_nearest_list_index(nodes, rnd): - d_list = [(node.x - rnd[0]) ** 2 + (node.y - rnd[1]) ** 2 for node in - nodes] - min_index = d_list.index(min(d_list)) - return min_index - - def get_new_node(self, theta, n_ind, nearest_node): - new_node = copy.deepcopy(nearest_node) - - new_node.x += self.expand_dis * math.cos(theta) - new_node.y += self.expand_dis * math.sin(theta) - - new_node.cost += self.expand_dis - new_node.parent = n_ind - return new_node - - def is_near_goal(self, node): - d = self.line_cost(node, self.goal) - if d < self.expand_dis: - return True - return False - - def rewire(self, new_node, near_inds): - n_node = len(self.node_list) - for i in near_inds: - near_node = self.node_list[i] - - d = math.hypot(near_node.x - new_node.x, near_node.y - new_node.y) - - s_cost = new_node.cost + d - - if near_node.cost > s_cost: - theta = math.atan2(new_node.y - near_node.y, - new_node.x - near_node.x) - if self.check_collision(near_node, theta, d): - near_node.parent = n_node - 1 - near_node.cost = s_cost - - @staticmethod - def distance_squared_point_to_segment(v, w, p): - # Return minimum distance between line segment vw and point p - if np.array_equal(v, w): - return (p - v).dot(p - v) # v == w case - l2 = (w - v).dot(w - v) # i.e. |w-v|^2 - avoid a sqrt - # Consider the line extending the segment, - # parameterized as v + t (w - v). - # We find projection of point p onto the line. - # It falls where t = [(p-v) . (w-v)] / |w-v|^2 - # We clamp t from [0,1] to handle points outside the segment vw. - t = max(0, min(1, (p - v).dot(w - v) / l2)) - projection = v + t * (w - v) # Projection falls on the segment - return (p - projection).dot(p - projection) - - def check_segment_collision(self, x1, y1, x2, y2): - for (ox, oy, size) in self.obstacle_list: - dd = self.distance_squared_point_to_segment( - np.array([x1, y1]), np.array([x2, y2]), np.array([ox, oy])) - if dd <= size ** 2: - return False # collision - return True - - def check_collision(self, near_node, theta, d): - tmp_node = copy.deepcopy(near_node) - end_x = tmp_node.x + math.cos(theta) * d - end_y = tmp_node.y + math.sin(theta) * d - return self.check_segment_collision(tmp_node.x, tmp_node.y, - end_x, end_y) - - def get_final_course(self, last_index): - path = [[self.goal.x, self.goal.y]] - while self.node_list[last_index].parent is not None: - node = self.node_list[last_index] - path.append([node.x, node.y]) - last_index = node.parent - path.append([self.start.x, self.start.y]) - return path - - def draw_graph(self, x_center=None, c_best=None, c_min=None, e_theta=None, - rnd=None): - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd[0], rnd[1], "^k") - if c_best != float('inf'): - self.plot_ellipse(x_center, c_best, c_min, e_theta) - - for node in self.node_list: - if node.parent is not None: - if node.x or node.y is not None: - plt.plot([node.x, self.node_list[node.parent].x], - [node.y, self.node_list[node.parent].y], "-g") - - for (ox, oy, size) in self.obstacle_list: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.goal.x, self.goal.y, "xr") - plt.axis([self.min_rand, self.max_rand, self.min_rand, self.max_rand]) - plt.grid(True) - plt.pause(0.01) - - @staticmethod - def plot_ellipse(x_center, c_best, c_min, e_theta): # pragma: no cover - - a = math.sqrt(c_best ** 2 - c_min ** 2) / 2.0 - b = c_best / 2.0 - angle = math.pi / 2.0 - e_theta - cx = x_center[0] - cy = x_center[1] - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - x = [a * math.cos(it) for it in t] - y = [b * math.sin(it) for it in t] - fx = rot_mat_2d(-angle) @ np.array([x, y]) - px = np.array(fx[0, :] + cx).flatten() - py = np.array(fx[1, :] + cy).flatten() - plt.plot(cx, cy, "xc") - plt.plot(px, py, "--c") - - -class Node: - - def __init__(self, x, y): - self.x = x - self.y = y - self.cost = 0.0 - self.parent = None - - -def main(): - print("Start informed rrt star planning") - - # create obstacles - obstacle_list = [(5, 5, 0.5), (9, 6, 1), (7, 5, 1), (1, 5, 1), (3, 6, 1), - (7, 9, 1)] - - # Set params - rrt = InformedRRTStar(start=[0, 0], goal=[5, 10], rand_area=[-2, 15], - obstacle_list=obstacle_list) - path = rrt.informed_rrt_star_search(animation=show_animation) - print("Done!!") - - # Plot path - if show_animation: - rrt.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.01) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/LQRPlanner/lqr_planner.py b/PathPlanning/LQRPlanner/lqr_planner.py deleted file mode 100644 index 0f58f93ea3..0000000000 --- a/PathPlanning/LQRPlanner/lqr_planner.py +++ /dev/null @@ -1,146 +0,0 @@ -""" - -LQR local path planning - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import random - -import matplotlib.pyplot as plt -import numpy as np -import scipy.linalg as la - -SHOW_ANIMATION = True - - -class LQRPlanner: - - def __init__(self): - self.MAX_TIME = 100.0 # Maximum simulation time - self.DT = 0.1 # Time tick - self.GOAL_DIST = 0.1 - self.MAX_ITER = 150 - self.EPS = 0.01 - - def lqr_planning(self, sx, sy, gx, gy, show_animation=True): - - rx, ry = [sx], [sy] - - x = np.array([sx - gx, sy - gy]).reshape(2, 1) # State vector - - # Linear system model - A, B = self.get_system_model() - - found_path = False - - time = 0.0 - while time <= self.MAX_TIME: - time += self.DT - - u = self.lqr_control(A, B, x) - - x = A @ x + B @ u - - rx.append(x[0, 0] + gx) - ry.append(x[1, 0] + gy) - - d = math.hypot(gx - rx[-1], gy - ry[-1]) - if d <= self.GOAL_DIST: - found_path = True - break - - # animation - if show_animation: # pragma: no cover - # 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]) - plt.plot(sx, sy, "or") - plt.plot(gx, gy, "ob") - plt.plot(rx, ry, "-r") - plt.axis("equal") - plt.pause(1.0) - - if not found_path: - print("Cannot found path") - return [], [] - - return rx, ry - - def solve_dare(self, A, B, Q, R): - """ - solve a discrete time_Algebraic Riccati equation (DARE) - """ - X, Xn = Q, Q - - for i in range(self.MAX_ITER): - Xn = A.T * X * A - A.T * X * B * \ - la.inv(R + B.T * X * B) * B.T * X * A + Q - if (abs(Xn - X)).max() < self.EPS: - break - X = Xn - - return Xn - - def dlqr(self, A, B, Q, R): - """Solve the discrete time lqr controller. - x[k+1] = A x[k] + B u[k] - cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k] - # ref Bertsekas, p.151 - """ - - # first, try to solve the ricatti equation - X = self.solve_dare(A, B, Q, R) - - # compute the LQR gain - K = la.inv(B.T @ X @ B + R) @ (B.T @ X @ A) - - eigValues = la.eigvals(A - B @ K) - - return K, X, eigValues - - def get_system_model(self): - - A = np.array([[self.DT, 1.0], - [0.0, self.DT]]) - B = np.array([0.0, 1.0]).reshape(2, 1) - - return A, B - - def lqr_control(self, A, B, x): - - Kopt, X, ev = self.dlqr(A, B, np.eye(2), np.eye(1)) - - u = -Kopt @ x - - return u - - -def main(): - print(__file__ + " start!!") - - ntest = 10 # number of goal - area = 100.0 # sampling area - - lqr_planner = LQRPlanner() - - for i in range(ntest): - sx = 6.0 - sy = 6.0 - gx = random.uniform(-area, area) - gy = random.uniform(-area, area) - - rx, ry = lqr_planner.lqr_planning(sx, sy, gx, gy, show_animation=SHOW_ANIMATION) - - if SHOW_ANIMATION: # pragma: no cover - plt.plot(sx, sy, "or") - plt.plot(gx, gy, "ob") - plt.plot(rx, ry, "-r") - plt.axis("equal") - plt.pause(1.0) - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/LQRRRTStar/lqr_rrt_star.py b/PathPlanning/LQRRRTStar/lqr_rrt_star.py deleted file mode 100644 index 0ed08123ea..0000000000 --- a/PathPlanning/LQRRRTStar/lqr_rrt_star.py +++ /dev/null @@ -1,241 +0,0 @@ -""" - -Path planning code with LQR RRT* - -author: AtsushiSakai(@Atsushi_twi) - -""" -import copy -import math -import random -import matplotlib.pyplot as plt -import numpy as np -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from LQRPlanner.lqr_planner import LQRPlanner -from RRTStar.rrt_star import RRTStar - -show_animation = True - - -class LQRRRTStar(RRTStar): - """ - Class for RRT star planning with LQR planning - """ - - def __init__(self, start, goal, obstacle_list, rand_area, - goal_sample_rate=10, - max_iter=200, - connect_circle_dist=50.0, - step_size=0.2, - robot_radius=0.0, - ): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1]) - self.end = self.Node(goal[0], goal[1]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - self.connect_circle_dist = connect_circle_dist - - self.curvature = 1.0 - self.goal_xy_th = 0.5 - self.step_size = step_size - self.robot_radius = robot_radius - - self.lqr_planner = LQRPlanner() - - def planning(self, animation=True, search_until_max_iter=True): - """ - RRT Star planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], rnd) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - near_indexes = self.find_near_nodes(new_node) - new_node = self.choose_parent(new_node, near_indexes) - if new_node: - self.node_list.append(new_node) - self.rewire(new_node, near_indexes) - - if animation and i % 5 == 0: - self.draw_graph(rnd) - - if (not search_until_max_iter) and new_node: # check reaching the goal - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - else: - print("Cannot find path") - - return None - - def draw_graph(self, rnd=None): - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - plt.pause(0.01) - - def search_best_goal_node(self): - dist_to_goal_list = [self.calc_dist_to_goal(n.x, n.y) for n in self.node_list] - goal_inds = [dist_to_goal_list.index(i) for i in dist_to_goal_list if i <= self.goal_xy_th] - - if not goal_inds: - return None - - min_cost = min([self.node_list[i].cost for i in goal_inds]) - for i in goal_inds: - if self.node_list[i].cost == min_cost: - return i - - return None - - def calc_new_cost(self, from_node, to_node): - - wx, wy = self.lqr_planner.lqr_planning( - from_node.x, from_node.y, to_node.x, to_node.y, show_animation=False) - - px, py, course_lengths = self.sample_path(wx, wy, self.step_size) - - if not course_lengths: - return float("inf") - - return from_node.cost + sum(course_lengths) - - def get_random_node(self): - - if random.randint(0, 100) > self.goal_sample_rate: - rnd = self.Node(random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand) - ) - else: # goal point sampling - rnd = self.Node(self.end.x, self.end.y) - - return rnd - - def generate_final_course(self, goal_index): - print("final") - path = [[self.end.x, self.end.y]] - node = self.node_list[goal_index] - while node.parent: - for (ix, iy) in zip(reversed(node.path_x), reversed(node.path_y)): - path.append([ix, iy]) - node = node.parent - path.append([self.start.x, self.start.y]) - return path - - def sample_path(self, wx, wy, step): - - px, py, clen = [], [], [] - - for i in range(len(wx) - 1): - - for t in np.arange(0.0, 1.0, step): - px.append(t * wx[i + 1] + (1.0 - t) * wx[i]) - py.append(t * wy[i + 1] + (1.0 - t) * wy[i]) - - dx = np.diff(px) - dy = np.diff(py) - - clen = [math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)] - - return px, py, clen - - def steer(self, from_node, to_node): - - wx, wy = self.lqr_planner.lqr_planning( - from_node.x, from_node.y, to_node.x, to_node.y, show_animation=False) - - px, py, course_lens = self.sample_path(wx, wy, self.step_size) - - if px is None: - return None - - newNode = copy.deepcopy(from_node) - newNode.x = px[-1] - newNode.y = py[-1] - newNode.path_x = px - newNode.path_y = py - newNode.cost += sum([abs(c) for c in course_lens]) - newNode.parent = from_node - - return newNode - - -def main(maxIter=200): - print("Start " + __file__) - - # ====Search Path with RRT==== - obstacleList = [ - (5, 5, 1), - (4, 6, 1), - (4, 7.5, 1), - (4, 9, 1), - (6, 5, 1), - (7, 5, 1) - ] # [x,y,size] - - # Set Initial parameters - start = [0.0, 0.0] - goal = [6.0, 7.0] - - lqr_rrt_star = LQRRRTStar(start, goal, - obstacleList, - [-2.0, 15.0]) - path = lqr_rrt_star.planning(animation=show_animation) - - # Draw final path - if show_animation: # pragma: no cover - lqr_rrt_star.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.001) - plt.show() - - print("Done") - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/ModelPredictiveTrajectoryGenerator/__init__.py b/PathPlanning/ModelPredictiveTrajectoryGenerator/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/ModelPredictiveTrajectoryGenerator/lookup_table_generator.py b/PathPlanning/ModelPredictiveTrajectoryGenerator/lookup_table_generator.py deleted file mode 100644 index 4c3b32f280..0000000000 --- a/PathPlanning/ModelPredictiveTrajectoryGenerator/lookup_table_generator.py +++ /dev/null @@ -1,110 +0,0 @@ -""" - -Lookup Table generation for model predictive trajectory generator - -author: Atsushi Sakai - -""" -import sys -import pathlib -path_planning_dir = pathlib.Path(__file__).parent.parent -sys.path.append(str(path_planning_dir)) - -from matplotlib import pyplot as plt -import numpy as np -import math - -from ModelPredictiveTrajectoryGenerator import trajectory_generator,\ - motion_model - - -def calc_states_list(max_yaw=np.deg2rad(-30.0)): - - x = np.arange(10.0, 30.0, 5.0) - y = np.arange(0.0, 20.0, 2.0) - yaw = np.arange(-max_yaw, max_yaw, max_yaw) - - states = [] - for iyaw in yaw: - for iy in y: - for ix in x: - states.append([ix, iy, iyaw]) - print("n_state:", len(states)) - - return states - - -def search_nearest_one_from_lookup_table(tx, ty, tyaw, lookup_table): - mind = float("inf") - minid = -1 - - for (i, table) in enumerate(lookup_table): - - dx = tx - table[0] - dy = ty - table[1] - dyaw = tyaw - table[2] - d = math.sqrt(dx ** 2 + dy ** 2 + dyaw ** 2) - if d <= mind: - minid = i - mind = d - - # print(minid) - - return lookup_table[minid] - - -def save_lookup_table(file_name, table): - np.savetxt(file_name, np.array(table), - fmt='%s', delimiter=",", header="x,y,yaw,s,km,kf", comments="") - - print("lookup table file is saved as " + file_name) - - -def generate_lookup_table(): - states = calc_states_list(max_yaw=np.deg2rad(-30.0)) - k0 = 0.0 - - # x, y, yaw, s, km, kf - lookup_table = [[1.0, 0.0, 0.0, 1.0, 0.0, 0.0]] - - for state in states: - best_p = search_nearest_one_from_lookup_table( - state[0], state[1], state[2], lookup_table) - - target = motion_model.State(x=state[0], y=state[1], yaw=state[2]) - init_p = np.array( - [np.hypot(state[0], state[1]), best_p[4], best_p[5]]).reshape(3, 1) - - x, y, yaw, p = trajectory_generator.optimize_trajectory(target, - k0, init_p) - - if x is not None: - print("find good path") - lookup_table.append( - [x[-1], y[-1], yaw[-1], float(p[0, 0]), float(p[1, 0]), float(p[2, 0])]) - - print("finish lookup table generation") - - save_lookup_table("lookup_table.csv", lookup_table) - - for table in lookup_table: - x_c, y_c, yaw_c = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - plt.plot(x_c, y_c, "-r") - x_c, y_c, yaw_c = motion_model.generate_trajectory( - table[3], -table[4], -table[5], k0) - plt.plot(x_c, y_c, "-r") - - plt.grid(True) - plt.axis("equal") - plt.show() - - print("Done") - - -def main(): - generate_lookup_table() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/ModelPredictiveTrajectoryGenerator/motion_model.py b/PathPlanning/ModelPredictiveTrajectoryGenerator/motion_model.py deleted file mode 100644 index 5ef6d2e23f..0000000000 --- a/PathPlanning/ModelPredictiveTrajectoryGenerator/motion_model.py +++ /dev/null @@ -1,95 +0,0 @@ -import math -import numpy as np -from scipy.interpolate import interp1d -from utils.angle import angle_mod - -# motion parameter -L = 1.0 # wheel base -ds = 0.1 # course distance -v = 10.0 / 3.6 # velocity [m/s] - - -class State: - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def update(state, v, delta, dt, L): - state.v = v - state.x = state.x + state.v * math.cos(state.yaw) * dt - state.y = state.y + state.v * math.sin(state.yaw) * dt - state.yaw = state.yaw + state.v / L * math.tan(delta) * dt - state.yaw = pi_2_pi(state.yaw) - - return state - - -def generate_trajectory(s, km, kf, k0): - n = s / ds - time = s / v # [s] - - if isinstance(time, type(np.array([]))): - time = time[0] - if isinstance(km, type(np.array([]))): - km = km[0] - if isinstance(kf, type(np.array([]))): - kf = kf[0] - - tk = np.array([0.0, time / 2.0, time]) - kk = np.array([k0, km, kf]) - t = np.arange(0.0, time, time / n) - fkp = interp1d(tk, kk, kind="quadratic") - kp = [fkp(ti) for ti in t] - dt = float(time / n) - - # plt.plot(t, kp) - # plt.show() - - state = State() - x, y, yaw = [state.x], [state.y], [state.yaw] - - for ikp in kp: - state = update(state, v, ikp, dt, L) - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - - return x, y, yaw - - -def generate_last_state(s, km, kf, k0): - n = s / ds - time = s / v # [s] - - if isinstance(n, type(np.array([]))): - n = n[0] - if isinstance(time, type(np.array([]))): - time = time[0] - if isinstance(km, type(np.array([]))): - km = km[0] - if isinstance(kf, type(np.array([]))): - kf = kf[0] - - tk = np.array([0.0, time / 2.0, time]) - kk = np.array([k0, km, kf]) - t = np.arange(0.0, time, time / n) - fkp = interp1d(tk, kk, kind="quadratic") - kp = [fkp(ti) for ti in t] - dt = time / n - - # plt.plot(t, kp) - # plt.show() - - state = State() - - _ = [update(state, v, ikp, dt, L) for ikp in kp] - - return state.x, state.y, state.yaw diff --git a/PathPlanning/ModelPredictiveTrajectoryGenerator/trajectory_generator.py b/PathPlanning/ModelPredictiveTrajectoryGenerator/trajectory_generator.py deleted file mode 100644 index 6084fc1a07..0000000000 --- a/PathPlanning/ModelPredictiveTrajectoryGenerator/trajectory_generator.py +++ /dev/null @@ -1,162 +0,0 @@ -""" - -Model trajectory generator - -author: Atsushi Sakai(@Atsushi_twi) - -""" - -import math -import matplotlib.pyplot as plt -import numpy as np -import sys -import pathlib -path_planning_dir = pathlib.Path(__file__).parent.parent -sys.path.append(str(path_planning_dir)) - -import ModelPredictiveTrajectoryGenerator.motion_model as motion_model - -# optimization parameter -max_iter = 100 -h: np.ndarray = np.array([0.5, 0.02, 0.02]).T # parameter sampling distance -cost_th = 0.1 - -show_animation = True - - -def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): # pragma: no cover - """ - Plot arrow - """ - 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) - plt.plot(0, 0) - - -def calc_diff(target, x, y, yaw): - d = np.array([target.x - x[-1], - target.y - y[-1], - motion_model.pi_2_pi(target.yaw - yaw[-1])]) - - return d - - -def calc_j(target, p, h, k0): - xp, yp, yawp = motion_model.generate_last_state( - p[0, 0] + h[0], p[1, 0], p[2, 0], k0) - dp = calc_diff(target, [xp], [yp], [yawp]) - xn, yn, yawn = motion_model.generate_last_state( - p[0, 0] - h[0], p[1, 0], p[2, 0], k0) - dn = calc_diff(target, [xn], [yn], [yawn]) - d1 = np.array((dp - dn) / (2.0 * h[0])).reshape(3, 1) - - xp, yp, yawp = motion_model.generate_last_state( - p[0, 0], p[1, 0] + h[1], p[2, 0], k0) - dp = calc_diff(target, [xp], [yp], [yawp]) - xn, yn, yawn = motion_model.generate_last_state( - p[0, 0], p[1, 0] - h[1], p[2, 0], k0) - dn = calc_diff(target, [xn], [yn], [yawn]) - d2 = np.array((dp - dn) / (2.0 * h[1])).reshape(3, 1) - - xp, yp, yawp = motion_model.generate_last_state( - p[0, 0], p[1, 0], p[2, 0] + h[2], k0) - dp = calc_diff(target, [xp], [yp], [yawp]) - xn, yn, yawn = motion_model.generate_last_state( - p[0, 0], p[1, 0], p[2, 0] - h[2], k0) - dn = calc_diff(target, [xn], [yn], [yawn]) - d3 = np.array((dp - dn) / (2.0 * h[2])).reshape(3, 1) - - J = np.hstack((d1, d2, d3)) - - return J - - -def selection_learning_param(dp, p, k0, target): - mincost = float("inf") - mina = 1.0 - maxa = 2.0 - da = 0.5 - - for a in np.arange(mina, maxa, da): - tp = p + a * dp - xc, yc, yawc = motion_model.generate_last_state( - tp[0], tp[1], tp[2], k0) - dc = calc_diff(target, [xc], [yc], [yawc]) - cost = np.linalg.norm(dc) - - if cost <= mincost and a != 0.0: - mina = a - mincost = cost - - # print(mincost, mina) - # input() - - return mina - - -def show_trajectory(target, xc, yc): # pragma: no cover - plt.clf() - plot_arrow(target.x, target.y, target.yaw) - plt.plot(xc, yc, "-r") - plt.axis("equal") - plt.grid(True) - plt.pause(0.1) - - -def optimize_trajectory(target, k0, p): - for i in range(max_iter): - xc, yc, yawc = motion_model.generate_trajectory(p[0, 0], p[1, 0], p[2, 0], k0) - dc = np.array(calc_diff(target, xc, yc, yawc)).reshape(3, 1) - - cost = np.linalg.norm(dc) - if cost <= cost_th: - print("path is ok cost is:" + str(cost)) - break - - J = calc_j(target, p, h, k0) - try: - dp = - np.linalg.inv(J) @ dc - except np.linalg.linalg.LinAlgError: - print("cannot calc path LinAlgError") - xc, yc, yawc, p = None, None, None, None - break - alpha = selection_learning_param(dp, p, k0, target) - - p += alpha * np.array(dp) - # print(p.T) - - if show_animation: # pragma: no cover - show_trajectory(target, xc, yc) - else: - xc, yc, yawc, p = None, None, None, None - print("cannot calc path") - - return xc, yc, yawc, p - - -def optimize_trajectory_demo(): # pragma: no cover - - # target = motion_model.State(x=5.0, y=2.0, yaw=np.deg2rad(00.0)) - target = motion_model.State(x=5.0, y=2.0, yaw=np.deg2rad(90.0)) - k0 = 0.0 - - init_p = np.array([6.0, 0.0, 0.0]).reshape(3, 1) - - x, y, yaw, p = optimize_trajectory(target, k0, init_p) - - if show_animation: - show_trajectory(target, x, y) - plot_arrow(target.x, target.y, target.yaw) - plt.axis("equal") - plt.grid(True) - plt.show() - - -def main(): # pragma: no cover - print(__file__ + " start!!") - optimize_trajectory_demo() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/PotentialFieldPlanning/potential_field_planning.py b/PathPlanning/PotentialFieldPlanning/potential_field_planning.py deleted file mode 100644 index 603a9d16cf..0000000000 --- a/PathPlanning/PotentialFieldPlanning/potential_field_planning.py +++ /dev/null @@ -1,199 +0,0 @@ -""" - -Potential Field based path planner - -author: Atsushi Sakai (@Atsushi_twi) - -Reference: -https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf - -""" - -from collections import deque -import numpy as np -import matplotlib.pyplot as plt - -# Parameters -KP = 5.0 # attractive potential gain -ETA = 100.0 # repulsive potential gain -AREA_WIDTH = 30.0 # potential area width [m] -# the number of previous positions used to check oscillations -OSCILLATIONS_DETECTION_LENGTH = 3 - -show_animation = True - - -def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy): - minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0 - miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0 - maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0 - maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0 - xw = int(round((maxx - minx) / reso)) - yw = int(round((maxy - miny) / reso)) - - # calc each potential - pmap = [[0.0 for i in range(yw)] for i in range(xw)] - - for ix in range(xw): - x = ix * reso + minx - - for iy in range(yw): - y = iy * reso + miny - ug = calc_attractive_potential(x, y, gx, gy) - uo = calc_repulsive_potential(x, y, ox, oy, rr) - uf = ug + uo - pmap[ix][iy] = uf - - return pmap, minx, miny - - -def calc_attractive_potential(x, y, gx, gy): - return 0.5 * KP * np.hypot(x - gx, y - gy) - - -def calc_repulsive_potential(x, y, ox, oy, rr): - # search nearest obstacle - minid = -1 - dmin = float("inf") - for i, _ in enumerate(ox): - d = np.hypot(x - ox[i], y - oy[i]) - if dmin >= d: - dmin = d - minid = i - - # calc repulsive potential - dq = np.hypot(x - ox[minid], y - oy[minid]) - - if dq <= rr: - if dq <= 0.1: - dq = 0.1 - - return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2 - else: - return 0.0 - - -def get_motion_model(): - # dx, dy - motion = [[1, 0], - [0, 1], - [-1, 0], - [0, -1], - [-1, -1], - [-1, 1], - [1, -1], - [1, 1]] - - return motion - - -def oscillations_detection(previous_ids, ix, iy): - previous_ids.append((ix, iy)) - - if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH): - previous_ids.popleft() - - # check if contains any duplicates by copying into a set - previous_ids_set = set() - for index in previous_ids: - if index in previous_ids_set: - return True - else: - previous_ids_set.add(index) - return False - - -def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr): - - # calc potential field - pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy) - - # search path - d = np.hypot(sx - gx, sy - gy) - ix = round((sx - minx) / reso) - iy = round((sy - miny) / reso) - gix = round((gx - minx) / reso) - giy = round((gy - miny) / reso) - - if show_animation: - draw_heatmap(pmap) - # 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]) - plt.plot(ix, iy, "*k") - plt.plot(gix, giy, "*m") - - rx, ry = [sx], [sy] - motion = get_motion_model() - previous_ids = deque() - - while d >= reso: - minp = float("inf") - minix, miniy = -1, -1 - for i, _ in enumerate(motion): - inx = int(ix + motion[i][0]) - iny = int(iy + motion[i][1]) - if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0: - p = float("inf") # outside area - print("outside potential!") - else: - p = pmap[inx][iny] - if minp > p: - minp = p - minix = inx - miniy = iny - ix = minix - iy = miniy - xp = ix * reso + minx - yp = iy * reso + miny - d = np.hypot(gx - xp, gy - yp) - rx.append(xp) - ry.append(yp) - - if (oscillations_detection(previous_ids, ix, iy)): - print("Oscillation detected at ({},{})!".format(ix, iy)) - break - - if show_animation: - plt.plot(ix, iy, ".r") - plt.pause(0.01) - - print("Goal!!") - - return rx, ry - - -def draw_heatmap(data): - data = np.array(data).T - plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues) - - -def main(): - print("potential_field_planning start") - - sx = 0.0 # start x position [m] - sy = 10.0 # start y positon [m] - gx = 30.0 # goal x position [m] - gy = 30.0 # goal y position [m] - grid_size = 0.5 # potential grid size [m] - robot_radius = 5.0 # robot radius [m] - - ox = [15.0, 5.0, 20.0, 25.0] # obstacle x position list [m] - oy = [25.0, 15.0, 26.0, 25.0] # obstacle y position list [m] - - if show_animation: - plt.grid(True) - plt.axis("equal") - - # path generation - _, _ = potential_field_planning( - sx, sy, gx, gy, ox, oy, grid_size, robot_radius) - - if show_animation: - plt.show() - - -if __name__ == '__main__': - print(__file__ + " start!!") - main() - print(__file__ + " Done!!") diff --git a/PathPlanning/ProbabilisticRoadMap/probabilistic_road_map.py b/PathPlanning/ProbabilisticRoadMap/probabilistic_road_map.py deleted file mode 100644 index 8bacfd5d19..0000000000 --- a/PathPlanning/ProbabilisticRoadMap/probabilistic_road_map.py +++ /dev/null @@ -1,312 +0,0 @@ -""" - -Probabilistic Road Map (PRM) Planner - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import numpy as np -import matplotlib.pyplot as plt -from scipy.spatial import KDTree - -# parameter -N_SAMPLE = 500 # number of sample_points -N_KNN = 10 # number of edge from one sampled point -MAX_EDGE_LEN = 30.0 # [m] Maximum edge length - -show_animation = True - - -class Node: - """ - Node class for dijkstra search - """ - - def __init__(self, x, y, cost, parent_index): - self.x = x - self.y = y - self.cost = cost - self.parent_index = parent_index - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," +\ - str(self.cost) + "," + str(self.parent_index) - - -def prm_planning(start_x, start_y, goal_x, goal_y, - obstacle_x_list, obstacle_y_list, robot_radius, *, rng=None): - """ - Run probabilistic road map planning - - :param start_x: start x position - :param start_y: start y position - :param goal_x: goal x position - :param goal_y: goal y position - :param obstacle_x_list: obstacle x positions - :param obstacle_y_list: obstacle y positions - :param robot_radius: robot radius - :param rng: (Optional) Random generator - :return: - """ - obstacle_kd_tree = KDTree(np.vstack((obstacle_x_list, obstacle_y_list)).T) - - sample_x, sample_y = sample_points(start_x, start_y, goal_x, goal_y, - robot_radius, - obstacle_x_list, obstacle_y_list, - obstacle_kd_tree, rng) - if show_animation: - plt.plot(sample_x, sample_y, ".b") - - road_map = generate_road_map(sample_x, sample_y, - robot_radius, obstacle_kd_tree) - - rx, ry = dijkstra_planning( - start_x, start_y, goal_x, goal_y, road_map, sample_x, sample_y) - - return rx, ry - - -def is_collision(sx, sy, gx, gy, rr, obstacle_kd_tree): - x = sx - y = sy - dx = gx - sx - dy = gy - sy - yaw = math.atan2(gy - sy, gx - sx) - d = math.hypot(dx, dy) - - if d >= MAX_EDGE_LEN: - return True - - D = rr - n_step = round(d / D) - - for i in range(n_step): - dist, _ = obstacle_kd_tree.query([x, y]) - if dist <= rr: - return True # collision - x += D * math.cos(yaw) - y += D * math.sin(yaw) - - # goal point check - dist, _ = obstacle_kd_tree.query([gx, gy]) - if dist <= rr: - return True # collision - - return False # OK - - -def generate_road_map(sample_x, sample_y, rr, obstacle_kd_tree): - """ - Road map generation - - sample_x: [m] x positions of sampled points - sample_y: [m] y positions of sampled points - robot_radius: Robot Radius[m] - obstacle_kd_tree: KDTree object of obstacles - """ - - road_map = [] - n_sample = len(sample_x) - sample_kd_tree = KDTree(np.vstack((sample_x, sample_y)).T) - - for (i, ix, iy) in zip(range(n_sample), sample_x, sample_y): - - dists, indexes = sample_kd_tree.query([ix, iy], k=n_sample) - edge_id = [] - - for ii in range(1, len(indexes)): - nx = sample_x[indexes[ii]] - ny = sample_y[indexes[ii]] - - if not is_collision(ix, iy, nx, ny, rr, obstacle_kd_tree): - edge_id.append(indexes[ii]) - - if len(edge_id) >= N_KNN: - break - - road_map.append(edge_id) - - # plot_road_map(road_map, sample_x, sample_y) - - return road_map - - -def dijkstra_planning(sx, sy, gx, gy, road_map, sample_x, sample_y): - """ - s_x: start x position [m] - s_y: start y position [m] - goal_x: goal x position [m] - goal_y: goal y position [m] - obstacle_x_list: x position list of Obstacles [m] - obstacle_y_list: y position list of Obstacles [m] - robot_radius: robot radius [m] - road_map: ??? [m] - sample_x: ??? [m] - sample_y: ??? [m] - - @return: Two lists of path coordinates ([x1, x2, ...], [y1, y2, ...]), empty list when no path was found - """ - - start_node = Node(sx, sy, 0.0, -1) - goal_node = Node(gx, gy, 0.0, -1) - - open_set, closed_set = dict(), dict() - open_set[len(road_map) - 2] = start_node - - path_found = True - - while True: - if not open_set: - print("Cannot find path") - path_found = False - break - - c_id = min(open_set, key=lambda o: open_set[o].cost) - current = open_set[c_id] - - # show graph - if show_animation and len(closed_set.keys()) % 2 == 0: - # 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]) - plt.plot(current.x, current.y, "xg") - plt.pause(0.001) - - if c_id == (len(road_map) - 1): - print("goal is found!") - goal_node.parent_index = current.parent_index - goal_node.cost = current.cost - break - - # Remove the item from the open set - del open_set[c_id] - # Add it to the closed set - closed_set[c_id] = current - - # expand search grid based on motion model - for i in range(len(road_map[c_id])): - n_id = road_map[c_id][i] - dx = sample_x[n_id] - current.x - dy = sample_y[n_id] - current.y - d = math.hypot(dx, dy) - node = Node(sample_x[n_id], sample_y[n_id], - current.cost + d, c_id) - - if n_id in closed_set: - continue - # Otherwise if it is already in the open set - if n_id in open_set: - if open_set[n_id].cost > node.cost: - open_set[n_id].cost = node.cost - open_set[n_id].parent_index = c_id - else: - open_set[n_id] = node - - if path_found is False: - return [], [] - - # generate final course - rx, ry = [goal_node.x], [goal_node.y] - parent_index = goal_node.parent_index - while parent_index != -1: - n = closed_set[parent_index] - rx.append(n.x) - ry.append(n.y) - parent_index = n.parent_index - - return rx, ry - - -def plot_road_map(road_map, sample_x, sample_y): # pragma: no cover - - for i, _ in enumerate(road_map): - for ii in range(len(road_map[i])): - ind = road_map[i][ii] - - plt.plot([sample_x[i], sample_x[ind]], - [sample_y[i], sample_y[ind]], "-k") - - -def sample_points(sx, sy, gx, gy, rr, ox, oy, obstacle_kd_tree, rng): - max_x = max(ox) - max_y = max(oy) - min_x = min(ox) - min_y = min(oy) - - sample_x, sample_y = [], [] - - if rng is None: - rng = np.random.default_rng() - - while len(sample_x) <= N_SAMPLE: - tx = (rng.random() * (max_x - min_x)) + min_x - ty = (rng.random() * (max_y - min_y)) + min_y - - dist, index = obstacle_kd_tree.query([tx, ty]) - - if dist >= rr: - sample_x.append(tx) - sample_y.append(ty) - - sample_x.append(sx) - sample_y.append(sy) - sample_x.append(gx) - sample_y.append(gy) - - return sample_x, sample_y - - -def main(rng=None): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - robot_size = 5.0 # [m] - - ox = [] - oy = [] - - for i in range(60): - ox.append(float(i)) - oy.append(0.0) - for i in range(60): - ox.append(60.0) - oy.append(float(i)) - for i in range(61): - ox.append(float(i)) - oy.append(60.0) - for i in range(61): - ox.append(0.0) - oy.append(float(i)) - for i in range(40): - ox.append(20.0) - oy.append(float(i)) - for i in range(40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "^r") - plt.plot(gx, gy, "^c") - plt.grid(True) - plt.axis("equal") - - rx, ry = prm_planning(sx, sy, gx, gy, ox, oy, robot_size, rng=rng) - - assert rx, 'Cannot found path' - - if show_animation: - plt.plot(rx, ry, "-r") - plt.pause(0.001) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/QuinticPolynomialsPlanner/quintic_polynomials_planner.py b/PathPlanning/QuinticPolynomialsPlanner/quintic_polynomials_planner.py deleted file mode 100644 index 86f9f662da..0000000000 --- a/PathPlanning/QuinticPolynomialsPlanner/quintic_polynomials_planner.py +++ /dev/null @@ -1,230 +0,0 @@ -""" - -Quintic Polynomials Planner - -author: Atsushi Sakai (@Atsushi_twi) - -Reference: - -- [Local Path planning And Motion Control For Agv In Positioning](https://ieeexplore.ieee.org/document/637936/) - -""" - -import math - -import matplotlib.pyplot as plt -import numpy as np - -# parameter -MAX_T = 100.0 # maximum time to the goal [s] -MIN_T = 5.0 # minimum time to the goal[s] - -show_animation = True - - -class QuinticPolynomial: - - def __init__(self, xs, vxs, axs, xe, vxe, axe, time): - # calc coefficient of quintic polynomial - # See jupyter notebook document for derivation of this equation. - self.a0 = xs - self.a1 = vxs - self.a2 = axs / 2.0 - - A = np.array([[time ** 3, time ** 4, time ** 5], - [3 * time ** 2, 4 * time ** 3, 5 * time ** 4], - [6 * time, 12 * time ** 2, 20 * time ** 3]]) - b = np.array([xe - self.a0 - self.a1 * time - self.a2 * time ** 2, - vxe - self.a1 - 2 * self.a2 * time, - axe - 2 * self.a2]) - x = np.linalg.solve(A, b) - - self.a3 = x[0] - self.a4 = x[1] - self.a5 = x[2] - - def calc_point(self, t): - xt = self.a0 + self.a1 * t + self.a2 * t ** 2 + \ - self.a3 * t ** 3 + self.a4 * t ** 4 + self.a5 * t ** 5 - - return xt - - def calc_first_derivative(self, t): - xt = self.a1 + 2 * self.a2 * t + \ - 3 * self.a3 * t ** 2 + 4 * self.a4 * t ** 3 + 5 * self.a5 * t ** 4 - - return xt - - def calc_second_derivative(self, t): - xt = 2 * self.a2 + 6 * self.a3 * t + 12 * self.a4 * t ** 2 + 20 * self.a5 * t ** 3 - - return xt - - def calc_third_derivative(self, t): - xt = 6 * self.a3 + 24 * self.a4 * t + 60 * self.a5 * t ** 2 - - return xt - - -def quintic_polynomials_planner(sx, sy, syaw, sv, sa, gx, gy, gyaw, gv, ga, max_accel, max_jerk, dt): - """ - quintic polynomial planner - - input - s_x: start x position [m] - s_y: start y position [m] - s_yaw: start yaw angle [rad] - sa: start accel [m/ss] - gx: goal x position [m] - gy: goal y position [m] - gyaw: goal yaw angle [rad] - ga: goal accel [m/ss] - max_accel: maximum accel [m/ss] - max_jerk: maximum jerk [m/sss] - dt: time tick [s] - - return - time: time result - rx: x position result list - ry: y position result list - ryaw: yaw angle result list - rv: velocity result list - ra: accel result list - - """ - - vxs = sv * math.cos(syaw) - vys = sv * math.sin(syaw) - vxg = gv * math.cos(gyaw) - vyg = gv * math.sin(gyaw) - - axs = sa * math.cos(syaw) - ays = sa * math.sin(syaw) - axg = ga * math.cos(gyaw) - ayg = ga * math.sin(gyaw) - - time, rx, ry, ryaw, rv, ra, rj = [], [], [], [], [], [], [] - - for T in np.arange(MIN_T, MAX_T, MIN_T): - xqp = QuinticPolynomial(sx, vxs, axs, gx, vxg, axg, T) - yqp = QuinticPolynomial(sy, vys, ays, gy, vyg, ayg, T) - - time, rx, ry, ryaw, rv, ra, rj = [], [], [], [], [], [], [] - - for t in np.arange(0.0, T + dt, dt): - time.append(t) - rx.append(xqp.calc_point(t)) - ry.append(yqp.calc_point(t)) - - vx = xqp.calc_first_derivative(t) - vy = yqp.calc_first_derivative(t) - v = np.hypot(vx, vy) - yaw = math.atan2(vy, vx) - rv.append(v) - ryaw.append(yaw) - - ax = xqp.calc_second_derivative(t) - ay = yqp.calc_second_derivative(t) - a = np.hypot(ax, ay) - if len(rv) >= 2 and rv[-1] - rv[-2] < 0.0: - a *= -1 - ra.append(a) - - jx = xqp.calc_third_derivative(t) - jy = yqp.calc_third_derivative(t) - j = np.hypot(jx, jy) - if len(ra) >= 2 and ra[-1] - ra[-2] < 0.0: - j *= -1 - rj.append(j) - - if max([abs(i) for i in ra]) <= max_accel and max([abs(i) for i in rj]) <= max_jerk: - print("find path!!") - break - - if show_animation: # pragma: no cover - for i, _ in enumerate(time): - 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]) - plt.grid(True) - plt.axis("equal") - plot_arrow(sx, sy, syaw) - plot_arrow(gx, gy, gyaw) - plot_arrow(rx[i], ry[i], ryaw[i]) - plt.title("Time[s]:" + str(time[i])[0:4] + - " v[m/s]:" + str(rv[i])[0:4] + - " a[m/ss]:" + str(ra[i])[0:4] + - " jerk[m/sss]:" + str(rj[i])[0:4], - ) - plt.pause(0.001) - - return time, rx, ry, ryaw, rv, ra, rj - - -def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): # pragma: no cover - """ - 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 main(): - print(__file__ + " start!!") - - sx = 10.0 # start x position [m] - sy = 10.0 # start y position [m] - syaw = np.deg2rad(10.0) # start yaw angle [rad] - sv = 1.0 # start speed [m/s] - sa = 0.1 # start accel [m/ss] - gx = 30.0 # goal x position [m] - gy = -10.0 # goal y position [m] - gyaw = np.deg2rad(20.0) # goal yaw angle [rad] - gv = 1.0 # goal speed [m/s] - ga = 0.1 # goal accel [m/ss] - max_accel = 1.0 # max accel [m/ss] - max_jerk = 0.5 # max jerk [m/sss] - dt = 0.1 # time tick [s] - - time, x, y, yaw, v, a, j = quintic_polynomials_planner( - sx, sy, syaw, sv, sa, gx, gy, gyaw, gv, ga, max_accel, max_jerk, dt) - - if show_animation: # pragma: no cover - plt.plot(x, y, "-r") - - plt.subplots() - plt.plot(time, [np.rad2deg(i) for i in yaw], "-r") - plt.xlabel("Time[s]") - plt.ylabel("Yaw[deg]") - plt.grid(True) - - plt.subplots() - plt.plot(time, v, "-r") - plt.xlabel("Time[s]") - plt.ylabel("Speed[m/s]") - plt.grid(True) - - plt.subplots() - plt.plot(time, a, "-r") - plt.xlabel("Time[s]") - plt.ylabel("accel[m/ss]") - plt.grid(True) - - plt.subplots() - plt.plot(time, j, "-r") - plt.xlabel("Time[s]") - plt.ylabel("jerk[m/sss]") - plt.grid(True) - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRT/__init__.py b/PathPlanning/RRT/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/RRT/rrt.py b/PathPlanning/RRT/rrt.py deleted file mode 100644 index e6dd9b648b..0000000000 --- a/PathPlanning/RRT/rrt.py +++ /dev/null @@ -1,291 +0,0 @@ -""" - -Path planning Sample Code with Randomized Rapidly-Exploring Random Trees (RRT) - -author: AtsushiSakai(@Atsushi_twi) - -""" - -import math -import random - -import matplotlib.pyplot as plt -import numpy as np - -show_animation = True - - -class RRT: - """ - Class for RRT planning - """ - - class Node: - """ - RRT Node - """ - - def __init__(self, x, y): - self.x = x - self.y = y - self.path_x = [] - self.path_y = [] - self.parent = None - - class AreaBounds: - - def __init__(self, area): - self.xmin = float(area[0]) - self.xmax = float(area[1]) - self.ymin = float(area[2]) - self.ymax = float(area[3]) - - - def __init__(self, - start, - goal, - obstacle_list, - rand_area, - expand_dis=3.0, - path_resolution=0.5, - goal_sample_rate=5, - max_iter=500, - play_area=None, - robot_radius=0.0, - ): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - play_area:stay inside this area [xmin,xmax,ymin,ymax] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1]) - self.end = self.Node(goal[0], goal[1]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - if play_area is not None: - self.play_area = self.AreaBounds(play_area) - else: - self.play_area = None - self.expand_dis = expand_dis - self.path_resolution = path_resolution - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - self.node_list = [] - self.robot_radius = robot_radius - - def planning(self, animation=True): - """ - rrt path planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - rnd_node = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd_node) - nearest_node = self.node_list[nearest_ind] - - new_node = self.steer(nearest_node, rnd_node, self.expand_dis) - - if self.check_if_outside_play_area(new_node, self.play_area) and \ - self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - self.node_list.append(new_node) - - if animation and i % 5 == 0: - self.draw_graph(rnd_node) - - if self.calc_dist_to_goal(self.node_list[-1].x, - self.node_list[-1].y) <= self.expand_dis: - final_node = self.steer(self.node_list[-1], self.end, - self.expand_dis) - if self.check_collision( - final_node, self.obstacle_list, self.robot_radius): - return self.generate_final_course(len(self.node_list) - 1) - - if animation and i % 5: - self.draw_graph(rnd_node) - - return None # cannot find path - - def steer(self, from_node, to_node, extend_length=float("inf")): - - new_node = self.Node(from_node.x, from_node.y) - d, theta = self.calc_distance_and_angle(new_node, to_node) - - new_node.path_x = [new_node.x] - new_node.path_y = [new_node.y] - - if extend_length > d: - extend_length = d - - n_expand = math.floor(extend_length / self.path_resolution) - - for _ in range(n_expand): - new_node.x += self.path_resolution * math.cos(theta) - new_node.y += self.path_resolution * math.sin(theta) - new_node.path_x.append(new_node.x) - new_node.path_y.append(new_node.y) - - d, _ = self.calc_distance_and_angle(new_node, to_node) - if d <= self.path_resolution: - new_node.path_x.append(to_node.x) - new_node.path_y.append(to_node.y) - new_node.x = to_node.x - new_node.y = to_node.y - - new_node.parent = from_node - - return new_node - - def generate_final_course(self, goal_ind): - path = [[self.end.x, self.end.y]] - node = self.node_list[goal_ind] - while node.parent is not None: - path.append([node.x, node.y]) - node = node.parent - path.append([node.x, node.y]) - - return path - - def calc_dist_to_goal(self, x, y): - dx = x - self.end.x - dy = y - self.end.y - return math.hypot(dx, dy) - - def get_random_node(self): - if random.randint(0, 100) > self.goal_sample_rate: - rnd = self.Node( - random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand)) - else: # goal point sampling - rnd = self.Node(self.end.x, self.end.y) - return rnd - - def draw_graph(self, rnd=None): - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - if self.robot_radius > 0.0: - self.plot_circle(rnd.x, rnd.y, self.robot_radius, '-r') - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - self.plot_circle(ox, oy, size) - - if self.play_area is not None: - plt.plot([self.play_area.xmin, self.play_area.xmax, - self.play_area.xmax, self.play_area.xmin, - self.play_area.xmin], - [self.play_area.ymin, self.play_area.ymin, - self.play_area.ymax, self.play_area.ymax, - self.play_area.ymin], - "-k") - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis("equal") - plt.axis([self.min_rand, self.max_rand, self.min_rand, self.max_rand]) - plt.grid(True) - plt.pause(0.01) - - @staticmethod - def plot_circle(x, y, size, color="-b"): # pragma: no cover - deg = list(range(0, 360, 5)) - deg.append(0) - xl = [x + size * math.cos(np.deg2rad(d)) for d in deg] - yl = [y + size * math.sin(np.deg2rad(d)) for d in deg] - plt.plot(xl, yl, color) - - @staticmethod - def get_nearest_node_index(node_list, rnd_node): - dlist = [(node.x - rnd_node.x)**2 + (node.y - rnd_node.y)**2 - for node in node_list] - minind = dlist.index(min(dlist)) - - return minind - - @staticmethod - def check_if_outside_play_area(node, play_area): - - if play_area is None: - return True # no play_area was defined, every pos should be ok - - if node.x < play_area.xmin or node.x > play_area.xmax or \ - node.y < play_area.ymin or node.y > play_area.ymax: - return False # outside - bad - else: - return True # inside - ok - - @staticmethod - def check_collision(node, obstacleList, robot_radius): - - if node is None: - return False - - for (ox, oy, size) in obstacleList: - dx_list = [ox - x for x in node.path_x] - dy_list = [oy - y for y in node.path_y] - d_list = [dx * dx + dy * dy for (dx, dy) in zip(dx_list, dy_list)] - - if min(d_list) <= (size+robot_radius)**2: - return False # collision - - return True # safe - - @staticmethod - def calc_distance_and_angle(from_node, to_node): - dx = to_node.x - from_node.x - dy = to_node.y - from_node.y - d = math.hypot(dx, dy) - theta = math.atan2(dy, dx) - return d, theta - - -def main(gx=6.0, gy=10.0): - print("start " + __file__) - - # ====Search Path with RRT==== - obstacleList = [(5, 5, 1), (3, 6, 2), (3, 8, 2), (3, 10, 2), (7, 5, 2), - (9, 5, 2), (8, 10, 1)] # [x, y, radius] - # Set Initial parameters - rrt = RRT( - start=[0, 0], - goal=[gx, gy], - rand_area=[-2, 15], - obstacle_list=obstacleList, - # play_area=[0, 10, 0, 14] - robot_radius=0.8 - ) - path = rrt.planning(animation=show_animation) - - if path is None: - print("Cannot find path") - else: - print("found path!!") - - # Draw final path - if show_animation: - rrt.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.01) # Need for Mac - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRT/rrt_with_pathsmoothing.py b/PathPlanning/RRT/rrt_with_pathsmoothing.py deleted file mode 100644 index ac68efe23f..0000000000 --- a/PathPlanning/RRT/rrt_with_pathsmoothing.py +++ /dev/null @@ -1,210 +0,0 @@ -""" - -Path planning Sample Code with RRT with path smoothing - -@author: AtsushiSakai(@Atsushi_twi) - -""" - -import math -import random -import matplotlib.pyplot as plt -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent)) - -from rrt import RRT - -show_animation = True - - -def get_path_length(path): - le = 0 - for i in range(len(path) - 1): - dx = path[i + 1][0] - path[i][0] - dy = path[i + 1][1] - path[i][1] - d = math.hypot(dx, dy) - le += d - - return le - - -def get_target_point(path, targetL): - le = 0 - ti = 0 - lastPairLen = 0 - for i in range(len(path) - 1): - dx = path[i + 1][0] - path[i][0] - dy = path[i + 1][1] - path[i][1] - d = math.hypot(dx, dy) - le += d - if le >= targetL: - ti = i - 1 - lastPairLen = d - break - - partRatio = (le - targetL) / lastPairLen - - x = path[ti][0] + (path[ti + 1][0] - path[ti][0]) * partRatio - y = path[ti][1] + (path[ti + 1][1] - path[ti][1]) * partRatio - - return [x, y, ti] - - -def is_point_collision(x, y, obstacle_list, robot_radius): - """ - Check whether a single point collides with any obstacle. - - This function calculates the Euclidean distance between the given point (x, y) - and each obstacle center. If the distance is less than or equal to the sum of - the obstacle's radius and the robot's radius, a collision is detected. - - Args: - x (float): X-coordinate of the point to check. - y (float): Y-coordinate of the point to check. - obstacle_list (List[Tuple[float, float, float]]): List of obstacles defined as (ox, oy, radius). - robot_radius (float): Radius of the robot, used to inflate the obstacles. - - Returns: - bool: True if the point is in collision with any obstacle, False otherwise. - """ - for (ox, oy, obstacle_radius) in obstacle_list: - d = math.hypot(ox - x, oy - y) - if d <= obstacle_radius + robot_radius: - return True # Collided - return False - - -def line_collision_check(first, second, obstacle_list, robot_radius=0.0, sample_step=0.2): - """ - Check if the line segment between `first` and `second` collides with any obstacle. - Considers the robot_radius by inflating the obstacle size. - - Args: - first (List[float]): Start point of the line [x, y] - second (List[float]): End point of the line [x, y] - obstacle_list (List[Tuple[float, float, float]]): Obstacles as (x, y, radius) - robot_radius (float): Radius of robot - sample_step (float): Distance between sampling points along the segment - - Returns: - bool: True if collision-free, False otherwise - """ - x1, y1 = first[0], first[1] - x2, y2 = second[0], second[1] - - dx = x2 - x1 - dy = y2 - y1 - length = math.hypot(dx, dy) - - if length == 0: - # Degenerate case: point collision check - return not is_point_collision(x1, y1, obstacle_list, robot_radius) - - steps = int(length / sample_step) + 1 # Sampling every sample_step along the segment - - for i in range(steps + 1): - t = i / steps - x = x1 + t * dx - y = y1 + t * dy - - if is_point_collision(x, y, obstacle_list, robot_radius): - return False # Collision found - - return True # Safe - - -def path_smoothing(path, max_iter, obstacle_list, robot_radius=0.0): - """ - Smooths a given path by iteratively replacing segments with shortcut connections, - while ensuring the new segments are collision-free. - - The algorithm randomly picks two points along the original path and attempts to - connect them with a straight line. If the line does not collide with any obstacles - (considering the robot's radius), the intermediate path points between them are - replaced with the direct connection. - - Args: - path (List[List[float]]): The original path as a list of [x, y] coordinates. - max_iter (int): Number of iterations for smoothing attempts. - obstacle_list (List[Tuple[float, float, float]]): List of obstacles represented as - (x, y, radius). - robot_radius (float, optional): Radius of the robot, used to inflate obstacle size - during collision checking. Defaults to 0.0. - - Returns: - List[List[float]]: The smoothed path as a list of [x, y] coordinates. - - Example: - >>> smoothed = path_smoothing(path, 1000, obstacle_list, robot_radius=0.5) - """ - le = get_path_length(path) - - for i in range(max_iter): - # Sample two points - pickPoints = [random.uniform(0, le), random.uniform(0, le)] - pickPoints.sort() - first = get_target_point(path, pickPoints[0]) - second = get_target_point(path, pickPoints[1]) - - if first[2] <= 0 or second[2] <= 0: - continue - - if (second[2] + 1) > len(path): - continue - - if second[2] == first[2]: - continue - - # collision check - if not line_collision_check(first, second, obstacle_list, robot_radius): - continue - - # Create New path - newPath = [] - newPath.extend(path[:first[2] + 1]) - newPath.append([first[0], first[1]]) - newPath.append([second[0], second[1]]) - newPath.extend(path[second[2] + 1:]) - path = newPath - le = get_path_length(path) - - return path - - -def main(): - # ====Search Path with RRT==== - # Parameter - obstacleList = [ - (5, 5, 1), - (3, 6, 2), - (3, 8, 2), - (3, 10, 2), - (7, 5, 2), - (9, 5, 2) - ] # [x,y,radius] - rrt = RRT(start=[0, 0], goal=[6, 10], - rand_area=[-2, 15], obstacle_list=obstacleList, - robot_radius=0.3) - path = rrt.planning(animation=show_animation) - - # Path smoothing - maxIter = 1000 - smoothedPath = path_smoothing(path, maxIter, obstacleList, - robot_radius=rrt.robot_radius) - - # Draw final path - if show_animation: - rrt.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - - plt.plot([x for (x, y) in smoothedPath], [ - y for (x, y) in smoothedPath], '-c') - - plt.grid(True) - plt.pause(0.01) # Need for Mac - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRT/rrt_with_sobol_sampler.py b/PathPlanning/RRT/rrt_with_sobol_sampler.py deleted file mode 100644 index 06e0c04c68..0000000000 --- a/PathPlanning/RRT/rrt_with_sobol_sampler.py +++ /dev/null @@ -1,278 +0,0 @@ -""" - -Path planning Sample Code with Randomized Rapidly-Exploring Random -Trees with sobol low discrepancy sampler(RRTSobol). -Sobol wiki https://en.wikipedia.org/wiki/Sobol_sequence - -The goal of low discrepancy samplers is to generate a sequence of points that -optimizes a criterion called dispersion. Intuitively, the idea is to place -samples to cover the exploration space in a way that makes the largest -uncovered area be as small as possible. This generalizes of the idea of grid -resolution. For a grid, the resolution may be selected by defining the step -size for each axis. As the step size is decreased, the resolution increases. -If a grid-based motion planning algorithm can increase the resolution -arbitrarily, it becomes resolution complete. Dispersion can be considered as a -powerful generalization of the notion of resolution. - -Taken from -LaValle, Steven M. Planning algorithms. Cambridge university press, 2006. - - -authors: - First implementation AtsushiSakai(@Atsushi_twi) - Sobol sampler Rafael A. -Rojas (rafaelrojasmiliani@gmail.com) - - -""" - -import math -import random -import sys -import matplotlib.pyplot as plt -import numpy as np - -from sobol import sobol_quasirand - -show_animation = True - - -class RRTSobol: - """ - Class for RRTSobol planning - """ - - class Node: - """ - RRTSobol Node - """ - - def __init__(self, x, y): - self.x = x - self.y = y - self.path_x = [] - self.path_y = [] - self.parent = None - - def __init__(self, - start, - goal, - obstacle_list, - rand_area, - expand_dis=3.0, - path_resolution=0.5, - goal_sample_rate=5, - max_iter=500, - robot_radius=0.0): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacle_list:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1]) - self.end = self.Node(goal[0], goal[1]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.expand_dis = expand_dis - self.path_resolution = path_resolution - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - self.node_list = [] - self.sobol_inter_ = 0 - self.robot_radius = robot_radius - - def planning(self, animation=True): - """ - rrt path planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - rnd_node = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd_node) - nearest_node = self.node_list[nearest_ind] - - new_node = self.steer(nearest_node, rnd_node, self.expand_dis) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - self.node_list.append(new_node) - - if animation and i % 5 == 0: - self.draw_graph(rnd_node) - - if self.calc_dist_to_goal(self.node_list[-1].x, - self.node_list[-1].y) <= self.expand_dis: - final_node = self.steer(self.node_list[-1], self.end, - self.expand_dis) - if self.check_collision( - final_node, self.obstacle_list, self.robot_radius): - return self.generate_final_course(len(self.node_list) - 1) - - if animation and i % 5: - self.draw_graph(rnd_node) - - return None # cannot find path - - def steer(self, from_node, to_node, extend_length=float("inf")): - - new_node = self.Node(from_node.x, from_node.y) - d, theta = self.calc_distance_and_angle(new_node, to_node) - - new_node.path_x = [new_node.x] - new_node.path_y = [new_node.y] - - if extend_length > d: - extend_length = d - - n_expand = math.floor(extend_length / self.path_resolution) - - for _ in range(n_expand): - new_node.x += self.path_resolution * math.cos(theta) - new_node.y += self.path_resolution * math.sin(theta) - new_node.path_x.append(new_node.x) - new_node.path_y.append(new_node.y) - - d, _ = self.calc_distance_and_angle(new_node, to_node) - if d <= self.path_resolution: - new_node.path_x.append(to_node.x) - new_node.path_y.append(to_node.y) - new_node.x = to_node.x - new_node.y = to_node.y - - new_node.parent = from_node - - return new_node - - def generate_final_course(self, goal_ind): - path = [[self.end.x, self.end.y]] - node = self.node_list[goal_ind] - while node.parent is not None: - path.append([node.x, node.y]) - node = node.parent - path.append([node.x, node.y]) - - return path - - def calc_dist_to_goal(self, x, y): - dx = x - self.end.x - dy = y - self.end.y - return math.hypot(dx, dy) - - def get_random_node(self): - if random.randint(0, 100) > self.goal_sample_rate: - rand_coordinates, n = sobol_quasirand(2, self.sobol_inter_) - - rand_coordinates = self.min_rand + \ - rand_coordinates*(self.max_rand - self.min_rand) - self.sobol_inter_ = n - rnd = self.Node(*rand_coordinates) - - else: # goal point sampling - rnd = self.Node(self.end.x, self.end.y) - return rnd - - def draw_graph(self, rnd=None): - plt.clf() - # for stopping simulation with the esc key. - plt.gcf().canvas.mpl_connect( - 'key_release_event', - lambda event: [sys.exit(0) if event.key == 'escape' else None]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - if self.robot_radius >= 0.0: - self.plot_circle(rnd.x, rnd.y, self.robot_radius, '-r') - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - self.plot_circle(ox, oy, size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis("equal") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - plt.pause(0.01) - - @staticmethod - def plot_circle(x, y, size, color="-b"): # pragma: no cover - deg = list(range(0, 360, 5)) - deg.append(0) - xl = [x + size * math.cos(np.deg2rad(d)) for d in deg] - yl = [y + size * math.sin(np.deg2rad(d)) for d in deg] - plt.plot(xl, yl, color) - - @staticmethod - def get_nearest_node_index(node_list, rnd_node): - dlist = [(node.x - rnd_node.x)**2 + (node.y - rnd_node.y)**2 - for node in node_list] - minind = dlist.index(min(dlist)) - - return minind - - @staticmethod - def check_collision(node, obstacle_list, robot_radius): - - if node is None: - return False - - for (ox, oy, size) in obstacle_list: - dx_list = [ox - x for x in node.path_x] - dy_list = [oy - y for y in node.path_y] - d_list = [dx * dx + dy * dy for (dx, dy) in zip(dx_list, dy_list)] - - if min(d_list) <= (size+robot_radius)**2: - return False # collision - - return True # safe - - @staticmethod - def calc_distance_and_angle(from_node, to_node): - dx = to_node.x - from_node.x - dy = to_node.y - from_node.y - d = math.hypot(dx, dy) - theta = math.atan2(dy, dx) - return d, theta - - -def main(gx=6.0, gy=10.0): - print("start " + __file__) - - # ====Search Path with RRTSobol==== - obstacle_list = [(5, 5, 1), (3, 6, 2), (3, 8, 2), (3, 10, 2), (7, 5, 2), - (9, 5, 2), (8, 10, 1)] # [x, y, radius] - # Set Initial parameters - rrt = RRTSobol( - start=[0, 0], - goal=[gx, gy], - rand_area=[-2, 15], - obstacle_list=obstacle_list, - robot_radius=0.8) - path = rrt.planning(animation=show_animation) - - if path is None: - print("Cannot find path") - else: - print("found path!!") - - # Draw final path - if show_animation: - rrt.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.01) # Need for Mac - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRT/sobol/__init__.py b/PathPlanning/RRT/sobol/__init__.py deleted file mode 100644 index c95ac8983b..0000000000 --- a/PathPlanning/RRT/sobol/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .sobol import i4_sobol as sobol_quasirand diff --git a/PathPlanning/RRT/sobol/sobol.py b/PathPlanning/RRT/sobol/sobol.py deleted file mode 100644 index 520d686a1d..0000000000 --- a/PathPlanning/RRT/sobol/sobol.py +++ /dev/null @@ -1,911 +0,0 @@ -""" - Licensing: - This code is distributed under the MIT license. - - Authors: - Original FORTRAN77 version of i4_sobol by Bennett Fox. - MATLAB version by John Burkardt. - PYTHON version by Corrado Chisari - - Original Python version of is_prime by Corrado Chisari - - Original MATLAB versions of other functions by John Burkardt. - PYTHON versions by Corrado Chisari - - Original code is available at - https://people.sc.fsu.edu/~jburkardt/py_src/sobol/sobol.html - - Note: the i4 prefix means that the function takes a numeric argument or - returns a number which is interpreted inside the function as a 4 - byte integer - Note: the r4 prefix means that the function takes a numeric argument or - returns a number which is interpreted inside the function as a 4 - byte float -""" -import math -import sys -import numpy as np - -atmost = None -dim_max = None -dim_num_save = None -initialized = None -lastq = None -log_max = None -maxcol = None -poly = None -recipd = None -seed_save = None -v = None - - -def i4_bit_hi1(n): - """ - I4_BIT_HI1 returns the position of the high 1 bit base 2 in an I4. - - Discussion: - - An I4 is an integer ( kind = 4 ) value. - - Example: - - N Binary Hi 1 - ---- -------- ---- - 0 0 0 - 1 1 1 - 2 10 2 - 3 11 2 - 4 100 3 - 5 101 3 - 6 110 3 - 7 111 3 - 8 1000 4 - 9 1001 4 - 10 1010 4 - 11 1011 4 - 12 1100 4 - 13 1101 4 - 14 1110 4 - 15 1111 4 - 16 10000 5 - 17 10001 5 - 1023 1111111111 10 - 1024 10000000000 11 - 1025 10000000001 11 - - Licensing: - - This code is distributed under the GNU LGPL license. - - Modified: - - 26 October 2014 - - Author: - - John Burkardt - - Parameters: - - Input, integer N, the integer to be measured. - N should be nonnegative. If N is nonpositive, the function - will always be 0. - - Output, integer BIT, the position of the highest bit. - - """ - i = n - bit = 0 - - while True: - - if i <= 0: - break - - bit = bit + 1 - i = i // 2 - - return bit - - -def i4_bit_lo0(n): - """ - I4_BIT_LO0 returns the position of the low 0 bit base 2 in an I4. - - Discussion: - - An I4 is an integer ( kind = 4 ) value. - - Example: - - N Binary Lo 0 - ---- -------- ---- - 0 0 1 - 1 1 2 - 2 10 1 - 3 11 3 - 4 100 1 - 5 101 2 - 6 110 1 - 7 111 4 - 8 1000 1 - 9 1001 2 - 10 1010 1 - 11 1011 3 - 12 1100 1 - 13 1101 2 - 14 1110 1 - 15 1111 5 - 16 10000 1 - 17 10001 2 - 1023 1111111111 11 - 1024 10000000000 1 - 1025 10000000001 2 - - Licensing: - - This code is distributed under the GNU LGPL license. - - Modified: - - 08 February 2018 - - Author: - - John Burkardt - - Parameters: - - Input, integer N, the integer to be measured. - N should be nonnegative. - - Output, integer BIT, the position of the low 1 bit. - - """ - bit = 0 - i = n - - while True: - - bit = bit + 1 - i2 = i // 2 - - if i == 2 * i2: - break - - i = i2 - - return bit - - -def i4_sobol_generate(m, n, skip): - """ - - - I4_SOBOL_GENERATE generates a Sobol dataset. - - Licensing: - - This code is distributed under the MIT license. - - Modified: - - 22 February 2011 - - Author: - - Original MATLAB version by John Burkardt. - PYTHON version by Corrado Chisari - - Parameters: - - Input, integer M, the spatial dimension. - - Input, integer N, the number of points to generate. - - Input, integer SKIP, the number of initial points to skip. - - Output, real R(M,N), the points. - - """ - r = np.zeros((m, n)) - for j in range(1, n + 1): - seed = skip + j - 2 - [r[0:m, j - 1], seed] = i4_sobol(m, seed) - return r - - -def i4_sobol(dim_num, seed): - """ - - - I4_SOBOL generates a new quasirandom Sobol vector with each call. - - Discussion: - - The routine adapts the ideas of Antonov and Saleev. - - Licensing: - - This code is distributed under the MIT license. - - Modified: - - 22 February 2011 - - Author: - - Original FORTRAN77 version by Bennett Fox. - MATLAB version by John Burkardt. - PYTHON version by Corrado Chisari - - Reference: - - Antonov, Saleev, - USSR Computational Mathematics and Mathematical Physics, - olume 19, 19, pages 252 - 256. - - Paul Bratley, Bennett Fox, - Algorithm 659: - Implementing Sobol's Quasirandom Sequence Generator, - ACM Transactions on Mathematical Software, - Volume 14, Number 1, pages 88-100, 1988. - - Bennett Fox, - Algorithm 647: - Implementation and Relative Efficiency of Quasirandom - Sequence Generators, - ACM Transactions on Mathematical Software, - Volume 12, Number 4, pages 362-376, 1986. - - Ilya Sobol, - USSR Computational Mathematics and Mathematical Physics, - Volume 16, pages 236-242, 1977. - - Ilya Sobol, Levitan, - The Production of Points Uniformly Distributed in a Multidimensional - Cube (in Russian), - Preprint IPM Akad. Nauk SSSR, - Number 40, Moscow 1976. - - Parameters: - - Input, integer DIM_NUM, the number of spatial dimensions. - DIM_NUM must satisfy 1 <= DIM_NUM <= 40. - - Input/output, integer SEED, the "seed" for the sequence. - This is essentially the index in the sequence of the quasirandom - value to be generated. On output, SEED has been set to the - appropriate next value, usually simply SEED+1. - If SEED is less than 0 on input, it is treated as though it were 0. - An input value of 0 requests the first (0-th) element of the sequence. - - Output, real QUASI(DIM_NUM), the next quasirandom vector. - - """ - - global atmost - global dim_max - global dim_num_save - global initialized - global lastq - global log_max - global maxcol - global poly - global recipd - global seed_save - global v - - if not initialized or dim_num != dim_num_save: - initialized = 1 - dim_max = 40 - dim_num_save = -1 - log_max = 30 - seed_save = -1 - # - # Initialize (part of) V. - # - v = np.zeros((dim_max, log_max)) - v[0:40, 0] = np.transpose([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ]) - - v[2:40, 1] = np.transpose([ - 1, 3, 1, 3, 1, 3, 3, 1, 3, 1, 3, 1, 3, 1, 1, 3, 1, 3, 1, 3, 1, 3, - 3, 1, 3, 1, 3, 1, 3, 1, 1, 3, 1, 3, 1, 3, 1, 3 - ]) - - v[3:40, 2] = np.transpose([ - 7, 5, 1, 3, 3, 7, 5, 5, 7, 7, 1, 3, 3, 7, 5, 1, 1, 5, 3, 3, 1, 7, - 5, 1, 3, 3, 7, 5, 1, 1, 5, 7, 7, 5, 1, 3, 3 - ]) - - v[5:40, 3] = np.transpose([ - 1, 7, 9, 13, 11, 1, 3, 7, 9, 5, 13, 13, 11, 3, 15, 5, 3, 15, 7, 9, - 13, 9, 1, 11, 7, 5, 15, 1, 15, 11, 5, 3, 1, 7, 9 - ]) - - v[7:40, 4] = np.transpose([ - 9, 3, 27, 15, 29, 21, 23, 19, 11, 25, 7, 13, 17, 1, 25, 29, 3, 31, - 11, 5, 23, 27, 19, 21, 5, 1, 17, 13, 7, 15, 9, 31, 9 - ]) - - v[13:40, 5] = np.transpose([ - 37, 33, 7, 5, 11, 39, 63, 27, 17, 15, 23, 29, 3, 21, 13, 31, 25, 9, - 49, 33, 19, 29, 11, 19, 27, 15, 25 - ]) - - v[19:40, 6] = np.transpose([ - 13, 33, 115, 41, 79, 17, 29, 119, 75, 73, 105, 7, 59, 65, 21, 3, - 113, 61, 89, 45, 107 - ]) - - v[37:40, 7] = np.transpose([7, 23, 39]) - # - # Set POLY. - # - poly = [ - 1, 3, 7, 11, 13, 19, 25, 37, 59, 47, 61, 55, 41, 67, 97, 91, 109, - 103, 115, 131, 193, 137, 145, 143, 241, 157, 185, 167, 229, 171, - 213, 191, 253, 203, 211, 239, 247, 285, 369, 299 - ] - - atmost = 2**log_max - 1 - # - # Find the number of bits in ATMOST. - # - maxcol = i4_bit_hi1(atmost) - # - # Initialize row 1 of V. - # - v[0, 0:maxcol] = 1 - - # Things to do only if the dimension changed. - - if dim_num != dim_num_save: - # - # Check parameters. - # - if (dim_num < 1 or dim_max < dim_num): - print('I4_SOBOL - Fatal error!') - print(' The spatial dimension DIM_NUM should satisfy:') - print(f' 1 <= DIM_NUM <= {dim_max:d}') - print(f' But this input value is DIM_NUM = {dim_num:d}') - return None - - dim_num_save = dim_num - # - # Initialize the remaining rows of V. - # - for i in range(2, dim_num + 1): - # - # The bits of the integer POLY(I) gives the form of polynomial - # I. - # - # Find the degree of polynomial I from binary encoding. - # - j = poly[i - 1] - m = 0 - while True: - j = math.floor(j / 2.) - if (j <= 0): - break - m = m + 1 - # - # Expand this bit pattern to separate components of the logical - # array INCLUD. - # - j = poly[i - 1] - includ = np.zeros(m) - for k in range(m, 0, -1): - j2 = math.floor(j / 2.) - includ[k - 1] = (j != 2 * j2) - j = j2 - # - # Calculate the remaining elements of row I as explained - # in Bratley and Fox, section 2. - # - for j in range(m + 1, maxcol + 1): - newv = v[i - 1, j - m - 1] - l_var = 1 - for k in range(1, m + 1): - l_var = 2 * l_var - if (includ[k - 1]): - newv = np.bitwise_xor( - int(newv), int(l_var * v[i - 1, j - k - 1])) - v[i - 1, j - 1] = newv -# -# Multiply columns of V by appropriate power of 2. -# - l_var = 1 - for j in range(maxcol - 1, 0, -1): - l_var = 2 * l_var - v[0:dim_num, j - 1] = v[0:dim_num, j - 1] * l_var -# -# RECIPD is 1/(common denominator of the elements in V). -# - recipd = 1.0 / (2 * l_var) - lastq = np.zeros(dim_num) - - seed = int(math.floor(seed)) - - if (seed < 0): - seed = 0 - - if (seed == 0): - l_var = 1 - lastq = np.zeros(dim_num) - - elif (seed == seed_save + 1): - # - # Find the position of the right-hand zero in SEED. - # - l_var = i4_bit_lo0(seed) - - elif seed <= seed_save: - - seed_save = 0 - lastq = np.zeros(dim_num) - - for seed_temp in range(int(seed_save), int(seed)): - l_var = i4_bit_lo0(seed_temp) - for i in range(1, dim_num + 1): - lastq[i - 1] = np.bitwise_xor( - int(lastq[i - 1]), int(v[i - 1, l_var - 1])) - - l_var = i4_bit_lo0(seed) - - elif (seed_save + 1 < seed): - - for seed_temp in range(int(seed_save + 1), int(seed)): - l_var = i4_bit_lo0(seed_temp) - for i in range(1, dim_num + 1): - lastq[i - 1] = np.bitwise_xor( - int(lastq[i - 1]), int(v[i - 1, l_var - 1])) - - l_var = i4_bit_lo0(seed) -# -# Check that the user is not calling too many times! -# - if maxcol < l_var: - print('I4_SOBOL - Fatal error!') - print(' Too many calls!') - print(f' MAXCOL = {maxcol:d}\n') - print(f' L = {l_var:d}\n') - return None - - -# -# Calculate the new components of QUASI. -# - quasi = np.zeros(dim_num) - for i in range(1, dim_num + 1): - quasi[i - 1] = lastq[i - 1] * recipd - lastq[i - 1] = np.bitwise_xor( - int(lastq[i - 1]), int(v[i - 1, l_var - 1])) - - seed_save = seed - seed = seed + 1 - - return [quasi, seed] - - -def i4_uniform_ab(a, b, seed): - """ - - - I4_UNIFORM_AB returns a scaled pseudorandom I4. - - Discussion: - - The pseudorandom number will be scaled to be uniformly distributed - between A and B. - - Licensing: - - This code is distributed under the GNU LGPL license. - - Modified: - - 05 April 2013 - - Author: - - John Burkardt - - Reference: - - Paul Bratley, Bennett Fox, Linus Schrage, - A Guide to Simulation, - Second Edition, - Springer, 1987, - ISBN: 0387964673, - LC: QA76.9.C65.B73. - - Bennett Fox, - Algorithm 647: - Implementation and Relative Efficiency of Quasirandom - Sequence Generators, - ACM Transactions on Mathematical Software, - Volume 12, Number 4, December 1986, pages 362-376. - - Pierre L'Ecuyer, - Random Number Generation, - in Handbook of Simulation, - edited by Jerry Banks, - Wiley, 1998, - ISBN: 0471134031, - LC: T57.62.H37. - - Peter Lewis, Allen Goodman, James Miller, - A Pseudo-Random Number Generator for the System/360, - IBM Systems Journal, - Volume 8, Number 2, 1969, pages 136-143. - - Parameters: - - Input, integer A, B, the minimum and maximum acceptable values. - - Input, integer SEED, a seed for the random number generator. - - Output, integer C, the randomly chosen integer. - - Output, integer SEED, the updated seed. - - """ - - i4_huge = 2147483647 - - seed = int(seed) - - seed = (seed % i4_huge) - - if seed < 0: - seed = seed + i4_huge - - if seed == 0: - print('') - print('I4_UNIFORM_AB - Fatal error!') - print(' Input SEED = 0!') - sys.exit('I4_UNIFORM_AB - Fatal error!') - - k = (seed // 127773) - - seed = 167 * (seed - k * 127773) - k * 2836 - - if seed < 0: - seed = seed + i4_huge - - r = seed * 4.656612875E-10 - # - # Scale R to lie between A-0.5 and B+0.5. - # - a = round(a) - b = round(b) - - r = (1.0 - r) * (min(a, b) - 0.5) \ - + r * (max(a, b) + 0.5) - # - # Use rounding to convert R to an integer between A and B. - # - value = round(r) - - value = max(value, min(a, b)) - value = min(value, max(a, b)) - value = int(value) - - return value, seed - - -def prime_ge(n): - """ - - - PRIME_GE returns the smallest prime greater than or equal to N. - - Example: - - N PRIME_GE - - -10 2 - 1 2 - 2 2 - 3 3 - 4 5 - 5 5 - 6 7 - 7 7 - 8 11 - 9 11 - 10 11 - - Licensing: - - This code is distributed under the MIT license. - - Modified: - - 22 February 2011 - - Author: - - Original MATLAB version by John Burkardt. - PYTHON version by Corrado Chisari - - Parameters: - - Input, integer N, the number to be bounded. - - Output, integer P, the smallest prime number that is greater - than or equal to N. - - """ - p = max(math.ceil(n), 2) - while not isprime(p): - p = p + 1 - - return p - - -def isprime(n): - """ - - - IS_PRIME returns True if N is a prime number, False otherwise - - Licensing: - - This code is distributed under the MIT license. - - Modified: - - 22 February 2011 - - Author: - - Corrado Chisari - - Parameters: - - Input, integer N, the number to be checked. - - Output, boolean value, True or False - - """ - if n != int(n) or n < 1: - return False - p = 2 - while p < n: - if n % p == 0: - return False - p += 1 - - return True - - -def r4_uniform_01(seed): - """ - - - R4_UNIFORM_01 returns a unit pseudorandom R4. - - Discussion: - - This routine implements the recursion - - seed = 167 * seed mod ( 2^31 - 1 ) - r = seed / ( 2^31 - 1 ) - - The integer arithmetic never requires more than 32 bits, - including a sign bit. - - If the initial seed is 12345, then the first three computations are - - Input Output R4_UNIFORM_01 - SEED SEED - - 12345 207482415 0.096616 - 207482415 1790989824 0.833995 - 1790989824 2035175616 0.947702 - - Licensing: - - This code is distributed under the GNU LGPL license. - - Modified: - - 04 April 2013 - - Author: - - John Burkardt - - Reference: - - Paul Bratley, Bennett Fox, Linus Schrage, - A Guide to Simulation, - Second Edition, - Springer, 1987, - ISBN: 0387964673, - LC: QA76.9.C65.B73. - - Bennett Fox, - Algorithm 647: - Implementation and Relative Efficiency of Quasirandom - Sequence Generators, - ACM Transactions on Mathematical Software, - Volume 12, Number 4, December 1986, pages 362-376. - - Pierre L'Ecuyer, - Random Number Generation, - in Handbook of Simulation, - edited by Jerry Banks, - Wiley, 1998, - ISBN: 0471134031, - LC: T57.62.H37. - - Peter Lewis, Allen Goodman, James Miller, - A Pseudo-Random Number Generator for the System/360, - IBM Systems Journal, - Volume 8, Number 2, 1969, pages 136-143. - - Parameters: - - Input, integer SEED, the integer "seed" used to generate - the output random number. SEED should not be 0. - - Output, real R, a random value between 0 and 1. - - Output, integer SEED, the updated seed. This would - normally be used as the input seed on the next call. - - """ - - i4_huge = 2147483647 - - if (seed == 0): - print('') - print('R4_UNIFORM_01 - Fatal error!') - print(' Input SEED = 0!') - sys.exit('R4_UNIFORM_01 - Fatal error!') - - seed = (seed % i4_huge) - - if seed < 0: - seed = seed + i4_huge - - k = (seed // 127773) - - seed = 167 * (seed - k * 127773) - k * 2836 - - if seed < 0: - seed = seed + i4_huge - - r = seed * 4.656612875E-10 - - return r, seed - - -def r8mat_write(filename, m, n, a): - """ - - - R8MAT_WRITE writes an R8MAT to a file. - - Licensing: - - This code is distributed under the GNU LGPL license. - - Modified: - - 12 October 2014 - - Author: - - John Burkardt - - Parameters: - - Input, string FILENAME, the name of the output file. - - Input, integer M, the number of rows in A. - - Input, integer N, the number of columns in A. - - Input, real A(M,N), the matrix. - """ - - with open(filename, 'w') as output: - for i in range(0, m): - for j in range(0, n): - s = f' {a[i, j]:g}' - output.write(s) - output.write('\n') - - -def tau_sobol(dim_num): - """ - - - TAU_SOBOL defines favorable starting seeds for Sobol sequences. - - Discussion: - - For spatial dimensions 1 through 13, this routine returns - a "favorable" value TAU by which an appropriate starting point - in the Sobol sequence can be determined. - - These starting points have the form N = 2**K, where - for integration problems, it is desirable that - TAU + DIM_NUM - 1 <= K - while for optimization problems, it is desirable that - TAU < K. - - Licensing: - - This code is distributed under the MIT license. - - Modified: - - 22 February 2011 - - Author: - - Original FORTRAN77 version by Bennett Fox. - MATLAB version by John Burkardt. - PYTHON version by Corrado Chisari - - Reference: - - IA Antonov, VM Saleev, - USSR Computational Mathematics and Mathematical Physics, - Volume 19, 19, pages 252 - 256. - - Paul Bratley, Bennett Fox, - Algorithm 659: - Implementing Sobol's Quasirandom Sequence Generator, - ACM Transactions on Mathematical Software, - Volume 14, Number 1, pages 88-100, 1988. - - Bennett Fox, - Algorithm 647: - Implementation and Relative Efficiency of Quasirandom - Sequence Generators, - ACM Transactions on Mathematical Software, - Volume 12, Number 4, pages 362-376, 1986. - - Stephen Joe, Frances Kuo - Remark on Algorithm 659: - Implementing Sobol's Quasirandom Sequence Generator, - ACM Transactions on Mathematical Software, - Volume 29, Number 1, pages 49-57, March 2003. - - Ilya Sobol, - USSR Computational Mathematics and Mathematical Physics, - Volume 16, pages 236-242, 1977. - - Ilya Sobol, YL Levitan, - The Production of Points Uniformly Distributed in a Multidimensional - Cube (in Russian), - Preprint IPM Akad. Nauk SSSR, - Number 40, Moscow 1976. - - Parameters: - - Input, integer DIM_NUM, the spatial dimension. Only values - of 1 through 13 will result in useful responses. - - Output, integer TAU, the value TAU. - - """ - dim_max = 13 - - tau_table = [0, 0, 1, 3, 5, 8, 11, 15, 19, 23, 27, 31, 35] - - if 1 <= dim_num <= dim_max: - tau = tau_table[dim_num] - else: - tau = -1 - - return tau diff --git a/PathPlanning/RRTDubins/__init__.py b/PathPlanning/RRTDubins/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/RRTDubins/rrt_dubins.py b/PathPlanning/RRTDubins/rrt_dubins.py deleted file mode 100644 index f938419f35..0000000000 --- a/PathPlanning/RRTDubins/rrt_dubins.py +++ /dev/null @@ -1,238 +0,0 @@ -""" -Path planning Sample Code with RRT with Dubins path - -author: AtsushiSakai(@Atsushi_twi) - -""" -import copy -import math -import random -import numpy as np -import matplotlib.pyplot as plt -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) # root dir -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from RRT.rrt import RRT -from DubinsPath import dubins_path_planner -from utils.plot import plot_arrow - -show_animation = True - - -class RRTDubins(RRT): - """ - Class for RRT planning with Dubins path - """ - - class Node(RRT.Node): - """ - RRT Node - """ - - def __init__(self, x, y, yaw): - super().__init__(x, y) - self.cost = 0 - self.yaw = yaw - self.path_yaw = [] - - def __init__(self, start, goal, obstacle_list, rand_area, - goal_sample_rate=10, - max_iter=200, - robot_radius=0.0 - ): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1], start[2]) - self.end = self.Node(goal[0], goal[1], goal[2]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - - self.curvature = 1.0 # for dubins path - self.goal_yaw_th = np.deg2rad(1.0) - self.goal_xy_th = 0.5 - self.robot_radius = robot_radius - - def planning(self, animation=True, search_until_max_iter=True): - """ - execute planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], rnd) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - self.node_list.append(new_node) - - if animation and i % 5 == 0: - self.plot_start_goal_arrow() - self.draw_graph(rnd) - - if (not search_until_max_iter) and new_node: # check reaching the goal - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - else: - print("Cannot find path") - - return None - - def draw_graph(self, rnd=None): # pragma: no cover - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - self.plot_start_goal_arrow() - plt.pause(0.01) - - def plot_start_goal_arrow(self): # pragma: no cover - plot_arrow(self.start.x, self.start.y, self.start.yaw) - plot_arrow(self.end.x, self.end.y, self.end.yaw) - - def steer(self, from_node, to_node): - - px, py, pyaw, mode, course_lengths = \ - dubins_path_planner.plan_dubins_path( - from_node.x, from_node.y, from_node.yaw, - to_node.x, to_node.y, to_node.yaw, self.curvature) - - if len(px) <= 1: # cannot find a dubins path - return None - - new_node = copy.deepcopy(from_node) - new_node.x = px[-1] - new_node.y = py[-1] - new_node.yaw = pyaw[-1] - - new_node.path_x = px - new_node.path_y = py - new_node.path_yaw = pyaw - new_node.cost += sum([abs(c) for c in course_lengths]) - new_node.parent = from_node - - return new_node - - def calc_new_cost(self, from_node, to_node): - - _, _, _, _, course_length = dubins_path_planner.plan_dubins_path( - from_node.x, from_node.y, from_node.yaw, - to_node.x, to_node.y, to_node.yaw, self.curvature) - - return from_node.cost + course_length - - def get_random_node(self): - - if random.randint(0, 100) > self.goal_sample_rate: - rnd = self.Node(random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand), - random.uniform(-math.pi, math.pi) - ) - else: # goal point sampling - rnd = self.Node(self.end.x, self.end.y, self.end.yaw) - - return rnd - - def search_best_goal_node(self): - - goal_indexes = [] - for (i, node) in enumerate(self.node_list): - if self.calc_dist_to_goal(node.x, node.y) <= self.goal_xy_th: - goal_indexes.append(i) - - # angle check - final_goal_indexes = [] - for i in goal_indexes: - if abs(self.node_list[i].yaw - self.end.yaw) <= self.goal_yaw_th: - final_goal_indexes.append(i) - - if not final_goal_indexes: - return None - - min_cost = min([self.node_list[i].cost for i in final_goal_indexes]) - for i in final_goal_indexes: - if self.node_list[i].cost == min_cost: - return i - - return None - - def generate_final_course(self, goal_index): - print("final") - path = [[self.end.x, self.end.y]] - node = self.node_list[goal_index] - while node.parent: - for (ix, iy) in zip(reversed(node.path_x), reversed(node.path_y)): - path.append([ix, iy]) - node = node.parent - path.append([self.start.x, self.start.y]) - return path - - -def main(): - - print("Start " + __file__) - # ====Search Path with RRT==== - obstacleList = [ - (5, 5, 1), - (3, 6, 2), - (3, 8, 2), - (3, 10, 2), - (7, 5, 2), - (9, 5, 2) - ] # [x,y,size(radius)] - - # Set Initial parameters - start = [0.0, 0.0, np.deg2rad(0.0)] - goal = [10.0, 10.0, np.deg2rad(0.0)] - - rrt_dubins = RRTDubins(start, goal, obstacleList, [-2.0, 15.0]) - path = rrt_dubins.planning(animation=show_animation) - - # Draw final path - if show_animation: # pragma: no cover - rrt_dubins.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.001) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRTStar/__init__.py b/PathPlanning/RRTStar/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/RRTStar/rrt_star.py b/PathPlanning/RRTStar/rrt_star.py deleted file mode 100644 index dcb1a066eb..0000000000 --- a/PathPlanning/RRTStar/rrt_star.py +++ /dev/null @@ -1,289 +0,0 @@ -""" - -Path planning Sample Code with RRT* - -author: Atsushi Sakai(@Atsushi_twi) - -""" - -import math -import sys -import matplotlib.pyplot as plt -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from RRT.rrt import RRT - -show_animation = True - - -class RRTStar(RRT): - """ - Class for RRT Star planning - """ - - class Node(RRT.Node): - def __init__(self, x, y): - super().__init__(x, y) - self.cost = 0.0 - - def __init__(self, - start, - goal, - obstacle_list, - rand_area, - expand_dis=30.0, - path_resolution=1.0, - goal_sample_rate=20, - max_iter=300, - connect_circle_dist=50.0, - search_until_max_iter=False, - robot_radius=0.0): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - - """ - super().__init__(start, goal, obstacle_list, rand_area, expand_dis, - path_resolution, goal_sample_rate, max_iter, - robot_radius=robot_radius) - self.connect_circle_dist = connect_circle_dist - self.goal_node = self.Node(goal[0], goal[1]) - self.search_until_max_iter = search_until_max_iter - self.node_list = [] - - def planning(self, animation=True): - """ - rrt star path planning - - animation: flag for animation on or off . - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], rnd, - self.expand_dis) - near_node = self.node_list[nearest_ind] - new_node.cost = near_node.cost + \ - math.hypot(new_node.x-near_node.x, - new_node.y-near_node.y) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - near_inds = self.find_near_nodes(new_node) - node_with_updated_parent = self.choose_parent( - new_node, near_inds) - if node_with_updated_parent: - self.rewire(node_with_updated_parent, near_inds) - self.node_list.append(node_with_updated_parent) - else: - self.node_list.append(new_node) - - if animation: - self.draw_graph(rnd) - - if ((not self.search_until_max_iter) - and new_node): # if reaches goal - last_index = self.search_best_goal_node() - if last_index is not None: - return self.generate_final_course(last_index) - - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index is not None: - return self.generate_final_course(last_index) - - return None - - def choose_parent(self, new_node, near_inds): - """ - Computes the cheapest point to new_node contained in the list - near_inds and set such a node as the parent of new_node. - Arguments: - -------- - new_node, Node - randomly generated node with a path from its neared point - There are not coalitions between this node and th tree. - near_inds: list - Indices of indices of the nodes what are near to new_node - - Returns. - ------ - Node, a copy of new_node - """ - if not near_inds: - return None - - # search nearest cost in near_inds - costs = [] - for i in near_inds: - near_node = self.node_list[i] - t_node = self.steer(near_node, new_node) - if t_node and self.check_collision( - t_node, self.obstacle_list, self.robot_radius): - costs.append(self.calc_new_cost(near_node, new_node)) - else: - costs.append(float("inf")) # the cost of collision node - min_cost = min(costs) - - if min_cost == float("inf"): - print("There is no good path.(min_cost is inf)") - return None - - min_ind = near_inds[costs.index(min_cost)] - new_node = self.steer(self.node_list[min_ind], new_node) - new_node.cost = min_cost - - return new_node - - def search_best_goal_node(self): - dist_to_goal_list = [ - self.calc_dist_to_goal(n.x, n.y) for n in self.node_list - ] - goal_inds = [ - dist_to_goal_list.index(i) for i in dist_to_goal_list - if i <= self.expand_dis - ] - - safe_goal_inds = [] - for goal_ind in goal_inds: - t_node = self.steer(self.node_list[goal_ind], self.goal_node) - if self.check_collision( - t_node, self.obstacle_list, self.robot_radius): - safe_goal_inds.append(goal_ind) - - if not safe_goal_inds: - return None - - safe_goal_costs = [self.node_list[i].cost + - self.calc_dist_to_goal(self.node_list[i].x, self.node_list[i].y) - for i in safe_goal_inds] - - min_cost = min(safe_goal_costs) - for i, cost in zip(safe_goal_inds, safe_goal_costs): - if cost == min_cost: - return i - - return None - - def find_near_nodes(self, new_node): - """ - 1) defines a ball centered on new_node - 2) Returns all nodes of the three that are inside this ball - Arguments: - --------- - new_node: Node - new randomly generated node, without collisions between - its nearest node - Returns: - ------- - list - List with the indices of the nodes inside the ball of - radius r - """ - nnode = len(self.node_list) + 1 - r = self.connect_circle_dist * math.sqrt(math.log(nnode) / nnode) - # if expand_dist exists, search vertices in a range no more than - # expand_dist - if hasattr(self, 'expand_dis'): - r = min(r, self.expand_dis) - dist_list = [(node.x - new_node.x)**2 + (node.y - new_node.y)**2 - for node in self.node_list] - near_inds = [dist_list.index(i) for i in dist_list if i <= r**2] - return near_inds - - def rewire(self, new_node, near_inds): - """ - For each node in near_inds, this will check if it is cheaper to - arrive to them from new_node. - In such a case, this will re-assign the parent of the nodes in - near_inds to new_node. - Parameters: - ---------- - new_node, Node - Node randomly added which can be joined to the tree - - near_inds, list of uints - A list of indices of the self.new_node which contains - nodes within a circle of a given radius. - Remark: parent is designated in choose_parent. - - """ - for i in near_inds: - near_node = self.node_list[i] - edge_node = self.steer(new_node, near_node) - if not edge_node: - continue - edge_node.cost = self.calc_new_cost(new_node, near_node) - - no_collision = self.check_collision( - edge_node, self.obstacle_list, self.robot_radius) - improved_cost = near_node.cost > edge_node.cost - - if no_collision and improved_cost: - for node in self.node_list: - if node.parent == self.node_list[i]: - node.parent = edge_node - self.node_list[i] = edge_node - self.propagate_cost_to_leaves(self.node_list[i]) - - def calc_new_cost(self, from_node, to_node): - d, _ = self.calc_distance_and_angle(from_node, to_node) - return from_node.cost + d - - def propagate_cost_to_leaves(self, parent_node): - - for node in self.node_list: - if node.parent == parent_node: - node.cost = self.calc_new_cost(parent_node, node) - self.propagate_cost_to_leaves(node) - - -def main(): - print("Start " + __file__) - - # ====Search Path with RRT==== - obstacle_list = [ - (5, 5, 1), - (3, 6, 2), - (3, 8, 2), - (3, 10, 2), - (7, 5, 2), - (9, 5, 2), - (8, 10, 1), - (6, 12, 1), - ] # [x,y,size(radius)] - - # Set Initial parameters - rrt_star = RRTStar( - start=[0, 0], - goal=[6, 10], - rand_area=[-2, 15], - obstacle_list=obstacle_list, - expand_dis=1, - robot_radius=0.8) - path = rrt_star.planning(animation=show_animation) - - if path is None: - print("Cannot find path") - else: - print("found path!!") - - # Draw final path - if show_animation: - rrt_star.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], 'r--') - plt.grid(True) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRTStarDubins/rrt_star_dubins.py b/PathPlanning/RRTStarDubins/rrt_star_dubins.py deleted file mode 100644 index 7c52879b7c..0000000000 --- a/PathPlanning/RRTStarDubins/rrt_star_dubins.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -Path planning Sample Code with RRT and Dubins path - -author: AtsushiSakai(@Atsushi_twi) - -""" -import copy -import math -import random -import matplotlib.pyplot as plt -import numpy as np -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) # root dir -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from DubinsPath import dubins_path_planner -from RRTStar.rrt_star import RRTStar -from utils.plot import plot_arrow - -show_animation = True - - -class RRTStarDubins(RRTStar): - """ - Class for RRT star planning with Dubins path - """ - - class Node(RRTStar.Node): - """ - RRT Node - """ - - def __init__(self, x, y, yaw): - super().__init__(x, y) - self.yaw = yaw - self.path_yaw = [] - - def __init__(self, start, goal, obstacle_list, rand_area, - goal_sample_rate=10, - max_iter=200, - connect_circle_dist=50.0, - robot_radius=0.0, - ): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1], start[2]) - self.end = self.Node(goal[0], goal[1], goal[2]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.goal_sample_rate = goal_sample_rate - self.max_iter = max_iter - self.obstacle_list = obstacle_list - self.connect_circle_dist = connect_circle_dist - - self.curvature = 1.0 # for dubins path - self.goal_yaw_th = np.deg2rad(1.0) - self.goal_xy_th = 0.5 - self.robot_radius = robot_radius - - def planning(self, animation=True, search_until_max_iter=True): - """ - RRT Star planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], rnd) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - near_indexes = self.find_near_nodes(new_node) - new_node = self.choose_parent(new_node, near_indexes) - if new_node: - self.node_list.append(new_node) - self.rewire(new_node, near_indexes) - - if animation and i % 5 == 0: - self.plot_start_goal_arrow() - self.draw_graph(rnd) - - if (not search_until_max_iter) and new_node: # check reaching the goal - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - else: - print("Cannot find path") - - return None - - def draw_graph(self, rnd=None): - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - self.plot_start_goal_arrow() - plt.pause(0.01) - - def plot_start_goal_arrow(self): - plot_arrow(self.start.x, self.start.y, self.start.yaw) - plot_arrow(self.end.x, self.end.y, self.end.yaw) - - def steer(self, from_node, to_node): - - px, py, pyaw, mode, course_lengths = \ - dubins_path_planner.plan_dubins_path( - from_node.x, from_node.y, from_node.yaw, - to_node.x, to_node.y, to_node.yaw, self.curvature) - - if len(px) <= 1: # cannot find a dubins path - return None - - new_node = copy.deepcopy(from_node) - new_node.x = px[-1] - new_node.y = py[-1] - new_node.yaw = pyaw[-1] - - new_node.path_x = px - new_node.path_y = py - new_node.path_yaw = pyaw - new_node.cost += sum([abs(c) for c in course_lengths]) - new_node.parent = from_node - - return new_node - - def calc_new_cost(self, from_node, to_node): - - _, _, _, _, course_lengths = dubins_path_planner.plan_dubins_path( - from_node.x, from_node.y, from_node.yaw, - to_node.x, to_node.y, to_node.yaw, self.curvature) - - cost = sum([abs(c) for c in course_lengths]) - - return from_node.cost + cost - - def get_random_node(self): - - if random.randint(0, 100) > self.goal_sample_rate: - rnd = self.Node(random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand), - random.uniform(-math.pi, math.pi) - ) - else: # goal point sampling - rnd = self.Node(self.end.x, self.end.y, self.end.yaw) - - return rnd - - def search_best_goal_node(self): - - goal_indexes = [] - for (i, node) in enumerate(self.node_list): - if self.calc_dist_to_goal(node.x, node.y) <= self.goal_xy_th: - goal_indexes.append(i) - - # angle check - final_goal_indexes = [] - for i in goal_indexes: - if abs(self.node_list[i].yaw - self.end.yaw) <= self.goal_yaw_th: - final_goal_indexes.append(i) - - if not final_goal_indexes: - return None - - min_cost = min([self.node_list[i].cost for i in final_goal_indexes]) - for i in final_goal_indexes: - if self.node_list[i].cost == min_cost: - return i - - return None - - def generate_final_course(self, goal_index): - print("final") - path = [[self.end.x, self.end.y]] - node = self.node_list[goal_index] - while node.parent: - for (ix, iy) in zip(reversed(node.path_x), reversed(node.path_y)): - path.append([ix, iy]) - node = node.parent - path.append([self.start.x, self.start.y]) - return path - - -def main(): - print("Start rrt star with dubins planning") - - # ====Search Path with RRT==== - obstacleList = [ - (5, 5, 1), - (3, 6, 2), - (3, 8, 2), - (3, 10, 2), - (7, 5, 2), - (9, 5, 2) - ] # [x,y,size(radius)] - - # Set Initial parameters - start = [0.0, 0.0, np.deg2rad(0.0)] - goal = [10.0, 10.0, np.deg2rad(0.0)] - - rrtstar_dubins = RRTStarDubins(start, goal, rand_area=[-2.0, 15.0], obstacle_list=obstacleList) - path = rrtstar_dubins.planning(animation=show_animation) - - # Draw final path - if show_animation: # pragma: no cover - rrtstar_dubins.draw_graph() - plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r') - plt.grid(True) - plt.pause(0.001) - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/RRTStarReedsShepp/rrt_star_reeds_shepp.py b/PathPlanning/RRTStarReedsShepp/rrt_star_reeds_shepp.py deleted file mode 100644 index c4c3e7c8a8..0000000000 --- a/PathPlanning/RRTStarReedsShepp/rrt_star_reeds_shepp.py +++ /dev/null @@ -1,265 +0,0 @@ -""" - -Path planning Sample Code with RRT with Reeds-Shepp path - -author: AtsushiSakai(@Atsushi_twi) - -""" -import copy -import math -import random -import sys -import pathlib -import matplotlib.pyplot as plt -import numpy as np -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from ReedsSheppPath import reeds_shepp_path_planning -from RRTStar.rrt_star import RRTStar - -show_animation = True - - -class RRTStarReedsShepp(RRTStar): - """ - Class for RRT star planning with Reeds Shepp path - """ - - class Node(RRTStar.Node): - """ - RRT Node - """ - - def __init__(self, x, y, yaw): - super().__init__(x, y) - self.yaw = yaw - self.path_yaw = [] - - def __init__(self, start, goal, obstacle_list, rand_area, - max_iter=200, step_size=0.2, - connect_circle_dist=50.0, - robot_radius=0.0 - ): - """ - Setting Parameter - - start:Start Position [x,y] - goal:Goal Position [x,y] - obstacleList:obstacle Positions [[x,y,size],...] - randArea:Random Sampling Area [min,max] - robot_radius: robot body modeled as circle with given radius - - """ - self.start = self.Node(start[0], start[1], start[2]) - self.end = self.Node(goal[0], goal[1], goal[2]) - self.min_rand = rand_area[0] - self.max_rand = rand_area[1] - self.max_iter = max_iter - self.step_size = step_size - self.obstacle_list = obstacle_list - self.connect_circle_dist = connect_circle_dist - self.robot_radius = robot_radius - - self.curvature = 1.0 - self.goal_yaw_th = np.deg2rad(1.0) - self.goal_xy_th = 0.5 - - def set_random_seed(self, seed): - random.seed(seed) - - def planning(self, animation=True, search_until_max_iter=True): - """ - planning - - animation: flag for animation on or off - """ - - self.node_list = [self.start] - for i in range(self.max_iter): - print("Iter:", i, ", number of nodes:", len(self.node_list)) - rnd = self.get_random_node() - nearest_ind = self.get_nearest_node_index(self.node_list, rnd) - new_node = self.steer(self.node_list[nearest_ind], rnd) - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - near_indexes = self.find_near_nodes(new_node) - new_node = self.choose_parent(new_node, near_indexes) - if new_node: - self.node_list.append(new_node) - self.rewire(new_node, near_indexes) - self.try_goal_path(new_node) - - if animation and i % 5 == 0: - self.plot_start_goal_arrow() - self.draw_graph(rnd) - - if (not search_until_max_iter) and new_node: # check reaching the goal - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - - print("reached max iteration") - - last_index = self.search_best_goal_node() - if last_index: - return self.generate_final_course(last_index) - else: - print("Cannot find path") - - return None - - def try_goal_path(self, node): - - goal = self.Node(self.end.x, self.end.y, self.end.yaw) - - new_node = self.steer(node, goal) - if new_node is None: - return - - if self.check_collision( - new_node, self.obstacle_list, self.robot_radius): - self.node_list.append(new_node) - - def draw_graph(self, rnd=None): - plt.clf() - # 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]) - if rnd is not None: - plt.plot(rnd.x, rnd.y, "^k") - for node in self.node_list: - if node.parent: - plt.plot(node.path_x, node.path_y, "-g") - - for (ox, oy, size) in self.obstacle_list: - plt.plot(ox, oy, "ok", ms=30 * size) - - plt.plot(self.start.x, self.start.y, "xr") - plt.plot(self.end.x, self.end.y, "xr") - plt.axis([-2, 15, -2, 15]) - plt.grid(True) - self.plot_start_goal_arrow() - plt.pause(0.01) - - def plot_start_goal_arrow(self): - reeds_shepp_path_planning.plot_arrow( - self.start.x, self.start.y, self.start.yaw) - reeds_shepp_path_planning.plot_arrow( - self.end.x, self.end.y, self.end.yaw) - - def steer(self, from_node, to_node): - - px, py, pyaw, mode, course_lengths = reeds_shepp_path_planning.reeds_shepp_path_planning( - from_node.x, from_node.y, from_node.yaw, to_node.x, - to_node.y, to_node.yaw, self.curvature, self.step_size) - - if not px: - return None - - new_node = copy.deepcopy(from_node) - new_node.x = px[-1] - new_node.y = py[-1] - new_node.yaw = pyaw[-1] - - new_node.path_x = px - new_node.path_y = py - new_node.path_yaw = pyaw - new_node.cost += sum([abs(l) for l in course_lengths]) - new_node.parent = from_node - - return new_node - - def calc_new_cost(self, from_node, to_node): - - _, _, _, _, course_lengths = reeds_shepp_path_planning.reeds_shepp_path_planning( - from_node.x, from_node.y, from_node.yaw, to_node.x, - to_node.y, to_node.yaw, self.curvature, self.step_size) - if not course_lengths: - return float("inf") - - return from_node.cost + sum([abs(l) for l in course_lengths]) - - def get_random_node(self): - - rnd = self.Node(random.uniform(self.min_rand, self.max_rand), - random.uniform(self.min_rand, self.max_rand), - random.uniform(-math.pi, math.pi) - ) - - return rnd - - def search_best_goal_node(self): - - goal_indexes = [] - for (i, node) in enumerate(self.node_list): - if self.calc_dist_to_goal(node.x, node.y) <= self.goal_xy_th: - goal_indexes.append(i) - print("goal_indexes:", len(goal_indexes)) - - # angle check - final_goal_indexes = [] - for i in goal_indexes: - if abs(self.node_list[i].yaw - self.end.yaw) <= self.goal_yaw_th: - final_goal_indexes.append(i) - - print("final_goal_indexes:", len(final_goal_indexes)) - - if not final_goal_indexes: - return None - - min_cost = min([self.node_list[i].cost for i in final_goal_indexes]) - print("min_cost:", min_cost) - for i in final_goal_indexes: - if self.node_list[i].cost == min_cost: - return i - - return None - - def generate_final_course(self, goal_index): - path = [[self.end.x, self.end.y, self.end.yaw]] - node = self.node_list[goal_index] - while node.parent: - for (ix, iy, iyaw) in zip(reversed(node.path_x), reversed(node.path_y), reversed(node.path_yaw)): - path.append([ix, iy, iyaw]) - node = node.parent - path.append([self.start.x, self.start.y, self.start.yaw]) - return path - - -def main(max_iter=100): - print("Start " + __file__) - - # ====Search Path with RRT==== - obstacleList = [ - (5, 5, 1), - (4, 6, 1), - (4, 8, 1), - (4, 10, 1), - (6, 5, 1), - (7, 5, 1), - (8, 6, 1), - (8, 8, 1), - (8, 10, 1) - ] # [x,y,size(radius)] - - # Set Initial parameters - start = [0.0, 0.0, np.deg2rad(0.0)] - goal = [6.0, 7.0, np.deg2rad(90.0)] - - rrt_star_reeds_shepp = RRTStarReedsShepp(start, goal, - obstacleList, - [-2.0, 15.0], max_iter=max_iter) - path = rrt_star_reeds_shepp.planning(animation=show_animation) - - # Draw final path - if path and show_animation: # pragma: no cover - rrt_star_reeds_shepp.draw_graph() - plt.plot([x for (x, y, yaw) in path], [y for (x, y, yaw) in path], '-r') - plt.grid(True) - plt.pause(0.001) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py b/PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py deleted file mode 100644 index 618d1d99ba..0000000000 --- a/PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py +++ /dev/null @@ -1,478 +0,0 @@ -""" - -Reeds Shepp path planner sample code - -author Atsushi Sakai(@Atsushi_twi) -co-author Videh Patel(@videh25) : Added the missing RS paths - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import math - -import matplotlib.pyplot as plt -import numpy as np -from utils.angle import angle_mod - -show_animation = True - - -class Path: - """ - Path data container - """ - - def __init__(self): - # course segment length (negative value is backward segment) - self.lengths = [] - # course segment type char ("S": straight, "L": left, "R": right) - self.ctypes = [] - self.L = 0.0 # Total lengths of the path - self.x = [] # x positions - self.y = [] # y positions - self.yaw = [] # orientations [rad] - self.directions = [] # directions (1:forward, -1:backward) - - -def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): - if isinstance(x, list): - 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 pi_2_pi(x): - return angle_mod(x) - -def mod2pi(x): - # Be consistent with fmod in cplusplus here. - v = np.mod(x, np.copysign(2.0 * math.pi, x)) - if v < -math.pi: - v += 2.0 * math.pi - else: - if v > math.pi: - v -= 2.0 * math.pi - return v - -def set_path(paths, lengths, ctypes, step_size): - path = Path() - path.ctypes = ctypes - path.lengths = lengths - path.L = sum(np.abs(lengths)) - - # check same path exist - for i_path in paths: - type_is_same = (i_path.ctypes == path.ctypes) - length_is_close = (sum(np.abs(i_path.lengths)) - path.L) <= step_size - if type_is_same and length_is_close: - return paths # same path found, so do not insert path - - # check path is long enough - if path.L <= step_size: - return paths # too short, so do not insert path - - paths.append(path) - return paths - - -def polar(x, y): - r = math.hypot(x, y) - theta = math.atan2(y, x) - return r, theta - - -def left_straight_left(x, y, phi): - u, t = polar(x - math.sin(phi), y - 1.0 + math.cos(phi)) - if 0.0 <= t <= math.pi: - v = mod2pi(phi - t) - if 0.0 <= v <= math.pi: - return True, [t, u, v], ['L', 'S', 'L'] - - return False, [], [] - - -def left_straight_right(x, y, phi): - u1, t1 = polar(x + math.sin(phi), y - 1.0 - math.cos(phi)) - u1 = u1 ** 2 - if u1 >= 4.0: - u = math.sqrt(u1 - 4.0) - theta = math.atan2(2.0, u) - t = mod2pi(t1 + theta) - v = mod2pi(t - phi) - - if (t >= 0.0) and (v >= 0.0): - return True, [t, u, v], ['L', 'S', 'R'] - - return False, [], [] - - -def left_x_right_x_left(x, y, phi): - zeta = x - math.sin(phi) - eeta = y - 1 + math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 <= 4.0: - A = math.acos(0.25 * u1) - t = mod2pi(A + theta + math.pi/2) - u = mod2pi(math.pi - 2 * A) - v = mod2pi(phi - t - u) - return True, [t, -u, v], ['L', 'R', 'L'] - - return False, [], [] - - -def left_x_right_left(x, y, phi): - zeta = x - math.sin(phi) - eeta = y - 1 + math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 <= 4.0: - A = math.acos(0.25 * u1) - t = mod2pi(A + theta + math.pi/2) - u = mod2pi(math.pi - 2*A) - v = mod2pi(-phi + t + u) - return True, [t, -u, -v], ['L', 'R', 'L'] - - return False, [], [] - - -def left_right_x_left(x, y, phi): - zeta = x - math.sin(phi) - eeta = y - 1 + math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 <= 4.0: - u = math.acos(1 - u1**2 * 0.125) - A = math.asin(2 * math.sin(u) / u1) - t = mod2pi(-A + theta + math.pi/2) - v = mod2pi(t - u - phi) - return True, [t, u, -v], ['L', 'R', 'L'] - - return False, [], [] - - -def left_right_x_left_right(x, y, phi): - zeta = x + math.sin(phi) - eeta = y - 1 - math.cos(phi) - u1, theta = polar(zeta, eeta) - - # Solutions refering to (2 < u1 <= 4) are considered sub-optimal in paper - # Solutions do not exist for u1 > 4 - if u1 <= 2: - A = math.acos((u1 + 2) * 0.25) - t = mod2pi(theta + A + math.pi/2) - u = mod2pi(A) - v = mod2pi(phi - t + 2*u) - if ((t >= 0) and (u >= 0) and (v >= 0)): - return True, [t, u, -u, -v], ['L', 'R', 'L', 'R'] - - return False, [], [] - - -def left_x_right_left_x_right(x, y, phi): - zeta = x + math.sin(phi) - eeta = y - 1 - math.cos(phi) - u1, theta = polar(zeta, eeta) - u2 = (20 - u1**2) / 16 - - if (0 <= u2 <= 1): - u = math.acos(u2) - A = math.asin(2 * math.sin(u) / u1) - t = mod2pi(theta + A + math.pi/2) - v = mod2pi(t - phi) - if (t >= 0) and (v >= 0): - return True, [t, -u, -u, v], ['L', 'R', 'L', 'R'] - - return False, [], [] - - -def left_x_right90_straight_left(x, y, phi): - zeta = x - math.sin(phi) - eeta = y - 1 + math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 >= 2.0: - u = math.sqrt(u1**2 - 4) - 2 - A = math.atan2(2, math.sqrt(u1**2 - 4)) - t = mod2pi(theta + A + math.pi/2) - v = mod2pi(t - phi + math.pi/2) - if (t >= 0) and (v >= 0): - return True, [t, -math.pi/2, -u, -v], ['L', 'R', 'S', 'L'] - - return False, [], [] - - -def left_straight_right90_x_left(x, y, phi): - zeta = x - math.sin(phi) - eeta = y - 1 + math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 >= 2.0: - u = math.sqrt(u1**2 - 4) - 2 - A = math.atan2(math.sqrt(u1**2 - 4), 2) - t = mod2pi(theta - A + math.pi/2) - v = mod2pi(t - phi - math.pi/2) - if (t >= 0) and (v >= 0): - return True, [t, u, math.pi/2, -v], ['L', 'S', 'R', 'L'] - - return False, [], [] - - -def left_x_right90_straight_right(x, y, phi): - zeta = x + math.sin(phi) - eeta = y - 1 - math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 >= 2.0: - t = mod2pi(theta + math.pi/2) - u = u1 - 2 - v = mod2pi(phi - t - math.pi/2) - if (t >= 0) and (v >= 0): - return True, [t, -math.pi/2, -u, -v], ['L', 'R', 'S', 'R'] - - return False, [], [] - - -def left_straight_left90_x_right(x, y, phi): - zeta = x + math.sin(phi) - eeta = y - 1 - math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 >= 2.0: - t = mod2pi(theta) - u = u1 - 2 - v = mod2pi(phi - t - math.pi/2) - if (t >= 0) and (v >= 0): - return True, [t, u, math.pi/2, -v], ['L', 'S', 'L', 'R'] - - return False, [], [] - - -def left_x_right90_straight_left90_x_right(x, y, phi): - zeta = x + math.sin(phi) - eeta = y - 1 - math.cos(phi) - u1, theta = polar(zeta, eeta) - - if u1 >= 4.0: - u = math.sqrt(u1**2 - 4) - 4 - A = math.atan2(2, math.sqrt(u1**2 - 4)) - t = mod2pi(theta + A + math.pi/2) - v = mod2pi(t - phi) - if (t >= 0) and (v >= 0): - return True, [t, -math.pi/2, -u, -math.pi/2, v], ['L', 'R', 'S', 'L', 'R'] - - return False, [], [] - - -def timeflip(travel_distances): - return [-x for x in travel_distances] - - -def reflect(steering_directions): - def switch_dir(dirn): - if dirn == 'L': - return 'R' - elif dirn == 'R': - return 'L' - else: - return 'S' - return[switch_dir(dirn) for dirn in steering_directions] - - -def generate_path(q0, q1, max_curvature, step_size): - dx = q1[0] - q0[0] - dy = q1[1] - q0[1] - dth = q1[2] - q0[2] - c = math.cos(q0[2]) - s = math.sin(q0[2]) - x = (c * dx + s * dy) * max_curvature - y = (-s * dx + c * dy) * max_curvature - step_size *= max_curvature - - paths = [] - path_functions = [left_straight_left, left_straight_right, # CSC - left_x_right_x_left, left_x_right_left, left_right_x_left, # CCC - left_right_x_left_right, left_x_right_left_x_right, # CCCC - left_x_right90_straight_left, left_x_right90_straight_right, # CCSC - left_straight_right90_x_left, left_straight_left90_x_right, # CSCC - left_x_right90_straight_left90_x_right] # CCSCC - - for path_func in path_functions: - flag, travel_distances, steering_dirns = path_func(x, y, dth) - if flag: - for distance in travel_distances: - if (0.1*sum([abs(d) for d in travel_distances]) < abs(distance) < step_size): - print("Step size too large for Reeds-Shepp paths.") - return [] - paths = set_path(paths, travel_distances, steering_dirns, step_size) - - flag, travel_distances, steering_dirns = path_func(-x, y, -dth) - if flag: - for distance in travel_distances: - if (0.1*sum([abs(d) for d in travel_distances]) < abs(distance) < step_size): - print("Step size too large for Reeds-Shepp paths.") - return [] - travel_distances = timeflip(travel_distances) - paths = set_path(paths, travel_distances, steering_dirns, step_size) - - flag, travel_distances, steering_dirns = path_func(x, -y, -dth) - if flag: - for distance in travel_distances: - if (0.1*sum([abs(d) for d in travel_distances]) < abs(distance) < step_size): - print("Step size too large for Reeds-Shepp paths.") - return [] - steering_dirns = reflect(steering_dirns) - paths = set_path(paths, travel_distances, steering_dirns, step_size) - - flag, travel_distances, steering_dirns = path_func(-x, -y, dth) - if flag: - for distance in travel_distances: - if (0.1*sum([abs(d) for d in travel_distances]) < abs(distance) < step_size): - print("Step size too large for Reeds-Shepp paths.") - return [] - travel_distances = timeflip(travel_distances) - steering_dirns = reflect(steering_dirns) - paths = set_path(paths, travel_distances, steering_dirns, step_size) - - return paths - - -def calc_interpolate_dists_list(lengths, step_size): - interpolate_dists_list = [] - for length in lengths: - d_dist = step_size if length >= 0.0 else -step_size - interp_dists = np.arange(0.0, length, d_dist) - interp_dists = np.append(interp_dists, length) - interpolate_dists_list.append(interp_dists) - - return interpolate_dists_list - - -def generate_local_course(lengths, modes, max_curvature, step_size): - interpolate_dists_list = calc_interpolate_dists_list(lengths, step_size * max_curvature) - - origin_x, origin_y, origin_yaw = 0.0, 0.0, 0.0 - - xs, ys, yaws, directions = [], [], [], [] - for (interp_dists, mode, length) in zip(interpolate_dists_list, modes, - lengths): - - for dist in interp_dists: - x, y, yaw, direction = interpolate(dist, length, mode, - max_curvature, origin_x, - origin_y, origin_yaw) - xs.append(x) - ys.append(y) - yaws.append(yaw) - directions.append(direction) - origin_x = xs[-1] - origin_y = ys[-1] - origin_yaw = yaws[-1] - - return xs, ys, yaws, directions - - -def interpolate(dist, length, mode, max_curvature, origin_x, origin_y, - origin_yaw): - if mode == "S": - x = origin_x + dist / max_curvature * math.cos(origin_yaw) - y = origin_y + dist / max_curvature * math.sin(origin_yaw) - yaw = origin_yaw - else: # curve - ldx = math.sin(dist) / max_curvature - ldy = 0.0 - yaw = None - if mode == "L": # left turn - ldy = (1.0 - math.cos(dist)) / max_curvature - yaw = origin_yaw + dist - elif mode == "R": # right turn - ldy = (1.0 - math.cos(dist)) / -max_curvature - yaw = origin_yaw - dist - gdx = math.cos(-origin_yaw) * ldx + math.sin(-origin_yaw) * ldy - gdy = -math.sin(-origin_yaw) * ldx + math.cos(-origin_yaw) * ldy - x = origin_x + gdx - y = origin_y + gdy - - return x, y, yaw, 1 if length > 0.0 else -1 - - -def calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size): - q0 = [sx, sy, syaw] - q1 = [gx, gy, gyaw] - - paths = generate_path(q0, q1, maxc, step_size) - for path in paths: - xs, ys, yaws, directions = generate_local_course(path.lengths, - path.ctypes, maxc, - step_size) - - # convert global coordinate - path.x = [math.cos(-q0[2]) * ix + math.sin(-q0[2]) * iy + q0[0] for - (ix, iy) in zip(xs, ys)] - path.y = [-math.sin(-q0[2]) * ix + math.cos(-q0[2]) * iy + q0[1] for - (ix, iy) in zip(xs, ys)] - path.yaw = [pi_2_pi(yaw + q0[2]) for yaw in yaws] - path.directions = directions - path.lengths = [length / maxc for length in path.lengths] - path.L = path.L / maxc - - return paths - - -def reeds_shepp_path_planning(sx, sy, syaw, gx, gy, gyaw, maxc, step_size=0.2): - paths = calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size) - if not paths: - return None, None, None, None, None # could not generate any path - - # search minimum cost path - best_path_index = paths.index(min(paths, key=lambda p: abs(p.L))) - b_path = paths[best_path_index] - - return b_path.x, b_path.y, b_path.yaw, b_path.ctypes, b_path.lengths - - -def main(): - print("Reeds Shepp path planner sample start!!") - - start_x = -1.0 # [m] - start_y = -4.0 # [m] - start_yaw = np.deg2rad(-20.0) # [rad] - - end_x = 5.0 # [m] - end_y = 5.0 # [m] - end_yaw = np.deg2rad(25.0) # [rad] - - curvature = 0.1 - step_size = 0.05 - - xs, ys, yaws, modes, lengths = reeds_shepp_path_planning(start_x, start_y, - start_yaw, end_x, - end_y, end_yaw, - curvature, - step_size) - - if not xs: - assert False, "No path" - - if show_animation: # pragma: no cover - plt.cla() - plt.plot(xs, ys, label="final course " + str(modes)) - print(f"{lengths=}") - - # plotting - plot_arrow(start_x, start_y, start_yaw) - plot_arrow(end_x, end_y, end_yaw) - - plt.legend() - plt.grid(True) - plt.axis("equal") - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/SpiralSpanningTreeCPP/map/test.png b/PathPlanning/SpiralSpanningTreeCPP/map/test.png deleted file mode 100644 index 4abca0bf30..0000000000 Binary files a/PathPlanning/SpiralSpanningTreeCPP/map/test.png and /dev/null differ diff --git a/PathPlanning/SpiralSpanningTreeCPP/map/test_2.png b/PathPlanning/SpiralSpanningTreeCPP/map/test_2.png deleted file mode 100644 index 0d27fa9f95..0000000000 Binary files a/PathPlanning/SpiralSpanningTreeCPP/map/test_2.png and /dev/null differ diff --git a/PathPlanning/SpiralSpanningTreeCPP/map/test_3.png b/PathPlanning/SpiralSpanningTreeCPP/map/test_3.png deleted file mode 100644 index 1a50b87ccf..0000000000 Binary files a/PathPlanning/SpiralSpanningTreeCPP/map/test_3.png and /dev/null differ diff --git a/PathPlanning/SpiralSpanningTreeCPP/spiral_spanning_tree_coverage_path_planner.py b/PathPlanning/SpiralSpanningTreeCPP/spiral_spanning_tree_coverage_path_planner.py deleted file mode 100644 index a8c513e6b1..0000000000 --- a/PathPlanning/SpiralSpanningTreeCPP/spiral_spanning_tree_coverage_path_planner.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -Spiral Spanning Tree Coverage Path Planner - -author: Todd Tang -paper: Spiral-STC: An On-Line Coverage Algorithm of Grid Environments - by a Mobile Robot - Gabriely et.al. -link: https://ieeexplore.ieee.org/abstract/document/1013479 -""" - -import os -import sys -import math - -import numpy as np -import matplotlib.pyplot as plt - -do_animation = True - - -class SpiralSpanningTreeCoveragePlanner: - def __init__(self, occ_map): - self.origin_map_height = occ_map.shape[0] - self.origin_map_width = occ_map.shape[1] - - # original map resolution must be even - if self.origin_map_height % 2 == 1 or self.origin_map_width % 2 == 1: - sys.exit('original map width/height must be even \ - in grayscale .png format') - - self.occ_map = occ_map - self.merged_map_height = self.origin_map_height // 2 - self.merged_map_width = self.origin_map_width // 2 - - self.edge = [] - - def plan(self, start): - """plan - - performing Spiral Spanning Tree Coverage path planning - - :param start: the start node of Spiral Spanning Tree Coverage - """ - - visit_times = np.zeros( - (self.merged_map_height, self.merged_map_width), dtype=int) - visit_times[start[0]][start[1]] = 1 - - # generate route by - # recusively call perform_spanning_tree_coverage() from start node - route = [] - self.perform_spanning_tree_coverage(start, visit_times, route) - - path = [] - # generate path from route - for idx in range(len(route)-1): - dp = abs(route[idx][0] - route[idx+1][0]) + \ - abs(route[idx][1] - route[idx+1][1]) - if dp == 0: - # special handle for round-trip path - path.append(self.get_round_trip_path(route[idx-1], route[idx])) - elif dp == 1: - path.append(self.move(route[idx], route[idx+1])) - elif dp == 2: - # special handle for non-adjacent route nodes - mid_node = self.get_intermediate_node(route[idx], route[idx+1]) - path.append(self.move(route[idx], mid_node)) - path.append(self.move(mid_node, route[idx+1])) - else: - sys.exit('adjacent path node distance larger than 2') - - return self.edge, route, path - - def perform_spanning_tree_coverage(self, current_node, visit_times, route): - """perform_spanning_tree_coverage - - recursive function for function - - :param current_node: current node - """ - - def is_valid_node(i, j): - is_i_valid_bounded = 0 <= i < self.merged_map_height - is_j_valid_bounded = 0 <= j < self.merged_map_width - if is_i_valid_bounded and is_j_valid_bounded: - # free only when the 4 sub-cells are all free - return bool( - self.occ_map[2*i][2*j] - and self.occ_map[2*i+1][2*j] - and self.occ_map[2*i][2*j+1] - and self.occ_map[2*i+1][2*j+1]) - - return False - - # counter-clockwise neighbor finding order - order = [[1, 0], [0, 1], [-1, 0], [0, -1]] - - found = False - route.append(current_node) - for inc in order: - ni, nj = current_node[0] + inc[0], current_node[1] + inc[1] - if is_valid_node(ni, nj) and visit_times[ni][nj] == 0: - neighbor_node = (ni, nj) - self.edge.append((current_node, neighbor_node)) - found = True - visit_times[ni][nj] += 1 - self.perform_spanning_tree_coverage( - neighbor_node, visit_times, route) - - # backtrace route from node with neighbors all visited - # to first node with unvisited neighbor - if not found: - has_node_with_unvisited_ngb = False - for node in reversed(route): - # drop nodes that have been visited twice - if visit_times[node[0]][node[1]] == 2: - continue - - visit_times[node[0]][node[1]] += 1 - route.append(node) - - for inc in order: - ni, nj = node[0] + inc[0], node[1] + inc[1] - if is_valid_node(ni, nj) and visit_times[ni][nj] == 0: - has_node_with_unvisited_ngb = True - break - - if has_node_with_unvisited_ngb: - break - - return route - - def move(self, p, q): - direction = self.get_vector_direction(p, q) - # move east - if direction == 'E': - p = self.get_sub_node(p, 'SE') - q = self.get_sub_node(q, 'SW') - # move west - elif direction == 'W': - p = self.get_sub_node(p, 'NW') - q = self.get_sub_node(q, 'NE') - # move south - elif direction == 'S': - p = self.get_sub_node(p, 'SW') - q = self.get_sub_node(q, 'NW') - # move north - elif direction == 'N': - p = self.get_sub_node(p, 'NE') - q = self.get_sub_node(q, 'SE') - else: - sys.exit('move direction error...') - return [p, q] - - def get_round_trip_path(self, last, pivot): - direction = self.get_vector_direction(last, pivot) - if direction == 'E': - return [self.get_sub_node(pivot, 'SE'), - self.get_sub_node(pivot, 'NE')] - elif direction == 'S': - return [self.get_sub_node(pivot, 'SW'), - self.get_sub_node(pivot, 'SE')] - elif direction == 'W': - return [self.get_sub_node(pivot, 'NW'), - self.get_sub_node(pivot, 'SW')] - elif direction == 'N': - return [self.get_sub_node(pivot, 'NE'), - self.get_sub_node(pivot, 'NW')] - else: - sys.exit('get_round_trip_path: last->pivot direction error.') - - def get_vector_direction(self, p, q): - # east - if p[0] == q[0] and p[1] < q[1]: - return 'E' - # west - elif p[0] == q[0] and p[1] > q[1]: - return 'W' - # south - elif p[0] < q[0] and p[1] == q[1]: - return 'S' - # north - elif p[0] > q[0] and p[1] == q[1]: - return 'N' - else: - sys.exit('get_vector_direction: Only E/W/S/N direction supported.') - - def get_sub_node(self, node, direction): - if direction == 'SE': - return [2*node[0]+1, 2*node[1]+1] - elif direction == 'SW': - return [2*node[0]+1, 2*node[1]] - elif direction == 'NE': - return [2*node[0], 2*node[1]+1] - elif direction == 'NW': - return [2*node[0], 2*node[1]] - else: - sys.exit('get_sub_node: sub-node direction error.') - - def get_interpolated_path(self, p, q): - # direction p->q: southwest / northeast - if (p[0] < q[0]) ^ (p[1] < q[1]): - ipx = [p[0], p[0], q[0]] - ipy = [p[1], q[1], q[1]] - # direction p->q: southeast / northwest - else: - ipx = [p[0], q[0], q[0]] - ipy = [p[1], p[1], q[1]] - return ipx, ipy - - def get_intermediate_node(self, p, q): - p_ngb, q_ngb = set(), set() - - for m, n in self.edge: - if m == p: - p_ngb.add(n) - if n == p: - p_ngb.add(m) - if m == q: - q_ngb.add(n) - if n == q: - q_ngb.add(m) - - itsc = p_ngb.intersection(q_ngb) - if len(itsc) == 0: - sys.exit('get_intermediate_node: \ - no intermediate node between', p, q) - elif len(itsc) == 1: - return list(itsc)[0] - else: - sys.exit('get_intermediate_node: \ - more than 1 intermediate node between', p, q) - - def visualize_path(self, edge, path, start): - def coord_transform(p): - return [2*p[1] + 0.5, 2*p[0] + 0.5] - - if do_animation: - last = path[0][0] - trajectory = [[last[1]], [last[0]]] - for p, q in path: - distance = math.hypot(p[0]-last[0], p[1]-last[1]) - if distance <= 1.0: - trajectory[0].append(p[1]) - trajectory[1].append(p[0]) - else: - ipx, ipy = self.get_interpolated_path(last, p) - trajectory[0].extend(ipy) - trajectory[1].extend(ipx) - - last = q - - trajectory[0].append(last[1]) - trajectory[1].append(last[0]) - - for idx, state in enumerate(np.transpose(trajectory)): - 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]) - - # draw spanning tree - plt.imshow(self.occ_map, 'gray') - for p, q in edge: - p = coord_transform(p) - q = coord_transform(q) - plt.plot([p[0], q[0]], [p[1], q[1]], '-oc') - sx, sy = coord_transform(start) - plt.plot([sx], [sy], 'pr', markersize=10) - - # draw move path - plt.plot(trajectory[0][:idx+1], trajectory[1][:idx+1], '-k') - plt.plot(state[0], state[1], 'or') - plt.axis('equal') - plt.grid(True) - plt.pause(0.01) - - else: - # draw spanning tree - plt.imshow(self.occ_map, 'gray') - for p, q in edge: - p = coord_transform(p) - q = coord_transform(q) - plt.plot([p[0], q[0]], [p[1], q[1]], '-oc') - sx, sy = coord_transform(start) - plt.plot([sx], [sy], 'pr', markersize=10) - - # draw move path - last = path[0][0] - for p, q in path: - distance = math.hypot(p[0]-last[0], p[1]-last[1]) - if distance == 1.0: - plt.plot([last[1], p[1]], [last[0], p[0]], '-k') - else: - ipx, ipy = self.get_interpolated_path(last, p) - plt.plot(ipy, ipx, '-k') - plt.arrow(p[1], p[0], q[1]-p[1], q[0]-p[0], head_width=0.2) - last = q - - plt.show() - - -def main(): - dir_path = os.path.dirname(os.path.realpath(__file__)) - img = plt.imread(os.path.join(dir_path, 'map', 'test_2.png')) - STC_planner = SpiralSpanningTreeCoveragePlanner(img) - start = (10, 0) - edge, route, path = STC_planner.plan(start) - STC_planner.visualize_path(edge, path, start) - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/StateLatticePlanner/__init__.py b/PathPlanning/StateLatticePlanner/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/StateLatticePlanner/lookup_table.csv b/PathPlanning/StateLatticePlanner/lookup_table.csv deleted file mode 100644 index 2c48a11a18..0000000000 --- a/PathPlanning/StateLatticePlanner/lookup_table.csv +++ /dev/null @@ -1,82 +0,0 @@ -x,y,yaw,s,km,kf -1.0,0.0,0.0,1.0,0.0,0.0 -9.975352133559392,0.0482183513545736,0.5660837104214496,10.00000000000002,0.0015736624607596847,0.31729782170754367 -15.021899857204536,0.023109001221800096,0.541061424167431,15.128443053611093,0.0006480950273134919,0.20847475849103875 -20.062147834064536,0.0406648159648112,0.5374967866814861,20.205553097986094,0.000452860235044122,0.15502921850050788 -24.924468605496358,-0.04047324014767662,0.5146575311501209,25.16775431470035,6.940620839146646e-05,0.12259452810198132 -9.971782095506931,1.9821448683146543,0.5206511572266477,10.287478424823748,0.05861430948618236,0.07036494964262185 -15.00170010872385,2.0003864283110473,0.5236741185892617,15.245993376540827,0.02657730439557895,0.09933479864250763 -19.991716537639487,1.9940629519465154,0.5226444441451559,20.277923997037238,0.015108540596275507,0.09478988637993524 -24.946447973869596,2.0815930190993206,0.5306354765686239,25.20115925294013,0.010036251429787917,0.08505936469481987 -10.033694822745312,3.9724800521928056,0.5349578864544497,11.087694168363686,0.10279429366023954,-0.12501011715795404 -15.010712586144749,4.0046776414868095,0.5234972445048012,15.729582155835587,0.05010930398580602,-0.0008557723200034717 -19.9175798257299,4.053680042954521,0.5265397234296523,20.52466275843717,0.029584390559990882,0.035276591227371874 -24.98769016158626,3.991699950598091,0.5229000018897194,25.543297770996556,0.018800715817231053,0.04750751098144048 -10.018665105170687,6.004814533505462,0.5183921334245007,12.249438857228967,0.13207408248643182,-0.2742892277307502 -14.988626131330372,5.991226410357179,0.5248160422552801,16.53593823576699,0.06924423592936522,-0.08634227857486092 -20.014420271646646,6.006767110727591,0.5233060851224174,21.23271362632659,0.041402041787912916,-0.01770377839603589 -24.998338724667267,5.997352722087869,0.5235621854299422,26.009046544833613,0.027285850882345728,0.011507045054418165 -10.040118020822444,8.017131894913126,0.5076867575242261,13.8061261785273,0.14561700178565884,-0.3527538468214878 -14.96397914886416,7.974972375534203,0.5303378183744862,17.667338175004062,0.08318912494381935,-0.15372672981944802 -20.045725182938817,8.023486945646207,0.5201839069343577,22.126364299043573,0.05173969669894265,-0.06557547083017647 -25.004687466358227,8.00036398460779,0.5234938146870878,26.740089158520917,0.034867425244601645,-0.02199309906456302 -10.065138949829214,10.03244363616002,0.49375882493214895,15.701940360254637,0.14847998727328912,-0.39355037236614626 -15.05373212031198,10.026401491912143,0.5111826366036252,19.105461052226858,0.09205576730549936,-0.20458802229704312 -19.965550451103926,9.977668905006206,0.5278605653376056,23.14082140870299,0.06010674632014157,-0.10340577652521214 -25.04062496016141,9.956781577401756,0.5252395961316738,27.641194908523495,0.04115083532669924,-0.05054407239730677 -9.980172344087242,11.981944953180841,0.5354924711458446,17.764377273124804,0.14616069587267325,-0.40115138946106776 -15.031707905116134,12.011530784459552,0.5157261778129998,20.745000892047745,0.0970285785481706,-0.2379719980195133 -20.05384921212195,12.02621662711961,0.5153884987125119,24.513013940487117,0.06601543941341544,-0.13666530932375262 -25.04326204059146,12.019946808479768,0.5198699857547844,28.74306622689766,0.04671545692054678,-0.07827401225777673 -10.005005976167096,13.993516346269931,0.5249997047973469,20.063732124124442,0.14007166951362482,-0.39994549637994103 -15.013469777117386,13.998572375088138,0.5211760827701193,22.591299061495683,0.0989196134377572,-0.25909446951756165 -19.980150236409695,13.98233838451409,0.5278966095736082,25.971685915503254,0.07029833263412807,-0.15993299513197096 -25.009669110020404,14.000751236643762,0.5227555344229664,29.949071374991423,0.05106114063333748,-0.09952052168406796 -9.996712859814979,15.986637217372996,0.5301458018536311,22.47478825250167,0.1329741433122606,-0.38823042580907063 -15.02373126750475,16.00384009060484,0.5182833077580984,24.557463511123004,0.0989491582793761,-0.26836010532851323 -20.023756339113735,16.004847752803656,0.5197401980953318,27.669260302891157,0.07275720314277462,-0.178811991371391 -25.015003771679122,16.002919774604504,0.5219791758565742,31.36524983655211,0.05429827198598215,-0.11766440355003502 -10.078596822781892,18.025309925487992,0.49768408992179225,25.02580432036455,0.1252233187334947,-0.3747545825918585 -15.001968473293188,17.988033772017467,0.5262415135796346,26.67625473617623,0.09746306394892065,-0.27167606206451594 -20.026047062413117,18.00445752595148,0.5193568548054093,29.442158339897595,0.07417227896231118,-0.19015828496001386 -24.984711558393403,17.982744830235063,0.5266809346220684,32.855828700083094,0.05675308229799072,-0.13090299334069386 -9.999999973554228,8.906672256498078e-05,-0.00024912926939091307,9.993250237275609,1.9794429093204823e-06,-0.00016167063578544257 -14.999999988951053,0.00030609885737583985,-9.259737492950393e-05,14.939586274030715,4.066161982234725e-06,-5.3230908443270726e-05 -19.999999963637627,0.0008196433029304879,-0.00010277758318455454,19.985770355960977,6.0902800817987275e-06,-5.581407356116362e-05 -24.999999906323,0.001558015443394581,-0.0001252423879458675,24.925430653319882,7.508303551937611e-06,-5.98269885073166e-05 -9.93201732790474,1.9700581591656137,-0.006606314895513332,10.1625049701131,0.05795554613825405,-0.23594780459354886 -15.017121844754504,2.000131018972639,-0.001958259181754851,15.130494387563031,0.026367577418638183,-0.10529363184139814 -19.962697589600058,2.0003823634817484,0.0021983556339688626,20.055058569558643,0.014972854970102445,-0.0592998512022201 -24.990093248087035,2.0008914594513647,0.0003319006512292333,25.020899019312747,0.009609456446194334,-0.03808543941908436 -9.942924437331126,3.9434423219621073,-0.047789349898090805,10.916318098481405,0.10417074854184473,-0.42509733550937057 -14.976393375378994,3.9987876606083557,0.004653465622298736,15.69826778341493,0.04981535482126709,-0.20027162173052074 -19.954160472557877,4.000101578371634,0.0053292950039418585,20.459066225465484,0.02905576509783228,-0.11479451096219842 -25.06247590490118,3.9997579161047643,-0.00486183691237807,25.40723367563786,0.01874893916371208,-0.07533000027879669 -9.974854017566281,5.998183884411291,0.01394025812352817,12.27808815775426,0.13163310345287574,-0.5111693653344966 -14.99829771854157,5.999020207860274,0.0007330116466723879,16.57520987140955,0.06880393034208837,-0.27508456151767885 -19.98389776689381,5.999506195067484,0.002770060727207646,21.17690590277397,0.04131547230609369,-0.1652252863196287 -25.022089217041394,5.998166050230614,-0.002551136444779001,25.974625009044832,0.02718132258204399,-0.10978755041013998 -9.940106683734614,7.99448983539684,0.03735909486314526,13.864600506318645,0.14554135993596395,-0.5498471044599721 -15.015405965817797,7.996301502316838,-0.004430455799697253,17.779484729664652,0.08234534796805798,-0.3300198333333338 -19.965919061860355,7.998456498324741,0.00732927315681664,22.0665101267907,0.05178054118886435,-0.20507088323830897 -24.97580637673196,7.998036396987909,0.0034859866489540536,26.699711792661176,0.03478260921646504,-0.13959734880932403 -10.003237328881212,9.994037173180942,-0.002542633641336778,15.800576175296408,0.1482242831571022,-0.5606578442626601 -14.95848212484301,9.995827033229693,0.016804720248816185,19.19635868417634,0.09159937492256161,-0.3610497877526804 -20.018365340632464,9.997789133099982,-0.003880405312526758,23.259977677838524,0.05967179836565363,-0.23873172503708404 -25.034844162753302,9.996613275552045,-0.005490232481425661,27.647073656497884,0.04122997694830456,-0.16548182742762063 -10.041413516307436,11.988808245039152,-0.015743247245750158,18.0174427655263,0.14424296158815444,-0.5545987939832356 -15.0710608536628,11.993636485613393,-0.025235844052727163,20.92474299071291,0.0960774359909814,-0.38199459745149106 -20.061838597733104,11.995243972143648,-0.015325438311212025,24.63090823780847,0.06556771814265559,-0.2626353022718591 -24.90813949494271,11.995929681233529,0.01760171116909426,28.6986397040137,0.046810556161518815,-0.1847353186190147 -10.005191819464756,13.97797567430312,0.018961636911005275,20.358534835690133,0.13825179056925302,-0.5307789523538471 -14.978392340358946,13.991362718235834,0.012411272386128935,22.755419658274054,0.0984622955030996,-0.38447788120958937 -20.015767113356507,13.992558840024987,-0.002205036951612893,26.18420998778461,0.06961025144239422,-0.2786494668163888 -25.01318440442437,13.994258255793202,-0.0016239998449329995,30.09124393513656,0.05071043613803722,-0.20387658283659768 -10.038844117562423,15.966797017942504,0.016384527088525225,22.88736140380268,0.13044436631301143,-0.5070826347325453 -14.91898245890566,15.984279670640529,0.03784081306841358,24.796728185207627,0.09830913950807817,-0.38207974071854045 -19.999487117727806,15.99041117221354,0.0034823225688951354,27.881676426972927,0.07220430103629995,-0.2873083396987492 -25.056418472201756,15.995103453935709,-0.011257522827095023,31.50238694595278,0.05406499488342877,-0.21526296035737832 -10.076107447676621,17.952889979512353,0.017798231103724138,25.454959881832874,0.1231232463335769,-0.47600174850950705 -15.032725028551983,17.978015286760307,0.0020752804670070013,27.089888269358894,0.09590219542773218,-0.3801465515462427 -20.03544756240551,17.98685790169768,-0.005300968094156033,29.75070206477736,0.07340450527104486,-0.29182757725382324 -24.960019173190652,17.98909417109214,0.011594018486178026,33.0995680641525,0.05634561447882407,-0.22402297280749597 diff --git a/PathPlanning/StateLatticePlanner/state_lattice_planner.py b/PathPlanning/StateLatticePlanner/state_lattice_planner.py deleted file mode 100644 index 554cd0f3b7..0000000000 --- a/PathPlanning/StateLatticePlanner/state_lattice_planner.py +++ /dev/null @@ -1,345 +0,0 @@ -""" - -State lattice planner with model predictive trajectory generator - -author: Atsushi Sakai (@Atsushi_twi) - -- plookuptable.csv is generated with this script: -https://github.com/AtsushiSakai/PythonRobotics/blob/master/PathPlanning -/ModelPredictiveTrajectoryGenerator/lookup_table_generator.py - -Reference: - -- State Space Sampling of Feasible Motions for High-Performance Mobile Robot -Navigation in Complex Environments -https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf -&doi=e2256b5b24137f89e473f01df288cb3aa72e56a0 - -""" -import sys -import os -from matplotlib import pyplot as plt -import numpy as np -import math -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent)) -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import ModelPredictiveTrajectoryGenerator.trajectory_generator as planner -import ModelPredictiveTrajectoryGenerator.motion_model as motion_model - -TABLE_PATH = os.path.dirname(os.path.abspath(__file__)) + "/lookup_table.csv" - -show_animation = True - - -def search_nearest_one_from_lookup_table(t_x, t_y, t_yaw, lookup_table): - mind = float("inf") - minid = -1 - - for (i, table) in enumerate(lookup_table): - dx = t_x - table[0] - dy = t_y - table[1] - dyaw = t_yaw - table[2] - d = math.sqrt(dx ** 2 + dy ** 2 + dyaw ** 2) - if d <= mind: - minid = i - mind = d - - return lookup_table[minid] - - -def get_lookup_table(table_path): - return np.loadtxt(table_path, delimiter=',', skiprows=1) - - -def generate_path(target_states, k0): - # x, y, yaw, s, km, kf - lookup_table = get_lookup_table(TABLE_PATH) - result = [] - - for state in target_states: - bestp = search_nearest_one_from_lookup_table( - state[0], state[1], state[2], lookup_table) - - target = motion_model.State(x=state[0], y=state[1], yaw=state[2]) - init_p = np.array( - [np.hypot(state[0], state[1]), bestp[4], bestp[5]]).reshape(3, 1) - - x, y, yaw, p = planner.optimize_trajectory(target, k0, init_p) - - if x is not None: - print("find good path") - result.append( - [x[-1], y[-1], yaw[-1], float(p[0, 0]), float(p[1, 0]), float(p[2, 0])]) - - print("finish path generation") - return result - - -def calc_uniform_polar_states(nxy, nh, d, a_min, a_max, p_min, p_max): - """ - - Parameters - ---------- - nxy : - number of position sampling - nh : - number of heading sampleing - d : - distance of terminal state - a_min : - position sampling min angle - a_max : - position sampling max angle - p_min : - heading sampling min angle - p_max : - heading sampling max angle - - Returns - ------- - - """ - angle_samples = [i / (nxy - 1) for i in range(nxy)] - states = sample_states(angle_samples, a_min, a_max, d, p_max, p_min, nh) - - return states - - -def calc_biased_polar_states(goal_angle, ns, nxy, nh, d, a_min, a_max, p_min, p_max): - """ - calc biased state - - :param goal_angle: goal orientation for biased sampling - :param ns: number of biased sampling - :param nxy: number of position sampling - :param nxy: number of position sampling - :param nh: number of heading sampleing - :param d: distance of terminal state - :param a_min: position sampling min angle - :param a_max: position sampling max angle - :param p_min: heading sampling min angle - :param p_max: heading sampling max angle - :return: states list - """ - - asi = [a_min + (a_max - a_min) * i / (ns - 1) for i in range(ns - 1)] - cnav = [math.pi - abs(i - goal_angle) for i in asi] - - cnav_sum = sum(cnav) - cnav_max = max(cnav) - - # normalize - cnav = [(cnav_max - cnav[i]) / (cnav_max * ns - cnav_sum) - for i in range(ns - 1)] - - csumnav = np.cumsum(cnav) - di = [] - li = 0 - for i in range(nxy): - for ii in range(li, ns - 1): - if ii / ns >= i / (nxy - 1): - di.append(csumnav[ii]) - li = ii - 1 - break - - states = sample_states(di, a_min, a_max, d, p_max, p_min, nh) - - return states - - -def calc_lane_states(l_center, l_heading, l_width, v_width, d, nxy): - """ - - calc lane states - - :param l_center: lane lateral position - :param l_heading: lane heading - :param l_width: lane width - :param v_width: vehicle width - :param d: longitudinal position - :param nxy: sampling number - :return: state list - """ - xc = d - yc = l_center - - states = [] - for i in range(nxy): - delta = -0.5 * (l_width - v_width) + \ - (l_width - v_width) * i / (nxy - 1) - xf = xc - delta * math.sin(l_heading) - yf = yc + delta * math.cos(l_heading) - yawf = l_heading - states.append([xf, yf, yawf]) - - return states - - -def sample_states(angle_samples, a_min, a_max, d, p_max, p_min, nh): - states = [] - for i in angle_samples: - a = a_min + (a_max - a_min) * i - - for j in range(nh): - xf = d * math.cos(a) - yf = d * math.sin(a) - if nh == 1: - yawf = (p_max - p_min) / 2 + a - else: - yawf = p_min + (p_max - p_min) * j / (nh - 1) + a - states.append([xf, yf, yawf]) - - return states - - -def uniform_terminal_state_sampling_test1(): - k0 = 0.0 - nxy = 5 - nh = 3 - d = 20 - a_min = - np.deg2rad(45.0) - a_max = np.deg2rad(45.0) - p_min = - np.deg2rad(45.0) - p_max = np.deg2rad(45.0) - states = calc_uniform_polar_states(nxy, nh, d, a_min, a_max, p_min, p_max) - result = generate_path(states, k0) - - for table in result: - xc, yc, yawc = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - - if show_animation: - plt.plot(xc, yc, "-r") - - if show_animation: - plt.grid(True) - plt.axis("equal") - plt.show() - - print("Done") - - -def uniform_terminal_state_sampling_test2(): - k0 = 0.1 - nxy = 6 - nh = 3 - d = 20 - a_min = - np.deg2rad(-10.0) - a_max = np.deg2rad(45.0) - p_min = - np.deg2rad(20.0) - p_max = np.deg2rad(20.0) - states = calc_uniform_polar_states(nxy, nh, d, a_min, a_max, p_min, p_max) - result = generate_path(states, k0) - - for table in result: - xc, yc, yawc = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - - if show_animation: - plt.plot(xc, yc, "-r") - - if show_animation: - plt.grid(True) - plt.axis("equal") - plt.show() - - print("Done") - - -def biased_terminal_state_sampling_test1(): - k0 = 0.0 - nxy = 30 - nh = 2 - d = 20 - a_min = np.deg2rad(-45.0) - a_max = np.deg2rad(45.0) - p_min = - np.deg2rad(20.0) - p_max = np.deg2rad(20.0) - ns = 100 - goal_angle = np.deg2rad(0.0) - states = calc_biased_polar_states( - goal_angle, ns, nxy, nh, d, a_min, a_max, p_min, p_max) - result = generate_path(states, k0) - - for table in result: - xc, yc, yawc = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - if show_animation: - plt.plot(xc, yc, "-r") - - if show_animation: - plt.grid(True) - plt.axis("equal") - plt.show() - - -def biased_terminal_state_sampling_test2(): - k0 = 0.0 - nxy = 30 - nh = 1 - d = 20 - a_min = np.deg2rad(0.0) - a_max = np.deg2rad(45.0) - p_min = - np.deg2rad(20.0) - p_max = np.deg2rad(20.0) - ns = 100 - goal_angle = np.deg2rad(30.0) - states = calc_biased_polar_states( - goal_angle, ns, nxy, nh, d, a_min, a_max, p_min, p_max) - result = generate_path(states, k0) - - for table in result: - xc, yc, yawc = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - - if show_animation: - plt.plot(xc, yc, "-r") - - if show_animation: - plt.grid(True) - plt.axis("equal") - plt.show() - - -def lane_state_sampling_test1(): - k0 = 0.0 - - l_center = 10.0 - l_heading = np.deg2rad(0.0) - l_width = 3.0 - v_width = 1.0 - d = 10 - nxy = 5 - states = calc_lane_states(l_center, l_heading, l_width, v_width, d, nxy) - result = generate_path(states, k0) - - if show_animation: - plt.close("all") - - for table in result: - x_c, y_c, yaw_c = motion_model.generate_trajectory( - table[3], table[4], table[5], k0) - - if show_animation: - plt.plot(x_c, y_c, "-r") - - if show_animation: - plt.grid(True) - plt.axis("equal") - plt.show() - - -def main(): - planner.show_animation = show_animation - uniform_terminal_state_sampling_test1() - uniform_terminal_state_sampling_test2() - biased_terminal_state_sampling_test1() - biased_terminal_state_sampling_test2() - lane_state_sampling_test1() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/TimeBasedPathPlanning/BaseClasses.py b/PathPlanning/TimeBasedPathPlanning/BaseClasses.py deleted file mode 100644 index 745cde45fb..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/BaseClasses.py +++ /dev/null @@ -1,49 +0,0 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - Position, -) -from PathPlanning.TimeBasedPathPlanning.Node import NodePath -import random -import numpy.random as numpy_random - -# Seed randomness for reproducibility -RANDOM_SEED = 50 -random.seed(RANDOM_SEED) -numpy_random.seed(RANDOM_SEED) - -class SingleAgentPlanner(ABC): - """ - Base class for single agent planners - """ - - @staticmethod - @abstractmethod - def plan(grid: Grid, start: Position, goal: Position, verbose: bool = False) -> NodePath: - pass - -@dataclass -class StartAndGoal: - # Index of this agent - index: int - # Start position of the robot - start: Position - # Goal position of the robot - goal: Position - - def distance_start_to_goal(self) -> float: - return pow(self.goal.x - self.start.x, 2) + pow(self.goal.y - self.start.y, 2) - -class MultiAgentPlanner(ABC): - """ - Base class for multi-agent planners - """ - - @staticmethod - @abstractmethod - def plan(grid: Grid, start_and_goal_positions: list[StartAndGoal], single_agent_planner_class: SingleAgentPlanner, verbose: bool = False) -> tuple[list[StartAndGoal], list[NodePath]]: - """ - Plan for all agents. Returned paths are in order corresponding to the returned list of `StartAndGoal` objects - """ - pass \ No newline at end of file diff --git a/PathPlanning/TimeBasedPathPlanning/GridWithDynamicObstacles.py b/PathPlanning/TimeBasedPathPlanning/GridWithDynamicObstacles.py deleted file mode 100644 index ccc2989001..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/GridWithDynamicObstacles.py +++ /dev/null @@ -1,370 +0,0 @@ -""" -This file implements a grid with a 3d reservation matrix with dimensions for x, y, and time. There -is also infrastructure to generate dynamic obstacles that move around the grid. The obstacles' paths -are stored in the reservation matrix on creation. -""" -import numpy as np -import matplotlib.pyplot as plt -from enum import Enum -from dataclasses import dataclass -from PathPlanning.TimeBasedPathPlanning.Node import NodePath, Position - -@dataclass -class Interval: - start_time: int - end_time: int - -class ObstacleArrangement(Enum): - # Random obstacle positions and movements - RANDOM = 0 - # Obstacles start in a line in y at center of grid and move side-to-side in x - ARRANGEMENT1 = 1 - # Static obstacle arrangement - NARROW_CORRIDOR = 2 - -""" -Generates a 2d numpy array with lists for elements. -""" -def empty_2d_array_of_lists(x: int, y: int) -> np.ndarray: - arr = np.empty((x, y), dtype=object) - # assign each element individually - np.full creates references to the same list - arr[:] = [[[] for _ in range(y)] for _ in range(x)] - return arr - -class Grid: - # Set in constructor - grid_size: np.ndarray - reservation_matrix: np.ndarray - obstacle_paths: list[list[Position]] = [] - # Obstacles will never occupy these points. Useful to avoid impossible scenarios - obstacle_avoid_points: list[Position] = [] - - # Number of time steps in the simulation - time_limit: int - - # Logging control - verbose = False - - def __init__( - self, - grid_size: np.ndarray, - num_obstacles: int = 40, - obstacle_avoid_points: list[Position] = [], - obstacle_arrangement: ObstacleArrangement = ObstacleArrangement.RANDOM, - time_limit: int = 100, - ): - self.obstacle_avoid_points = obstacle_avoid_points - self.time_limit = time_limit - self.grid_size = grid_size - self.reservation_matrix = np.zeros((grid_size[0], grid_size[1], self.time_limit)) - - if num_obstacles > self.grid_size[0] * self.grid_size[1]: - raise Exception("Number of obstacles is greater than grid size!") - - if obstacle_arrangement == ObstacleArrangement.RANDOM: - self.obstacle_paths = self.generate_dynamic_obstacles(num_obstacles) - elif obstacle_arrangement == ObstacleArrangement.ARRANGEMENT1: - self.obstacle_paths = self.obstacle_arrangement_1(num_obstacles) - elif obstacle_arrangement == ObstacleArrangement.NARROW_CORRIDOR: - self.obstacle_paths = self.generate_narrow_corridor_obstacles(num_obstacles) - - for i, path in enumerate(self.obstacle_paths): - obs_idx = i + 1 # avoid using 0 - that indicates free space in the grid - for t, position in enumerate(path): - # Reserve old & new position at this time step - if t > 0: - self.reservation_matrix[path[t - 1].x, path[t - 1].y, t] = obs_idx - self.reservation_matrix[position.x, position.y, t] = obs_idx - - """ - Generate dynamic obstacles that move around the grid. Initial positions and movements are random - """ - def generate_dynamic_obstacles(self, obs_count: int) -> list[list[Position]]: - obstacle_paths = [] - for _ in range(0, obs_count): - # Sample until a free starting space is found - initial_position = self.sample_random_position() - while not self.valid_obstacle_position(initial_position, 0): - initial_position = self.sample_random_position() - - positions = [initial_position] - if self.verbose: - print("Obstacle initial position: ", initial_position) - - # Encourage obstacles to mostly stay in place - too much movement leads to chaotic planning scenarios - # that are not fun to watch - weights = [0.05, 0.05, 0.05, 0.05, 0.8] - diffs = [ - Position(0, 1), - Position(0, -1), - Position(1, 0), - Position(-1, 0), - Position(0, 0), - ] - - for t in range(1, self.time_limit - 1): - sampled_indices = np.random.choice( - len(diffs), size=5, replace=False, p=weights - ) - rand_diffs = [diffs[i] for i in sampled_indices] - - valid_position = None - for diff in rand_diffs: - new_position = positions[-1] + diff - - if not self.valid_obstacle_position(new_position, t): - continue - - valid_position = new_position - break - - # Impossible situation for obstacle - stay in place - # -> this can happen if the oaths of other obstacles this one - if valid_position is None: - valid_position = positions[-1] - - positions.append(valid_position) - - obstacle_paths.append(positions) - - return obstacle_paths - - """ - Generate a line of obstacles in y at the center of the grid that move side-to-side in x - Bottom half start moving right, top half start moving left. If `obs_count` is less than the length of - the grid, only the first `obs_count` obstacles will be generated. - """ - def obstacle_arrangement_1(self, obs_count: int) -> list[list[Position]]: - obstacle_paths = [] - half_grid_x = self.grid_size[0] // 2 - half_grid_y = self.grid_size[1] // 2 - - for y_idx in range(0, min(obs_count, self.grid_size[1])): - moving_right = y_idx < half_grid_y - position = Position(half_grid_x, y_idx) - path = [position] - - for t in range(1, self.time_limit - 1): - # sit in place every other time step - if t % 2 == 0: - path.append(position) - continue - - # first check if we should switch direction (at edge of grid) - if (moving_right and position.x == self.grid_size[0] - 1) or ( - not moving_right and position.x == 0 - ): - moving_right = not moving_right - # step in direction - position = Position( - position.x + (1 if moving_right else -1), position.y - ) - path.append(position) - - obstacle_paths.append(path) - - return obstacle_paths - - def generate_narrow_corridor_obstacles(self, obs_count: int) -> list[list[Position]]: - obstacle_paths = [] - - for y in range(0, self.grid_size[1]): - if y > obs_count: - break - - if y == self.grid_size[1] // 2: - # Skip the middle row - continue - - obstacle_path = [] - x = self.grid_size[0] // 2 # middle of the grid - for t in range(0, self.time_limit - 1): - obstacle_path.append(Position(x, y)) - - obstacle_paths.append(obstacle_path) - - return obstacle_paths - - """ - Check if the given position is valid at time t - - input: - position (Position): (x, y) position - t (int): time step - - output: - bool: True if position/time combination is valid, False otherwise - """ - def valid_position(self, position: Position, t: int) -> bool: - # Check if position is in grid - if not self.inside_grid_bounds(position): - return False - - # Check if position is not occupied at time t - return self.reservation_matrix[position.x, position.y, t] == 0 - - """ - Returns True if the given position is valid at time t and is not in the set of obstacle_avoid_points - """ - def valid_obstacle_position(self, position: Position, t: int) -> bool: - return ( - self.valid_position(position, t) - and position not in self.obstacle_avoid_points - ) - - """ - Returns True if the given position is within the grid's boundaries - """ - def inside_grid_bounds(self, position: Position) -> bool: - return ( - position.x >= 0 - and position.x < self.grid_size[0] - and position.y >= 0 - and position.y < self.grid_size[1] - ) - - """ - Sample a random position that is within the grid's boundaries - - output: - Position: (x, y) position - """ - def sample_random_position(self) -> Position: - return Position( - np.random.randint(0, self.grid_size[0]), - np.random.randint(0, self.grid_size[1]), - ) - - """ - Returns a tuple of (x_positions, y_positions) of the obstacles at time t - """ - def get_obstacle_positions_at_time(self, t: int) -> tuple[list[int], list[int]]: - x_positions = [] - y_positions = [] - for obs_path in self.obstacle_paths: - x_positions.append(obs_path[t].x) - y_positions.append(obs_path[t].y) - return (x_positions, y_positions) - - """ - Returns safe intervals for each cell. - """ - def get_safe_intervals(self) -> np.ndarray: - intervals = empty_2d_array_of_lists(self.grid_size[0], self.grid_size[1]) - for x in range(intervals.shape[0]): - for y in range(intervals.shape[1]): - intervals[x, y] = self.get_safe_intervals_at_cell(Position(x, y)) - - return intervals - - """ - Generate the safe intervals for a given cell. The intervals will be in order of start time. - ex: Interval (2, 3) will be before Interval (4, 5) - """ - def get_safe_intervals_at_cell(self, cell: Position) -> list[Interval]: - vals = self.reservation_matrix[cell.x, cell.y, :] - # Find where the array is zero - zero_mask = (vals == 0) - - # Identify transitions between zero and nonzero elements - diff = np.diff(zero_mask.astype(int)) - - # Start indices: where zeros begin (1 after a nonzero) - start_indices = np.where(diff == 1)[0] + 1 - - # End indices: where zeros stop (just before a nonzero) - end_indices = np.where(diff == -1)[0] - - # Handle edge cases if the array starts or ends with zeros - if zero_mask[0]: # If the first element is zero, add index 0 to start_indices - start_indices = np.insert(start_indices, 0, 0) - if zero_mask[-1]: # If the last element is zero, add the last index to end_indices - end_indices = np.append(end_indices, len(vals) - 1) - - # Create pairs of (first zero, last zero) - intervals = [Interval(int(start), int(end)) for start, end in zip(start_indices, end_indices)] - - # Remove intervals where a cell is only free for one time step. Those intervals not provide enough time to - # move into and out of the cell each take 1 time step, and the cell is considered occupied during - # both the time step when it is entering the cell, and the time step when it is leaving the cell. - intervals = [interval for interval in intervals if interval.start_time != interval.end_time] - return intervals - - """ - Reserve an agent's path in the grid. Raises an exception if the agent's index is 0, or if a position is - already reserved by a different agent. - """ - def reserve_path(self, node_path: NodePath, agent_index: int): - if agent_index == 0: - raise Exception("Agent index cannot be 0") - - for i, node in enumerate(node_path.path): - reservation_finish_time = node.time + 1 - if i < len(node_path.path) - 1: - reservation_finish_time = node_path.path[i + 1].time - - self.reserve_position(node.position, agent_index, Interval(node.time, reservation_finish_time)) - - """ - Reserve a position for the provided agent during the provided time interval. - Raises an exception if the agent's index is 0, or if the position is already reserved by a different agent during the interval. - """ - def reserve_position(self, position: Position, agent_index: int, interval: Interval): - if agent_index == 0: - raise Exception("Agent index cannot be 0") - - for t in range(interval.start_time, interval.end_time + 1): - current_reserver = self.reservation_matrix[position.x, position.y, t] - if current_reserver not in [0, agent_index]: - raise Exception( - f"Agent {agent_index} tried to reserve a position already reserved by another agent: {position} at time {t}, reserved by {current_reserver}" - ) - self.reservation_matrix[position.x, position.y, t] = agent_index - - """ - Clears the initial reservation for an agent by clearing reservations at its start position with its index for - from time 0 to the time limit. - """ - def clear_initial_reservation(self, position: Position, agent_index: int): - for t in range(self.time_limit): - if self.reservation_matrix[position.x, position.y, t] == agent_index: - self.reservation_matrix[position.x, position.y, t] = 0 - -show_animation = True - -def main(): - grid = Grid( - np.array([11, 11]), - num_obstacles=10, - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - ) - - if not show_animation: - return - - fig = plt.figure(figsize=(8, 7)) - ax = fig.add_subplot( - autoscale_on=False, - xlim=(0, grid.grid_size[0] - 1), - ylim=(0, grid.grid_size[1] - 1), - ) - ax.set_aspect("equal") - ax.grid() - ax.set_xticks(np.arange(0, 11, 1)) - ax.set_yticks(np.arange(0, 11, 1)) - (obs_points,) = ax.plot([], [], "ro", ms=15) - - # 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] - ) - - for i in range(0, grid.time_limit - 1): - obs_positions = grid.get_obstacle_positions_at_time(i) - obs_points.set_data(obs_positions[0], obs_positions[1]) - plt.pause(0.2) - plt.show() - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/TimeBasedPathPlanning/Node.py b/PathPlanning/TimeBasedPathPlanning/Node.py deleted file mode 100644 index 728eebb676..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/Node.py +++ /dev/null @@ -1,99 +0,0 @@ -from dataclasses import dataclass -from functools import total_ordering -import numpy as np -from typing import Sequence - -@dataclass(order=True) -class Position: - x: int - y: int - - def as_ndarray(self) -> np.ndarray: - return np.array([self.x, self.y]) - - def __add__(self, other): - if isinstance(other, Position): - return Position(self.x + other.x, self.y + other.y) - raise NotImplementedError( - f"Addition not supported for Position and {type(other)}" - ) - - def __sub__(self, other): - if isinstance(other, Position): - return Position(self.x - other.x, self.y - other.y) - raise NotImplementedError( - f"Subtraction not supported for Position and {type(other)}" - ) - - def __hash__(self): - return hash((self.x, self.y)) - -@dataclass() -# Note: Total_ordering is used instead of adding `order=True` to the @dataclass decorator because -# this class needs to override the __lt__ and __eq__ methods to ignore parent_index. Parent -# index is just used to track the path found by the algorithm, and has no effect on the quality -# of a node. -@total_ordering -class Node: - position: Position - time: int - heuristic: int - parent_index: int - - """ - This is what is used to drive node expansion. The node with the lowest value is expanded next. - This comparison prioritizes the node with the lowest cost-to-come (self.time) + cost-to-go (self.heuristic) - """ - def __lt__(self, other: object): - if not isinstance(other, Node): - return NotImplementedError(f"Cannot compare Node with object of type: {type(other)}") - return (self.time + self.heuristic) < (other.time + other.heuristic) - - """ - Note: cost and heuristic are not included in eq or hash, since they will always be the same - for a given (position, time) pair. Including either cost or heuristic would be redundant. - """ - def __eq__(self, other: object): - if not isinstance(other, Node): - return NotImplementedError(f"Cannot compare Node with object of type: {type(other)}") - return self.position == other.position and self.time == other.time - - def __hash__(self): - return hash((self.position, self.time)) - -class NodePath: - path: Sequence[Node] - positions_at_time: dict[int, Position] - # Number of nodes expanded while finding this path - expanded_node_count: int - - def __init__(self, path: Sequence[Node], expanded_node_count: int): - self.path = path - self.expanded_node_count = expanded_node_count - - self.positions_at_time = {} - for i, node in enumerate(path): - reservation_finish_time = node.time + 1 - if i < len(path) - 1: - reservation_finish_time = path[i + 1].time - - for t in range(node.time, reservation_finish_time): - self.positions_at_time[t] = node.position - - """ - Get the position of the path at a given time - """ - def get_position(self, time: int) -> Position | None: - return self.positions_at_time.get(time) - - """ - Time stamp of the last node in the path - """ - def goal_reached_time(self) -> int: - return self.path[-1].time - - def __repr__(self): - repr_string = "" - for i, node in enumerate(self.path): - repr_string += f"{i}: {node}\n" - return repr_string \ No newline at end of file diff --git a/PathPlanning/TimeBasedPathPlanning/Plotting.py b/PathPlanning/TimeBasedPathPlanning/Plotting.py deleted file mode 100644 index 7cd1f615d8..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/Plotting.py +++ /dev/null @@ -1,135 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.backend_bases import KeyEvent -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - Position, -) -from PathPlanning.TimeBasedPathPlanning.BaseClasses import StartAndGoal -from PathPlanning.TimeBasedPathPlanning.Node import NodePath - -''' -Plot a single agent path. -''' -def PlotNodePath(grid: Grid, start: Position, goal: Position, path: NodePath): - fig = plt.figure(figsize=(10, 7)) - ax = fig.add_subplot( - autoscale_on=False, - xlim=(0, grid.grid_size[0] - 1), - ylim=(0, grid.grid_size[1] - 1), - ) - ax.set_aspect("equal") - ax.grid() - ax.set_xticks(np.arange(0, grid.grid_size[0], 1)) - ax.set_yticks(np.arange(0, grid.grid_size[1], 1)) - - (start_and_goal,) = ax.plot([], [], "mD", ms=15, label="Start and Goal") - start_and_goal.set_data([start.x, goal.x], [start.y, goal.y]) - (obs_points,) = ax.plot([], [], "ro", ms=15, label="Obstacles") - (path_points,) = ax.plot([], [], "bo", ms=10, label="Path Found") - ax.legend(bbox_to_anchor=(1.05, 1)) - - # 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] - if isinstance(event, KeyEvent) else None - ) - - for i in range(0, path.goal_reached_time()): - obs_positions = grid.get_obstacle_positions_at_time(i) - obs_points.set_data(obs_positions[0], obs_positions[1]) - path_position = path.get_position(i) - if not path_position: - raise Exception(f"Path position not found for time {i}.") - - path_points.set_data([path_position.x], [path_position.y]) - plt.pause(0.2) - plt.show() - -''' -Plot a series of agent paths. -''' -def PlotNodePaths(grid: Grid, start_and_goals: list[StartAndGoal], paths: list[NodePath]): - fig = plt.figure(figsize=(10, 7)) - - ax = fig.add_subplot( - autoscale_on=False, - xlim=(0, grid.grid_size[0] - 1), - ylim=(0, grid.grid_size[1] - 1), - ) - ax.set_aspect("equal") - ax.grid() - ax.set_xticks(np.arange(0, grid.grid_size[0], 1)) - ax.set_yticks(np.arange(0, grid.grid_size[1], 1)) - - # Plot start and goal positions for each agent - colors = [] # generated randomly in loop - markers = ['D', 's', '^', 'o', 'p'] # Different markers for visual distinction - - # Create plots for start and goal positions - start_and_goal_plots = [] - for i, path in enumerate(paths): - marker_idx = i % len(markers) - agent_id = start_and_goals[i].index - start = start_and_goals[i].start - goal = start_and_goals[i].goal - - color = np.random.rand(3,) - colors.append(color) - sg_plot, = ax.plot([], [], markers[marker_idx], c=color, ms=15, - label=f"Agent {agent_id} Start/Goal") - sg_plot.set_data([start.x, goal.x], [start.y, goal.y]) - start_and_goal_plots.append(sg_plot) - - # Plot for obstacles - (obs_points,) = ax.plot([], [], "ro", ms=15, label="Obstacles") - - # Create plots for each agent's path - path_plots = [] - for i, path in enumerate(paths): - agent_id = start_and_goals[i].index - path_plot, = ax.plot([], [], "o", c=colors[i], ms=10, - label=f"Agent {agent_id} Path") - path_plots.append(path_plot) - - ax.legend(bbox_to_anchor=(1.05, 1)) - - # 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] - if isinstance(event, KeyEvent) else None - ) - - # Find the maximum time across all paths - max_time = max(path.goal_reached_time() for path in paths) - - # Animation loop - for i in range(0, max_time + 1): - # Update obstacle positions - obs_positions = grid.get_obstacle_positions_at_time(i) - obs_points.set_data(obs_positions[0], obs_positions[1]) - - # Update each agent's position - for (j, path) in enumerate(paths): - path_positions = [] - if i <= path.goal_reached_time(): - res = path.get_position(i) - if not res: - print(path) - print(i) - path_position = path.get_position(i) - if not path_position: - raise Exception(f"Path position not found for time {i}.") - - # Verify position is valid - assert not path_position in obs_positions - assert not path_position in path_positions - path_positions.append(path_position) - - path_plots[j].set_data([path_position.x], [path_position.y]) - - plt.pause(0.2) - - plt.show() \ No newline at end of file diff --git a/PathPlanning/TimeBasedPathPlanning/PriorityBasedPlanner.py b/PathPlanning/TimeBasedPathPlanning/PriorityBasedPlanner.py deleted file mode 100644 index 2573965cf6..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/PriorityBasedPlanner.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Priority Based Planner for multi agent path planning. -The planner generates an order to plan in, and generates plans for the robots in that order. Each planned -path is reserved in the grid, and all future plans must avoid that path. - -Algorithm outlined in section III of this paper: https://pure.tudelft.nl/ws/portalfiles/portal/67074672/07138650.pdf -""" - -import numpy as np -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - Interval, - ObstacleArrangement, - Position, -) -from PathPlanning.TimeBasedPathPlanning.BaseClasses import MultiAgentPlanner, StartAndGoal -from PathPlanning.TimeBasedPathPlanning.Node import NodePath -from PathPlanning.TimeBasedPathPlanning.BaseClasses import SingleAgentPlanner -from PathPlanning.TimeBasedPathPlanning.SafeInterval import SafeIntervalPathPlanner -from PathPlanning.TimeBasedPathPlanning.Plotting import PlotNodePaths -import time - -class PriorityBasedPlanner(MultiAgentPlanner): - - @staticmethod - def plan(grid: Grid, start_and_goals: list[StartAndGoal], single_agent_planner_class: SingleAgentPlanner, verbose: bool = False) -> tuple[list[StartAndGoal], list[NodePath]]: - """ - Generate a path from the start to the goal for each agent in the `start_and_goals` list. - Returns the re-ordered StartAndGoal combinations, and a list of path plans. The order of the plans - corresponds to the order of the `start_and_goals` list. - """ - print(f"Using single-agent planner: {single_agent_planner_class}") - - # Reserve initial positions - for start_and_goal in start_and_goals: - grid.reserve_position(start_and_goal.start, start_and_goal.index, Interval(0, 10)) - - # Plan in descending order of distance from start to goal - start_and_goals = sorted(start_and_goals, - key=lambda item: item.distance_start_to_goal(), - reverse=True) - - paths = [] - for start_and_goal in start_and_goals: - if verbose: - print(f"\nPlanning for agent: {start_and_goal}" ) - - grid.clear_initial_reservation(start_and_goal.start, start_and_goal.index) - path = single_agent_planner_class.plan(grid, start_and_goal.start, start_and_goal.goal, verbose) - - if path is None: - print(f"Failed to find path for {start_and_goal}") - return [] - - agent_index = start_and_goal.index - grid.reserve_path(path, agent_index) - paths.append(path) - - return (start_and_goals, paths) - -verbose = False -show_animation = True -def main(): - grid_side_length = 21 - - start_and_goals = [StartAndGoal(i, Position(1, i), Position(19, 19-i)) for i in range(1, 16)] - obstacle_avoid_points = [pos for item in start_and_goals for pos in (item.start, item.goal)] - - grid = Grid( - np.array([grid_side_length, grid_side_length]), - num_obstacles=250, - obstacle_avoid_points=obstacle_avoid_points, - # obstacle_arrangement=ObstacleArrangement.NARROW_CORRIDOR, - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - # obstacle_arrangement=ObstacleArrangement.RANDOM, - ) - - start_time = time.time() - start_and_goals, paths = PriorityBasedPlanner.plan(grid, start_and_goals, SafeIntervalPathPlanner, verbose) - - runtime = time.time() - start_time - print(f"\nPlanning took: {runtime:.5f} seconds") - - if verbose: - print(f"Paths:") - for path in paths: - print(f"{path}\n") - - if not show_animation: - return - - PlotNodePaths(grid, start_and_goals, paths) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/PathPlanning/TimeBasedPathPlanning/SafeInterval.py b/PathPlanning/TimeBasedPathPlanning/SafeInterval.py deleted file mode 100644 index 446847ac6d..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/SafeInterval.py +++ /dev/null @@ -1,212 +0,0 @@ -""" -Safe interval path planner - This script implements a safe-interval path planner for a 2d grid with dynamic obstacles. It is faster than - SpaceTime A* because it reduces the number of redundant node expansions by pre-computing regions of adjacent - time steps that are safe ("safe intervals") at each position. This allows the algorithm to skip expanding nodes - that are in intervals that have already been visited earlier. - - Reference: https://www.cs.cmu.edu/~maxim/files/sipp_icra11.pdf -""" - -import numpy as np -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - Interval, - ObstacleArrangement, - Position, - empty_2d_array_of_lists, -) -from PathPlanning.TimeBasedPathPlanning.BaseClasses import SingleAgentPlanner -from PathPlanning.TimeBasedPathPlanning.Node import Node, NodePath -from PathPlanning.TimeBasedPathPlanning.Plotting import PlotNodePath - -import heapq -from dataclasses import dataclass -from functools import total_ordering -import time - -@dataclass() -# Note: Total_ordering is used instead of adding `order=True` to the @dataclass decorator because -# this class needs to override the __lt__ and __eq__ methods to ignore parent_index. The Parent -# index and interval member variables are just used to track the path found by the algorithm, -# and has no effect on the quality of a node. -@total_ordering -class SIPPNode(Node): - interval: Interval - -@dataclass -class EntryTimeAndInterval: - entry_time: int - interval: Interval - -class SafeIntervalPathPlanner(SingleAgentPlanner): - - """ - Generate a plan given the loaded problem statement. Raises an exception if it fails to find a path. - Arguments: - verbose (bool): set to True to print debug information - """ - @staticmethod - def plan(grid: Grid, start: Position, goal: Position, verbose: bool = False) -> NodePath: - - safe_intervals = grid.get_safe_intervals() - - open_set: list[SIPPNode] = [] - first_node_interval = safe_intervals[start.x, start.y][0] - heapq.heappush( - open_set, SIPPNode(start, 0, SafeIntervalPathPlanner.calculate_heuristic(start, goal), -1, first_node_interval) - ) - - expanded_list: list[SIPPNode] = [] - visited_intervals = empty_2d_array_of_lists(grid.grid_size[0], grid.grid_size[1]) - while open_set: - expanded_node: SIPPNode = heapq.heappop(open_set) - if verbose: - print("Expanded node:", expanded_node) - - if expanded_node.time + 1 >= grid.time_limit: - if verbose: - print(f"\tSkipping node that is past time limit: {expanded_node}") - continue - - if expanded_node.position == goal: - if verbose: - print(f"Found path to goal after {len(expanded_list)} expansions") - - path = [] - path_walker: SIPPNode = expanded_node - while True: - path.append(path_walker) - if path_walker.parent_index == -1: - break - path_walker = expanded_list[path_walker.parent_index] - - # reverse path so it goes start -> goal - path.reverse() - return NodePath(path, len(expanded_list)) - - expanded_idx = len(expanded_list) - expanded_list.append(expanded_node) - entry_time_and_node = EntryTimeAndInterval(expanded_node.time, expanded_node.interval) - add_entry_to_visited_intervals_array(entry_time_and_node, visited_intervals, expanded_node) - - for child in SafeIntervalPathPlanner.generate_successors(grid, goal, expanded_node, expanded_idx, safe_intervals, visited_intervals): - heapq.heappush(open_set, child) - - raise Exception("No path found") - - """ - Generate list of possible successors of the provided `parent_node` that are worth expanding - """ - @staticmethod - def generate_successors( - grid: Grid, goal: Position, parent_node: SIPPNode, parent_node_idx: int, intervals: np.ndarray, visited_intervals: np.ndarray - ) -> list[SIPPNode]: - new_nodes = [] - diffs = [ - Position(0, 0), - Position(1, 0), - Position(-1, 0), - Position(0, 1), - Position(0, -1), - ] - for diff in diffs: - new_pos = parent_node.position + diff - if not grid.inside_grid_bounds(new_pos): - continue - - current_interval = parent_node.interval - - new_cell_intervals: list[Interval] = intervals[new_pos.x, new_pos.y] - for interval in new_cell_intervals: - # if interval starts after current ends, break - # assumption: intervals are sorted by start time, so all future intervals will hit this condition as well - if interval.start_time > current_interval.end_time: - break - - # if interval ends before current starts, skip - if interval.end_time < current_interval.start_time: - continue - - # if we have already expanded a node in this interval with a <= starting time, skip - better_node_expanded = False - for visited in visited_intervals[new_pos.x, new_pos.y]: - if interval == visited.interval and visited.entry_time <= parent_node.time + 1: - better_node_expanded = True - break - if better_node_expanded: - continue - - # We know there is a node worth expanding. Generate successor at the earliest possible time the - # new interval can be entered - for possible_t in range(max(parent_node.time + 1, interval.start_time), min(current_interval.end_time, interval.end_time)): - if grid.valid_position(new_pos, possible_t): - new_nodes.append(SIPPNode( - new_pos, - # entry is max of interval start and parent node time + 1 (get there as soon as possible) - max(interval.start_time, parent_node.time + 1), - SafeIntervalPathPlanner.calculate_heuristic(new_pos, goal), - parent_node_idx, - interval, - )) - # break because all t's after this will make nodes with a higher cost, the same heuristic, and are in the same interval - break - - return new_nodes - - """ - Calculate the heuristic for a given position - Manhattan distance to the goal - """ - @staticmethod - def calculate_heuristic(position: Position, goal: Position) -> int: - diff = goal - position - return abs(diff.x) + abs(diff.y) - - -""" -Adds a new entry to the visited intervals array. If the entry is already present, the entry time is updated if the new -entry time is better. Otherwise, the entry is added to `visited_intervals` at the position of `expanded_node`. -""" -def add_entry_to_visited_intervals_array(entry_time_and_interval: EntryTimeAndInterval, visited_intervals: np.ndarray, expanded_node: SIPPNode): - # if entry is present, update entry time if better - for existing_entry_and_interval in visited_intervals[expanded_node.position.x, expanded_node.position.y]: - if existing_entry_and_interval.interval == entry_time_and_interval.interval: - existing_entry_and_interval.entry_time = min(existing_entry_and_interval.entry_time, entry_time_and_interval.entry_time) - - # Otherwise, append - visited_intervals[expanded_node.position.x, expanded_node.position.y].append(entry_time_and_interval) - - -show_animation = True -verbose = False - -def main(): - start = Position(1, 18) - goal = Position(19, 19) - grid_side_length = 21 - - start_time = time.time() - - grid = Grid( - np.array([grid_side_length, grid_side_length]), - num_obstacles=250, - obstacle_avoid_points=[start, goal], - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - # obstacle_arrangement=ObstacleArrangement.RANDOM, - ) - - path = SafeIntervalPathPlanner.plan(grid, start, goal, verbose) - runtime = time.time() - start_time - print(f"Planning took: {runtime:.5f} seconds") - - if verbose: - print(f"Path: {path}") - - if not show_animation: - return - - PlotNodePath(grid, start, goal, path) - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/TimeBasedPathPlanning/SpaceTimeAStar.py b/PathPlanning/TimeBasedPathPlanning/SpaceTimeAStar.py deleted file mode 100644 index b85569f5dc..0000000000 --- a/PathPlanning/TimeBasedPathPlanning/SpaceTimeAStar.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -Space-time A* Algorithm - This script demonstrates the Space-time A* algorithm for path planning in a grid world with moving obstacles. - This algorithm is different from normal 2D A* in one key way - the cost (often notated as g(n)) is - the number of time steps it took to get to a given node, instead of the number of cells it has - traversed. This ensures the path is time-optimal, while respecting any dynamic obstacles in the environment. - - Reference: https://www.davidsilver.uk/wp-content/uploads/2020/03/coop-path-AIWisdom.pdf -""" - -import numpy as np -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - ObstacleArrangement, - Position, -) -from PathPlanning.TimeBasedPathPlanning.Node import Node, NodePath -import heapq -from collections.abc import Generator -import time -from PathPlanning.TimeBasedPathPlanning.BaseClasses import SingleAgentPlanner -from PathPlanning.TimeBasedPathPlanning.Plotting import PlotNodePath - -class SpaceTimeAStar(SingleAgentPlanner): - - @staticmethod - def plan(grid: Grid, start: Position, goal: Position, verbose: bool = False) -> NodePath: - open_set: list[Node] = [] - heapq.heappush( - open_set, Node(start, 0, SpaceTimeAStar.calculate_heuristic(start, goal), -1) - ) - - expanded_list: list[Node] = [] - expanded_set: set[Node] = set() - while open_set: - expanded_node: Node = heapq.heappop(open_set) - if verbose: - print("Expanded node:", expanded_node) - - if expanded_node.time + 1 >= grid.time_limit: - if verbose: - print(f"\tSkipping node that is past time limit: {expanded_node}") - continue - - if expanded_node.position == goal: - if verbose: - print(f"Found path to goal after {len(expanded_list)} expansions") - - path = [] - path_walker: Node = expanded_node - while True: - path.append(path_walker) - if path_walker.parent_index == -1: - break - path_walker = expanded_list[path_walker.parent_index] - - # reverse path so it goes start -> goal - path.reverse() - return NodePath(path, len(expanded_set)) - - expanded_idx = len(expanded_list) - expanded_list.append(expanded_node) - expanded_set.add(expanded_node) - - for child in SpaceTimeAStar.generate_successors(grid, goal, expanded_node, expanded_idx, verbose, expanded_set): - heapq.heappush(open_set, child) - - raise Exception("No path found") - - """ - Generate possible successors of the provided `parent_node` - """ - @staticmethod - def generate_successors( - grid: Grid, goal: Position, parent_node: Node, parent_node_idx: int, verbose: bool, expanded_set: set[Node] - ) -> Generator[Node, None, None]: - diffs = [ - Position(0, 0), - Position(1, 0), - Position(-1, 0), - Position(0, 1), - Position(0, -1), - ] - for diff in diffs: - new_pos = parent_node.position + diff - new_node = Node( - new_pos, - parent_node.time + 1, - SpaceTimeAStar.calculate_heuristic(new_pos, goal), - parent_node_idx, - ) - - if new_node in expanded_set: - continue - - # Check if the new node is valid for the next 2 time steps - one step to enter, and another to leave - if all([grid.valid_position(new_pos, parent_node.time + dt) for dt in [1, 2]]): - if verbose: - print("\tNew successor node: ", new_node) - yield new_node - - @staticmethod - def calculate_heuristic(position: Position, goal: Position) -> int: - diff = goal - position - return abs(diff.x) + abs(diff.y) - - -show_animation = True -verbose = False - -def main(): - start = Position(1, 5) - goal = Position(19, 19) - grid_side_length = 21 - - start_time = time.time() - - grid = Grid( - np.array([grid_side_length, grid_side_length]), - num_obstacles=40, - obstacle_avoid_points=[start, goal], - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - ) - - path = SpaceTimeAStar.plan(grid, start, goal, verbose) - - runtime = time.time() - start_time - print(f"Planning took: {runtime:.5f} seconds") - - if verbose: - print(f"Path: {path}") - - if not show_animation: - return - - PlotNodePath(grid, start, goal, path) - -if __name__ == "__main__": - main() diff --git a/PathPlanning/TimeBasedPathPlanning/__init__.py b/PathPlanning/TimeBasedPathPlanning/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/VisibilityRoadMap/__init__.py b/PathPlanning/VisibilityRoadMap/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/VisibilityRoadMap/geometry.py b/PathPlanning/VisibilityRoadMap/geometry.py deleted file mode 100644 index b15cdb8a43..0000000000 --- a/PathPlanning/VisibilityRoadMap/geometry.py +++ /dev/null @@ -1,44 +0,0 @@ -class Geometry: - - class Point: - def __init__(self, x, y): - self.x = x - self.y = y - - @staticmethod - def is_seg_intersect(p1, q1, p2, q2): - - def on_segment(p, q, r): - if ((q.x <= max(p.x, r.x)) and (q.x >= min(p.x, r.x)) and - (q.y <= max(p.y, r.y)) and (q.y >= min(p.y, r.y))): - return True - return False - - def orientation(p, q, r): - val = (float(q.y - p.y) * (r.x - q.x)) - ( - float(q.x - p.x) * (r.y - q.y)) - if val > 0: - return 1 - if val < 0: - return 2 - return 0 - - # Find the 4 orientations required for - # the general and special cases - o1 = orientation(p1, q1, p2) - o2 = orientation(p1, q1, q2) - o3 = orientation(p2, q2, p1) - o4 = orientation(p2, q2, q1) - - if (o1 != o2) and (o3 != o4): - return True - if (o1 == 0) and on_segment(p1, p2, q1): - return True - if (o2 == 0) and on_segment(p1, q2, q1): - return True - if (o3 == 0) and on_segment(p2, p1, q2): - return True - if (o4 == 0) and on_segment(p2, q1, q2): - return True - - return False diff --git a/PathPlanning/VisibilityRoadMap/visibility_road_map.py b/PathPlanning/VisibilityRoadMap/visibility_road_map.py deleted file mode 100644 index 5f7ffadd16..0000000000 --- a/PathPlanning/VisibilityRoadMap/visibility_road_map.py +++ /dev/null @@ -1,222 +0,0 @@ -""" - -Visibility Road Map Planner - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import sys -import math -import numpy as np -import matplotlib.pyplot as plt -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from VisibilityRoadMap.geometry import Geometry -from VoronoiRoadMap.dijkstra_search import DijkstraSearch - -show_animation = True - - -class VisibilityRoadMap: - - def __init__(self, expand_distance, do_plot=False): - self.expand_distance = expand_distance - self.do_plot = do_plot - - def planning(self, start_x, start_y, goal_x, goal_y, obstacles): - - nodes = self.generate_visibility_nodes(start_x, start_y, - goal_x, goal_y, obstacles) - - road_map_info = self.generate_road_map_info(nodes, obstacles) - - if self.do_plot: - self.plot_road_map(nodes, road_map_info) - plt.pause(1.0) - - rx, ry = DijkstraSearch(show_animation).search( - start_x, start_y, - goal_x, goal_y, - [node.x for node in nodes], - [node.y for node in nodes], - road_map_info - ) - - return rx, ry - - def generate_visibility_nodes(self, start_x, start_y, goal_x, goal_y, - obstacles): - - # add start and goal as nodes - nodes = [DijkstraSearch.Node(start_x, start_y), - DijkstraSearch.Node(goal_x, goal_y, 0, None)] - - # add vertexes in configuration space as nodes - for obstacle in obstacles: - - cvx_list, cvy_list = self.calc_vertexes_in_configuration_space( - obstacle.x_list, obstacle.y_list) - - for (vx, vy) in zip(cvx_list, cvy_list): - nodes.append(DijkstraSearch.Node(vx, vy)) - - if self.do_plot: - for node in nodes: - plt.plot(node.x, node.y, "xr") - - return nodes - - def calc_vertexes_in_configuration_space(self, x_list, y_list): - x_list = x_list[0:-1] - y_list = y_list[0:-1] - cvx_list, cvy_list = [], [] - - n_data = len(x_list) - - for index in range(n_data): - offset_x, offset_y = self.calc_offset_xy( - x_list[index - 1], y_list[index - 1], - x_list[index], y_list[index], - x_list[(index + 1) % n_data], y_list[(index + 1) % n_data], - ) - cvx_list.append(offset_x) - cvy_list.append(offset_y) - - return cvx_list, cvy_list - - def generate_road_map_info(self, nodes, obstacles): - - road_map_info_list = [] - - for target_node in nodes: - road_map_info = [] - for node_id, node in enumerate(nodes): - if np.hypot(target_node.x - node.x, - target_node.y - node.y) <= 0.1: - continue - - is_valid = True - for obstacle in obstacles: - if not self.is_edge_valid(target_node, node, obstacle): - is_valid = False - break - if is_valid: - road_map_info.append(node_id) - - road_map_info_list.append(road_map_info) - - return road_map_info_list - - @staticmethod - def is_edge_valid(target_node, node, obstacle): - - for i in range(len(obstacle.x_list) - 1): - p1 = Geometry.Point(target_node.x, target_node.y) - p2 = Geometry.Point(node.x, node.y) - p3 = Geometry.Point(obstacle.x_list[i], obstacle.y_list[i]) - p4 = Geometry.Point(obstacle.x_list[i + 1], obstacle.y_list[i + 1]) - - if Geometry.is_seg_intersect(p1, p2, p3, p4): - return False - - return True - - def calc_offset_xy(self, px, py, x, y, nx, ny): - p_vec = math.atan2(y - py, x - px) - n_vec = math.atan2(ny - y, nx - x) - offset_vec = math.atan2(math.sin(p_vec) + math.sin(n_vec), - math.cos(p_vec) + math.cos( - n_vec)) + math.pi / 2.0 - offset_x = x + self.expand_distance * math.cos(offset_vec) - offset_y = y + self.expand_distance * math.sin(offset_vec) - return offset_x, offset_y - - @staticmethod - def plot_road_map(nodes, road_map_info_list): - for i, node in enumerate(nodes): - for index in road_map_info_list[i]: - plt.plot([node.x, nodes[index].x], - [node.y, nodes[index].y], "-b") - - -class ObstaclePolygon: - - def __init__(self, x_list, y_list): - self.x_list = x_list - self.y_list = y_list - - self.close_polygon() - self.make_clockwise() - - def make_clockwise(self): - if not self.is_clockwise(): - self.x_list = list(reversed(self.x_list)) - self.y_list = list(reversed(self.y_list)) - - def is_clockwise(self): - n_data = len(self.x_list) - eval_sum = sum([(self.x_list[i + 1] - self.x_list[i]) * - (self.y_list[i + 1] + self.y_list[i]) - for i in range(n_data - 1)]) - eval_sum += (self.x_list[0] - self.x_list[n_data - 1]) * \ - (self.y_list[0] + self.y_list[n_data - 1]) - return eval_sum >= 0 - - def close_polygon(self): - is_x_same = self.x_list[0] == self.x_list[-1] - is_y_same = self.y_list[0] == self.y_list[-1] - if is_x_same and is_y_same: - return # no need to close - - self.x_list.append(self.x_list[0]) - self.y_list.append(self.y_list[0]) - - def plot(self): - plt.plot(self.x_list, self.y_list, "-k") - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx, sy = 10.0, 10.0 # [m] - gx, gy = 50.0, 50.0 # [m] - - expand_distance = 5.0 # [m] - - obstacles = [ - ObstaclePolygon( - [20.0, 30.0, 15.0], - [20.0, 20.0, 30.0], - ), - ObstaclePolygon( - [40.0, 45.0, 50.0, 40.0], - [50.0, 40.0, 20.0, 40.0], - ), - ObstaclePolygon( - [20.0, 30.0, 30.0, 20.0], - [40.0, 45.0, 60.0, 50.0], - ) - ] - - if show_animation: # pragma: no cover - plt.plot(sx, sy, "or") - plt.plot(gx, gy, "ob") - for ob in obstacles: - ob.plot() - plt.axis("equal") - plt.pause(1.0) - - rx, ry = VisibilityRoadMap(expand_distance, do_plot=show_animation)\ - .planning(sx, sy, gx, gy, obstacles) - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.1) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/VoronoiRoadMap/__init__.py b/PathPlanning/VoronoiRoadMap/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathPlanning/VoronoiRoadMap/dijkstra_search.py b/PathPlanning/VoronoiRoadMap/dijkstra_search.py deleted file mode 100644 index 503ccc342e..0000000000 --- a/PathPlanning/VoronoiRoadMap/dijkstra_search.py +++ /dev/null @@ -1,140 +0,0 @@ -""" - -Dijkstra Search library - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import matplotlib.pyplot as plt -import math -import numpy as np - - -class DijkstraSearch: - class Node: - """ - Node class for dijkstra search - """ - - def __init__(self, x, y, cost=None, parent=None, edge_ids=None): - self.x = x - self.y = y - self.cost = cost - self.parent = parent - self.edge_ids = edge_ids - - def __str__(self): - return str(self.x) + "," + str(self.y) + "," + str( - self.cost) + "," + str(self.parent) - - def __init__(self, show_animation): - self.show_animation = show_animation - - def search(self, sx, sy, gx, gy, node_x, node_y, edge_ids_list): - """ - Search shortest path - - s_x: start x positions [m] - s_y: start y positions [m] - gx: goal x position [m] - gx: goal x position [m] - node_x: node x position - node_y: node y position - edge_ids_list: edge_list each item includes a list of edge ids - """ - - start_node = self.Node(sx, sy, 0.0, -1) - goal_node = self.Node(gx, gy, 0.0, -1) - current_node = None - - open_set, close_set = dict(), dict() - open_set[self.find_id(node_x, node_y, start_node)] = start_node - - while True: - if self.has_node_in_set(close_set, goal_node): - print("goal is found!") - goal_node.parent = current_node.parent - goal_node.cost = current_node.cost - break - elif not open_set: - print("Cannot find path") - break - - current_id = min(open_set, key=lambda o: open_set[o].cost) - current_node = open_set[current_id] - - # show graph - if self.show_animation and len( - close_set.keys()) % 2 == 0: # pragma: no cover - plt.plot(current_node.x, current_node.y, "xg") - # 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]) - plt.pause(0.1) - - # Remove the item from the open set - del open_set[current_id] - # Add it to the closed set - close_set[current_id] = current_node - - # expand search grid based on motion model - for i in range(len(edge_ids_list[current_id])): - n_id = edge_ids_list[current_id][i] - dx = node_x[n_id] - current_node.x - dy = node_y[n_id] - current_node.y - d = math.hypot(dx, dy) - node = self.Node(node_x[n_id], node_y[n_id], - current_node.cost + d, current_id) - - if n_id in close_set: - continue - # Otherwise if it is already in the open set - if n_id in open_set: - if open_set[n_id].cost > node.cost: - open_set[n_id] = node - else: - open_set[n_id] = node - - # generate final course - rx, ry = self.generate_final_path(close_set, goal_node) - - return rx, ry - - @staticmethod - def generate_final_path(close_set, goal_node): - rx, ry = [goal_node.x], [goal_node.y] - parent = goal_node.parent - while parent != -1: - n = close_set[parent] - rx.append(n.x) - ry.append(n.y) - parent = n.parent - rx, ry = rx[::-1], ry[::-1] # reverse it - return rx, ry - - def has_node_in_set(self, target_set, node): - for key in target_set: - if self.is_same_node(target_set[key], node): - return True - return False - - def find_id(self, node_x_list, node_y_list, target_node): - for i, _ in enumerate(node_x_list): - if self.is_same_node_with_xy(node_x_list[i], node_y_list[i], - target_node): - return i - return None - - @staticmethod - def is_same_node_with_xy(node_x, node_y, node_b): - dist = np.hypot(node_x - node_b.x, - node_y - node_b.y) - return dist <= 0.1 - - @staticmethod - def is_same_node(node_a, node_b): - dist = np.hypot(node_a.x - node_b.x, - node_a.y - node_b.y) - return dist <= 0.1 diff --git a/PathPlanning/VoronoiRoadMap/voronoi_road_map.py b/PathPlanning/VoronoiRoadMap/voronoi_road_map.py deleted file mode 100644 index a27e1b6928..0000000000 --- a/PathPlanning/VoronoiRoadMap/voronoi_road_map.py +++ /dev/null @@ -1,186 +0,0 @@ -""" - -Voronoi Road Map Planner - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math -import numpy as np -import matplotlib.pyplot as plt -from scipy.spatial import cKDTree, Voronoi -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent)) - -from VoronoiRoadMap.dijkstra_search import DijkstraSearch - -show_animation = True - - -class VoronoiRoadMapPlanner: - - def __init__(self): - # parameter - self.N_KNN = 10 # number of edge from one sampled point - self.MAX_EDGE_LEN = 30.0 # [m] Maximum edge length - - def planning(self, sx, sy, gx, gy, ox, oy, robot_radius): - obstacle_tree = cKDTree(np.vstack((ox, oy)).T) - - sample_x, sample_y = self.voronoi_sampling(sx, sy, gx, gy, ox, oy) - if show_animation: # pragma: no cover - plt.plot(sample_x, sample_y, ".b") - - road_map_info = self.generate_road_map_info( - sample_x, sample_y, robot_radius, obstacle_tree) - - rx, ry = DijkstraSearch(show_animation).search(sx, sy, gx, gy, - sample_x, sample_y, - road_map_info) - return rx, ry - - def is_collision(self, sx, sy, gx, gy, rr, obstacle_kd_tree): - x = sx - y = sy - dx = gx - sx - dy = gy - sy - yaw = math.atan2(gy - sy, gx - sx) - d = math.hypot(dx, dy) - - if d >= self.MAX_EDGE_LEN: - return True - - D = rr - n_step = round(d / D) - - for i in range(n_step): - dist, _ = obstacle_kd_tree.query([x, y]) - if dist <= rr: - return True # collision - x += D * math.cos(yaw) - y += D * math.sin(yaw) - - # goal point check - dist, _ = obstacle_kd_tree.query([gx, gy]) - if dist <= rr: - return True # collision - - return False # OK - - def generate_road_map_info(self, node_x, node_y, rr, obstacle_tree): - """ - Road map generation - - node_x: [m] x positions of sampled points - node_y: [m] y positions of sampled points - rr: Robot Radius[m] - obstacle_tree: KDTree object of obstacles - """ - - road_map = [] - n_sample = len(node_x) - node_tree = cKDTree(np.vstack((node_x, node_y)).T) - - for (i, ix, iy) in zip(range(n_sample), node_x, node_y): - - dists, indexes = node_tree.query([ix, iy], k=n_sample) - - edge_id = [] - - for ii in range(1, len(indexes)): - nx = node_x[indexes[ii]] - ny = node_y[indexes[ii]] - - if not self.is_collision(ix, iy, nx, ny, rr, obstacle_tree): - edge_id.append(indexes[ii]) - - if len(edge_id) >= self.N_KNN: - break - - road_map.append(edge_id) - - # plot_road_map(road_map, sample_x, sample_y) - - return road_map - - @staticmethod - def plot_road_map(road_map, sample_x, sample_y): # pragma: no cover - - for i, _ in enumerate(road_map): - for ii in range(len(road_map[i])): - ind = road_map[i][ii] - - plt.plot([sample_x[i], sample_x[ind]], - [sample_y[i], sample_y[ind]], "-k") - - @staticmethod - def voronoi_sampling(sx, sy, gx, gy, ox, oy): - oxy = np.vstack((ox, oy)).T - - # generate voronoi point - vor = Voronoi(oxy) - sample_x = [ix for [ix, _] in vor.vertices] - sample_y = [iy for [_, iy] in vor.vertices] - - sample_x.append(sx) - sample_y.append(sy) - sample_x.append(gx) - sample_y.append(gy) - - return sample_x, sample_y - - -def main(): - print(__file__ + " start!!") - - # start and goal position - sx = 10.0 # [m] - sy = 10.0 # [m] - gx = 50.0 # [m] - gy = 50.0 # [m] - robot_size = 5.0 # [m] - - ox = [] - oy = [] - - for i in range(60): - ox.append(float(i)) - oy.append(0.0) - for i in range(60): - ox.append(60.0) - oy.append(float(i)) - for i in range(61): - ox.append(float(i)) - oy.append(60.0) - for i in range(61): - ox.append(0.0) - oy.append(float(i)) - for i in range(40): - ox.append(20.0) - oy.append(float(i)) - for i in range(40): - ox.append(40.0) - oy.append(60.0 - i) - - if show_animation: # pragma: no cover - plt.plot(ox, oy, ".k") - plt.plot(sx, sy, "^r") - plt.plot(gx, gy, "^c") - plt.grid(True) - plt.axis("equal") - - rx, ry = VoronoiRoadMapPlanner().planning(sx, sy, gx, gy, ox, oy, - robot_size) - - assert rx, 'Cannot found path' - - if show_animation: # pragma: no cover - plt.plot(rx, ry, "-r") - plt.pause(0.1) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathPlanning/WavefrontCPP/map/test.png b/PathPlanning/WavefrontCPP/map/test.png deleted file mode 100644 index 4abca0bf30..0000000000 Binary files a/PathPlanning/WavefrontCPP/map/test.png and /dev/null differ diff --git a/PathPlanning/WavefrontCPP/map/test_2.png b/PathPlanning/WavefrontCPP/map/test_2.png deleted file mode 100644 index 0d27fa9f95..0000000000 Binary files a/PathPlanning/WavefrontCPP/map/test_2.png and /dev/null differ diff --git a/PathPlanning/WavefrontCPP/map/test_3.png b/PathPlanning/WavefrontCPP/map/test_3.png deleted file mode 100644 index 1a50b87ccf..0000000000 Binary files a/PathPlanning/WavefrontCPP/map/test_3.png and /dev/null differ diff --git a/PathPlanning/WavefrontCPP/wavefront_coverage_path_planner.py b/PathPlanning/WavefrontCPP/wavefront_coverage_path_planner.py deleted file mode 100644 index c5a139454b..0000000000 --- a/PathPlanning/WavefrontCPP/wavefront_coverage_path_planner.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Distance/Path Transform Wavefront Coverage Path Planner - -author: Todd Tang -paper: Planning paths of complete coverage of an unstructured environment - by a mobile robot - Zelinsky et.al. -link: https://pinkwink.kr/attachment/cfile3.uf@1354654A4E8945BD13FE77.pdf -""" - -import os -import sys - -import matplotlib.pyplot as plt -import numpy as np -from scipy import ndimage - -do_animation = True - - -def transform( - grid_map, src, distance_type='chessboard', - transform_type='path', alpha=0.01 -): - """transform - - calculating transform of transform_type from src - in given distance_type - - :param grid_map: 2d binary map - :param src: distance transform source - :param distance_type: type of distance used - :param transform_type: type of transform used - :param alpha: weight of Obstacle Transform used when using path_transform - """ - - n_rows, n_cols = grid_map.shape - - if n_rows == 0 or n_cols == 0: - sys.exit('Empty grid_map.') - - inc_order = [[0, 1], [1, 1], [1, 0], [1, -1], - [0, -1], [-1, -1], [-1, 0], [-1, 1]] - if distance_type == 'chessboard': - cost = [1, 1, 1, 1, 1, 1, 1, 1] - elif distance_type == 'eculidean': - cost = [1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2)] - else: - sys.exit('Unsupported distance type.') - - transform_matrix = float('inf') * np.ones_like(grid_map, dtype=float) - transform_matrix[src[0], src[1]] = 0 - if transform_type == 'distance': - eT = np.zeros_like(grid_map) - elif transform_type == 'path': - eT = ndimage.distance_transform_cdt(1 - grid_map, distance_type) - else: - sys.exit('Unsupported transform type.') - - # set obstacle transform_matrix value to infinity - for i in range(n_rows): - for j in range(n_cols): - if grid_map[i][j] == 1.0: - transform_matrix[i][j] = float('inf') - is_visited = np.zeros_like(transform_matrix, dtype=bool) - is_visited[src[0], src[1]] = True - traversal_queue = [src] - calculated = set([(src[0] - 1) * n_cols + src[1]]) - - def is_valid_neighbor(g_i, g_j): - return 0 <= g_i < n_rows and 0 <= g_j < n_cols \ - and not grid_map[g_i][g_j] - - while traversal_queue: - i, j = traversal_queue.pop(0) - for k, inc in enumerate(inc_order): - ni = i + inc[0] - nj = j + inc[1] - if is_valid_neighbor(ni, nj): - is_visited[i][j] = True - - # update transform_matrix - transform_matrix[i][j] = min( - transform_matrix[i][j], - transform_matrix[ni][nj] + cost[k] + alpha * eT[ni][nj]) - - if not is_visited[ni][nj] \ - and ((ni - 1) * n_cols + nj) not in calculated: - traversal_queue.append((ni, nj)) - calculated.add((ni - 1) * n_cols + nj) - - return transform_matrix - - -def get_search_order_increment(start, goal): - if start[0] >= goal[0] and start[1] >= goal[1]: - order = [[1, 0], [0, 1], [-1, 0], [0, -1], - [1, 1], [1, -1], [-1, 1], [-1, -1]] - elif start[0] <= goal[0] and start[1] >= goal[1]: - order = [[-1, 0], [0, 1], [1, 0], [0, -1], - [-1, 1], [-1, -1], [1, 1], [1, -1]] - elif start[0] >= goal[0] and start[1] <= goal[1]: - order = [[1, 0], [0, -1], [-1, 0], [0, 1], - [1, -1], [-1, -1], [1, 1], [-1, 1]] - elif start[0] <= goal[0] and start[1] <= goal[1]: - order = [[-1, 0], [0, -1], [0, 1], [1, 0], - [-1, -1], [-1, 1], [1, -1], [1, 1]] - else: - sys.exit('get_search_order_increment: cannot determine \ - start=>goal increment order') - return order - - -def wavefront(transform_matrix, start, goal): - """wavefront - - performing wavefront coverage path planning - - :param transform_matrix: the transform matrix - :param start: start point of planning - :param goal: goal point of planning - """ - - path = [] - n_rows, n_cols = transform_matrix.shape - - def is_valid_neighbor(g_i, g_j): - is_i_valid_bounded = 0 <= g_i < n_rows - is_j_valid_bounded = 0 <= g_j < n_cols - if is_i_valid_bounded and is_j_valid_bounded: - return not is_visited[g_i][g_j] and \ - transform_matrix[g_i][g_j] != float('inf') - return False - - inc_order = get_search_order_increment(start, goal) - - current_node = start - is_visited = np.zeros_like(transform_matrix, dtype=bool) - - while current_node != goal: - i, j = current_node - path.append((i, j)) - is_visited[i][j] = True - - max_T = float('-inf') - i_max = (-1, -1) - i_last = 0 - for i_last in range(len(path)): - current_node = path[-1 - i_last] # get latest node in path - for ci, cj in inc_order: - ni, nj = current_node[0] + ci, current_node[1] + cj - if is_valid_neighbor(ni, nj) and \ - transform_matrix[ni][nj] > max_T: - i_max = (ni, nj) - max_T = transform_matrix[ni][nj] - - if i_max != (-1, -1): - break - - if i_max == (-1, -1): - break - else: - current_node = i_max - if i_last != 0: - print('backtracing to', current_node) - path.append(goal) - - return path - - -def visualize_path(grid_map, start, goal, path): # pragma: no cover - oy, ox = start - gy, gx = goal - px, py = np.transpose(np.flipud(np.fliplr(path))) - - if not do_animation: - plt.imshow(grid_map, cmap='Greys') - plt.plot(ox, oy, "-xy") - plt.plot(px, py, "-r") - plt.plot(gx, gy, "-pg") - plt.show() - else: - for ipx, ipy in zip(px, py): - 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]) - plt.imshow(grid_map, cmap='Greys') - plt.plot(ox, oy, "-xb") - plt.plot(px, py, "-r") - plt.plot(gx, gy, "-pg") - plt.plot(ipx, ipy, "or") - plt.axis("equal") - plt.grid(True) - plt.pause(0.1) - - -def main(): - dir_path = os.path.dirname(os.path.realpath(__file__)) - img = plt.imread(os.path.join(dir_path, 'map', 'test.png')) - img = 1 - img # revert pixel values - - start = (43, 0) - goal = (0, 0) - - # distance transform wavefront - DT = transform(img, goal, transform_type='distance') - DT_path = wavefront(DT, start, goal) - visualize_path(img, start, goal, DT_path) - - # path transform wavefront - PT = transform(img, goal, transform_type='path', alpha=0.01) - PT_path = wavefront(PT, start, goal) - visualize_path(img, start, goal, PT_path) - - -if __name__ == "__main__": - main() diff --git a/PathPlanning/__init__.py b/PathPlanning/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathTracking/__init__.py b/PathTracking/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathTracking/cgmres_nmpc/cgmres_nmpc.py b/PathTracking/cgmres_nmpc/cgmres_nmpc.py deleted file mode 100644 index ee40e73504..0000000000 --- a/PathTracking/cgmres_nmpc/cgmres_nmpc.py +++ /dev/null @@ -1,616 +0,0 @@ -""" - -Nonlinear MPC simulation with CGMRES - -author Atsushi Sakai (@Atsushi_twi) - -Reference: -Shunichi09/nonlinear_control: Implementing the nonlinear model predictive -control, sliding mode control https://github.com/Shunichi09/PythonLinearNonlinearControl - -""" - -from math import cos, sin, radians, atan2 - -import matplotlib.pyplot as plt -import numpy as np - -U_A_MAX = 1.0 -U_OMEGA_MAX = radians(45.0) -PHI_V = 0.01 -PHI_OMEGA = 0.01 -WB = 0.25 # [m] wheel base - -show_animation = True - - -def differential_model(v, yaw, u_1, u_2): - dx = cos(yaw) * v - dy = sin(yaw) * v - dv = u_1 - # tangent is not good for nonlinear optimization - d_yaw = v / WB * sin(u_2) - - return dx, dy, d_yaw, dv - - -class TwoWheeledSystem: - - def __init__(self, init_x, init_y, init_yaw, init_v): - self.x = init_x - self.y = init_y - self.yaw = init_yaw - self.v = init_v - self.history_x = [init_x] - self.history_y = [init_y] - self.history_yaw = [init_yaw] - self.history_v = [init_v] - - def update_state(self, u_1, u_2, dt=0.01): - dx, dy, d_yaw, dv = differential_model(self.v, self.yaw, u_1, u_2) - - self.x += dt * dx - self.y += dt * dy - self.yaw += dt * d_yaw - self.v += dt * dv - - # save - self.history_x.append(self.x) - self.history_y.append(self.y) - self.history_yaw.append(self.yaw) - self.history_v.append(self.v) - - -class NMPCSimulatorSystem: - - def calc_predict_and_adjoint_state(self, x, y, yaw, v, u_1s, u_2s, N, dt): - # by using state equation - x_s, y_s, yaw_s, v_s = self._calc_predict_states( - x, y, yaw, v, u_1s, u_2s, N, dt) - # by using adjoint equation - lam_1s, lam_2s, lam_3s, lam_4s = self._calc_adjoint_states( - x_s, y_s, yaw_s, v_s, u_2s, N, dt) - - return x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s - - def _calc_predict_states(self, x, y, yaw, v, u_1s, u_2s, N, dt): - x_s = [x] - y_s = [y] - yaw_s = [yaw] - v_s = [v] - - for i in range(N): - temp_x_1, temp_x_2, temp_x_3, temp_x_4 = self._predict_state_with_oylar( - x_s[i], y_s[i], yaw_s[i], v_s[i], u_1s[i], u_2s[i], dt) - x_s.append(temp_x_1) - y_s.append(temp_x_2) - yaw_s.append(temp_x_3) - v_s.append(temp_x_4) - - return x_s, y_s, yaw_s, v_s - - def _calc_adjoint_states(self, x_s, y_s, yaw_s, v_s, u_2s, N, dt): - lam_1s = [x_s[-1]] - lam_2s = [y_s[-1]] - lam_3s = [yaw_s[-1]] - lam_4s = [v_s[-1]] - - # backward adjoint state calc - for i in range(N - 1, 0, -1): - temp_lam_1, temp_lam_2, temp_lam_3, temp_lam_4 = self._adjoint_state_with_oylar( - yaw_s[i], v_s[i], lam_1s[0], lam_2s[0], lam_3s[0], lam_4s[0], - u_2s[i], dt) - lam_1s.insert(0, temp_lam_1) - lam_2s.insert(0, temp_lam_2) - lam_3s.insert(0, temp_lam_3) - lam_4s.insert(0, temp_lam_4) - - return lam_1s, lam_2s, lam_3s, lam_4s - - @staticmethod - def _predict_state_with_oylar(x, y, yaw, v, u_1, u_2, dt): - - dx, dy, dyaw, dv = differential_model( - v, yaw, u_1, u_2) - - next_x_1 = x + dt * dx - next_x_2 = y + dt * dy - next_x_3 = yaw + dt * dyaw - next_x_4 = v + dt * dv - - return next_x_1, next_x_2, next_x_3, next_x_4 - - @staticmethod - def _adjoint_state_with_oylar(yaw, v, lam_1, lam_2, lam_3, lam_4, u_2, dt): - - # ∂H/∂x - pre_lam_1 = lam_1 + dt * 0.0 - pre_lam_2 = lam_2 + dt * 0.0 - tmp1 = - lam_1 * sin(yaw) * v + lam_2 * cos(yaw) * v - pre_lam_3 = lam_3 + dt * tmp1 - tmp2 = lam_1 * cos(yaw) + lam_2 * sin(yaw) + lam_3 * sin(u_2) / WB - pre_lam_4 = lam_4 + dt * tmp2 - - return pre_lam_1, pre_lam_2, pre_lam_3, pre_lam_4 - - -class NMPCControllerCGMRES: - """ - Attributes - ------------ - zeta : float - gain of optimal answer stability - ht : float - update value of NMPC this should be decided by zeta - tf : float - predict time - alpha : float - gain of predict time - N : int - predict step, discrete value - threshold : float - cgmres's threshold value - input_num : int - system input length, this should include dummy u and constraint variables - max_iteration : int - decide by the solved matrix size - simulator : NMPCSimulatorSystem class - u_1s : list of float - estimated optimal system input - u_2s : list of float - estimated optimal system input - dummy_u_1s : list of float - estimated dummy input - dummy_u_2s : list of float - estimated dummy input - raw_1s : list of float - estimated constraint variable - raw_2s : list of float - estimated constraint variable - history_u_1 : list of float - time history of actual system input - history_u_2 : list of float - time history of actual system input - history_dummy_u_1 : list of float - time history of actual dummy u_1 - history_dummy_u_2 : list of float - time history of actual dummy u_2 - history_raw_1 : list of float - time history of actual raw_1 - history_raw_2 : list of float - time history of actual raw_2 - history_f : list of float - time history of error of optimal - """ - - def __init__(self): - # parameters - self.zeta = 100. # stability gain - self.ht = 0.01 # difference approximation tick - self.tf = 3.0 # final time - self.alpha = 0.5 # time gain - self.N = 10 # division number - self.threshold = 0.001 - self.input_num = 6 # input number of dummy, constraints - self.max_iteration = self.input_num * self.N - - # simulator - self.simulator = NMPCSimulatorSystem() - - # initial input, initialize as 1.0 - self.u_1s = np.ones(self.N) - self.u_2s = np.ones(self.N) - self.dummy_u_1s = np.ones(self.N) - self.dummy_u_2s = np.ones(self.N) - self.raw_1s = np.zeros(self.N) - self.raw_2s = np.zeros(self.N) - - self.history_u_1 = [] - self.history_u_2 = [] - self.history_dummy_u_1 = [] - self.history_dummy_u_2 = [] - self.history_raw_1 = [] - self.history_raw_2 = [] - self.history_f = [] - - def calc_input(self, x, y, yaw, v, time): - - # calculating sampling time - dt = self.tf * (1. - np.exp(-self.alpha * time)) / float(self.N) - - # x_dot - x_1_dot, x_2_dot, x_3_dot, x_4_dot = differential_model( - v, yaw, self.u_1s[0], self.u_2s[0]) - - dx_1 = x_1_dot * self.ht - dx_2 = x_2_dot * self.ht - dx_3 = x_3_dot * self.ht - dx_4 = x_4_dot * self.ht - - x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s = self.simulator.calc_predict_and_adjoint_state( - x + dx_1, y + dx_2, yaw + dx_3, v + dx_4, self.u_1s, self.u_2s, - self.N, dt) - - # Fxt:F(U,x+hx˙,t+h) - Fxt = self._calc_f(v_s, lam_3s, lam_4s, - self.u_1s, self.u_2s, self.dummy_u_1s, - self.dummy_u_2s, - self.raw_1s, self.raw_2s, self.N) - - # F:F(U,x,t) - x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s = self.simulator.calc_predict_and_adjoint_state( - x, y, yaw, v, self.u_1s, self.u_2s, self.N, dt) - - F = self._calc_f(v_s, lam_3s, lam_4s, - self.u_1s, self.u_2s, self.dummy_u_1s, self.dummy_u_2s, - self.raw_1s, self.raw_2s, self.N) - - right = -self.zeta * F - ((Fxt - F) / self.ht) - - du_1 = self.u_1s * self.ht - du_2 = self.u_2s * self.ht - ddummy_u_1 = self.dummy_u_1s * self.ht - ddummy_u_2 = self.dummy_u_2s * self.ht - draw_1 = self.raw_1s * self.ht - draw_2 = self.raw_2s * self.ht - - x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s = self.simulator.calc_predict_and_adjoint_state( - x + dx_1, y + dx_2, yaw + dx_3, v + dx_4, self.u_1s + du_1, - self.u_2s + du_2, self.N, dt) - - # Fuxt:F(U+hdU(0),x+hx˙,t+h) - Fuxt = self._calc_f(v_s, lam_3s, lam_4s, - self.u_1s + du_1, self.u_2s + du_2, - self.dummy_u_1s + ddummy_u_1, - self.dummy_u_2s + ddummy_u_2, - self.raw_1s + draw_1, self.raw_2s + draw_2, self.N) - - left = ((Fuxt - Fxt) / self.ht) - - # calculating cgmres - r0 = right - left - r0_norm = np.linalg.norm(r0) - - vs = np.zeros((self.max_iteration, self.max_iteration + 1)) - vs[:, 0] = r0 / r0_norm - - hs = np.zeros((self.max_iteration + 1, self.max_iteration + 1)) - - # in this case the state is 3(u and dummy_u) - e = np.zeros((self.max_iteration + 1, 1)) - e[0] = 1.0 - - ys_pre = None - - du_1_new, du_2_new, draw_1_new, draw_2_new = None, None, None, None - ddummy_u_1_new, ddummy_u_2_new = None, None - - for i in range(self.max_iteration): - du_1 = vs[::self.input_num, i] * self.ht - du_2 = vs[1::self.input_num, i] * self.ht - ddummy_u_1 = vs[2::self.input_num, i] * self.ht - ddummy_u_2 = vs[3::self.input_num, i] * self.ht - draw_1 = vs[4::self.input_num, i] * self.ht - draw_2 = vs[5::self.input_num, i] * self.ht - - x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s = self.simulator.calc_predict_and_adjoint_state( - x + dx_1, y + dx_2, yaw + dx_3, v + dx_4, self.u_1s + du_1, - self.u_2s + du_2, self.N, dt) - - Fuxt = self._calc_f(v_s, lam_3s, lam_4s, - self.u_1s + du_1, self.u_2s + du_2, - self.dummy_u_1s + ddummy_u_1, - self.dummy_u_2s + ddummy_u_2, - self.raw_1s + draw_1, self.raw_2s + draw_2, - self.N) - - Av = ((Fuxt - Fxt) / self.ht) - - sum_Av = np.zeros(self.max_iteration) - - # Gram–Schmidt orthonormalization - for j in range(i + 1): - hs[j, i] = np.dot(Av, vs[:, j]) - sum_Av = sum_Av + hs[j, i] * vs[:, j] - - v_est = Av - sum_Av - - hs[i + 1, i] = np.linalg.norm(v_est) - - vs[:, i + 1] = v_est / hs[i + 1, i] - - inv_hs = np.linalg.pinv(hs[:i + 1, :i]) - ys = np.dot(inv_hs, r0_norm * e[:i + 1]) - - judge_value = r0_norm * e[:i + 1] - np.dot(hs[:i + 1, :i], ys[:i]) - - flag1 = np.linalg.norm(judge_value) < self.threshold - - flag2 = i == self.max_iteration - 1 - if flag1 or flag2: - update_val = np.dot(vs[:, :i - 1], ys_pre[:i - 1]).flatten() - du_1_new = du_1 + update_val[::self.input_num] - du_2_new = du_2 + update_val[1::self.input_num] - ddummy_u_1_new = ddummy_u_1 + update_val[2::self.input_num] - ddummy_u_2_new = ddummy_u_2 + update_val[3::self.input_num] - draw_1_new = draw_1 + update_val[4::self.input_num] - draw_2_new = draw_2 + update_val[5::self.input_num] - break - - ys_pre = ys - - # update input - self.u_1s += du_1_new * self.ht - self.u_2s += du_2_new * self.ht - self.dummy_u_1s += ddummy_u_1_new * self.ht - self.dummy_u_2s += ddummy_u_2_new * self.ht - self.raw_1s += draw_1_new * self.ht - self.raw_2s += draw_2_new * self.ht - - x_s, y_s, yaw_s, v_s, lam_1s, lam_2s, lam_3s, lam_4s = self.simulator.calc_predict_and_adjoint_state( - x, y, yaw, v, self.u_1s, self.u_2s, self.N, dt) - - F = self._calc_f(v_s, lam_3s, lam_4s, - self.u_1s, self.u_2s, self.dummy_u_1s, self.dummy_u_2s, - self.raw_1s, self.raw_2s, self.N) - - print("norm(F) = {0}".format(np.linalg.norm(F))) - - # for save - self.history_f.append(np.linalg.norm(F)) - self.history_u_1.append(self.u_1s[0]) - self.history_u_2.append(self.u_2s[0]) - self.history_dummy_u_1.append(self.dummy_u_1s[0]) - self.history_dummy_u_2.append(self.dummy_u_2s[0]) - self.history_raw_1.append(self.raw_1s[0]) - self.history_raw_2.append(self.raw_2s[0]) - - return self.u_1s, self.u_2s - - @staticmethod - def _calc_f(v_s, lam_3s, lam_4s, u_1s, u_2s, dummy_u_1s, dummy_u_2s, - raw_1s, raw_2s, N): - - F = [] - for i in range(N): - # ∂H/∂u(xi, ui, λi) - F.append(u_1s[i] + lam_4s[i] + 2.0 * raw_1s[i] * u_1s[i]) - F.append(u_2s[i] + lam_3s[i] * v_s[i] / - WB * cos(u_2s[i]) ** 2 + 2.0 * raw_2s[i] * u_2s[i]) - F.append(-PHI_V + 2.0 * raw_1s[i] * dummy_u_1s[i]) - F.append(-PHI_OMEGA + 2.0 * raw_2s[i] * dummy_u_2s[i]) - - # C(xi, ui, λi) - F.append(u_1s[i] ** 2 + dummy_u_1s[i] ** 2 - U_A_MAX ** 2) - F.append(u_2s[i] ** 2 + dummy_u_2s[i] ** 2 - U_OMEGA_MAX ** 2) - - return np.array(F) - - -def plot_figures(plant_system, controller, iteration_num, - dt): # pragma: no cover - # figure - # time history - fig_p = plt.figure() - fig_u = plt.figure() - fig_f = plt.figure() - - # trajectory - fig_t = plt.figure() - fig_trajectory = fig_t.add_subplot(111) - fig_trajectory.set_aspect('equal') - - x_1_fig = fig_p.add_subplot(411) - x_2_fig = fig_p.add_subplot(412) - x_3_fig = fig_p.add_subplot(413) - x_4_fig = fig_p.add_subplot(414) - - u_1_fig = fig_u.add_subplot(411) - u_2_fig = fig_u.add_subplot(412) - dummy_1_fig = fig_u.add_subplot(413) - dummy_2_fig = fig_u.add_subplot(414) - - raw_1_fig = fig_f.add_subplot(311) - raw_2_fig = fig_f.add_subplot(312) - f_fig = fig_f.add_subplot(313) - - x_1_fig.plot(np.arange(iteration_num) * dt, plant_system.history_x) - x_1_fig.set_xlabel("time [s]") - x_1_fig.set_ylabel("x") - - x_2_fig.plot(np.arange(iteration_num) * dt, plant_system.history_y) - x_2_fig.set_xlabel("time [s]") - x_2_fig.set_ylabel("y") - - x_3_fig.plot(np.arange(iteration_num) * dt, plant_system.history_yaw) - x_3_fig.set_xlabel("time [s]") - x_3_fig.set_ylabel("yaw") - - x_4_fig.plot(np.arange(iteration_num) * dt, plant_system.history_v) - x_4_fig.set_xlabel("time [s]") - x_4_fig.set_ylabel("v") - - u_1_fig.plot(np.arange(iteration_num - 1) * dt, controller.history_u_1) - u_1_fig.set_xlabel("time [s]") - u_1_fig.set_ylabel("u_a") - - u_2_fig.plot(np.arange(iteration_num - 1) * dt, controller.history_u_2) - u_2_fig.set_xlabel("time [s]") - u_2_fig.set_ylabel("u_omega") - - dummy_1_fig.plot(np.arange(iteration_num - 1) * - dt, controller.history_dummy_u_1) - dummy_1_fig.set_xlabel("time [s]") - dummy_1_fig.set_ylabel("dummy u_1") - - dummy_2_fig.plot(np.arange(iteration_num - 1) * - dt, controller.history_dummy_u_2) - dummy_2_fig.set_xlabel("time [s]") - dummy_2_fig.set_ylabel("dummy u_2") - - raw_1_fig.plot(np.arange(iteration_num - 1) * dt, controller.history_raw_1) - raw_1_fig.set_xlabel("time [s]") - raw_1_fig.set_ylabel("raw_1") - - raw_2_fig.plot(np.arange(iteration_num - 1) * dt, controller.history_raw_2) - raw_2_fig.set_xlabel("time [s]") - raw_2_fig.set_ylabel("raw_2") - - f_fig.plot(np.arange(iteration_num - 1) * dt, controller.history_f) - f_fig.set_xlabel("time [s]") - f_fig.set_ylabel("optimal error") - - fig_trajectory.plot(plant_system.history_x, - plant_system.history_y, "-r") - fig_trajectory.set_xlabel("x [m]") - fig_trajectory.set_ylabel("y [m]") - fig_trajectory.axis("equal") - - # start state - plot_car(plant_system.history_x[0], - plant_system.history_y[0], - plant_system.history_yaw[0], - controller.history_u_2[0], - ) - - # goal state - plot_car(0.0, 0.0, 0.0, 0.0) - - plt.show() - - -def plot_car(x, y, yaw, steer=0.0, truck_color="-k"): # pragma: no cover - - # Vehicle parameters - LENGTH = 0.4 # [m] - WIDTH = 0.2 # [m] - BACK_TO_WHEEL = 0.1 # [m] - WHEEL_LEN = 0.03 # [m] - WHEEL_WIDTH = 0.02 # [m] - TREAD = 0.07 # [m] - - outline = np.array( - [[-BACK_TO_WHEEL, (LENGTH - BACK_TO_WHEEL), (LENGTH - BACK_TO_WHEEL), - -BACK_TO_WHEEL, -BACK_TO_WHEEL], - [WIDTH / 2, WIDTH / 2, - WIDTH / 2, -WIDTH / 2, WIDTH / 2]]) - - fr_wheel = np.array( - [[WHEEL_LEN, -WHEEL_LEN, -WHEEL_LEN, WHEEL_LEN, WHEEL_LEN], - [-WHEEL_WIDTH - TREAD, -WHEEL_WIDTH - TREAD, WHEEL_WIDTH - - TREAD, WHEEL_WIDTH - TREAD, -WHEEL_WIDTH - TREAD]]) - - rr_wheel = np.copy(fr_wheel) - - fl_wheel = np.copy(fr_wheel) - fl_wheel[1, :] *= -1 - rl_wheel = np.copy(rr_wheel) - rl_wheel[1, :] *= -1 - - Rot1 = np.array([[cos(yaw), sin(yaw)], - [-sin(yaw), cos(yaw)]]) - Rot2 = np.array([[cos(steer), sin(steer)], - [-sin(steer), cos(steer)]]) - - fr_wheel = (fr_wheel.T.dot(Rot2)).T - fl_wheel = (fl_wheel.T.dot(Rot2)).T - fr_wheel[0, :] += WB - fl_wheel[0, :] += WB - - fr_wheel = (fr_wheel.T.dot(Rot1)).T - fl_wheel = (fl_wheel.T.dot(Rot1)).T - - outline = (outline.T.dot(Rot1)).T - rr_wheel = (rr_wheel.T.dot(Rot1)).T - rl_wheel = (rl_wheel.T.dot(Rot1)).T - - outline[0, :] += x - outline[1, :] += y - fr_wheel[0, :] += x - fr_wheel[1, :] += y - rr_wheel[0, :] += x - rr_wheel[1, :] += y - fl_wheel[0, :] += x - fl_wheel[1, :] += y - rl_wheel[0, :] += x - rl_wheel[1, :] += y - - plt.plot(np.array(outline[0, :]).flatten(), - np.array(outline[1, :]).flatten(), truck_color) - plt.plot(np.array(fr_wheel[0, :]).flatten(), - np.array(fr_wheel[1, :]).flatten(), truck_color) - plt.plot(np.array(rr_wheel[0, :]).flatten(), - np.array(rr_wheel[1, :]).flatten(), truck_color) - plt.plot(np.array(fl_wheel[0, :]).flatten(), - np.array(fl_wheel[1, :]).flatten(), truck_color) - plt.plot(np.array(rl_wheel[0, :]).flatten(), - np.array(rl_wheel[1, :]).flatten(), truck_color) - plt.plot(x, y, "*") - - -def animation(plant, controller, dt): - skip = 2 # skip index for animation - - for t in range(1, len(controller.history_u_1), skip): - x = plant.history_x[t] - y = plant.history_y[t] - yaw = plant.history_yaw[t] - v = plant.history_v[t] - accel = controller.history_u_1[t] - time = t * dt - - if abs(v) <= 0.01: - steer = 0.0 - else: - steer = atan2(controller.history_u_2[t] * WB / v, 1.0) - - 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]) - plt.plot(plant.history_x, plant.history_y, "-r", label="trajectory") - plot_car(x, y, yaw, steer=steer) - plt.axis("equal") - plt.grid(True) - plt.title("Time[s]:" + str(round(time, 2)) + - ", accel[m/s]:" + str(round(accel, 2)) + - ", speed[km/h]:" + str(round(v * 3.6, 2))) - plt.pause(0.0001) - - plt.close("all") - - -def main(): - # simulation time - dt = 0.1 - iteration_time = 150.0 # [s] - - init_x = -4.5 - init_y = -2.5 - init_yaw = radians(45.0) - init_v = -1.0 - - # plant - plant_system = TwoWheeledSystem( - init_x, init_y, init_yaw, init_v) - - # controller - controller = NMPCControllerCGMRES() - - iteration_num = int(iteration_time / dt) - for i in range(1, iteration_num): - time = float(i) * dt - # make input - u_1s, u_2s = controller.calc_input( - plant_system.x, plant_system.y, plant_system.yaw, plant_system.v, - time) - # update state - plant_system.update_state(u_1s[0], u_2s[0]) - - if show_animation: # pragma: no cover - animation(plant_system, controller, dt) - plot_figures(plant_system, controller, iteration_num, dt) - - -if __name__ == "__main__": - main() diff --git a/PathTracking/lqr_speed_steer_control/lqr_speed_steer_control.py b/PathTracking/lqr_speed_steer_control/lqr_speed_steer_control.py deleted file mode 100644 index 5831d02d30..0000000000 --- a/PathTracking/lqr_speed_steer_control/lqr_speed_steer_control.py +++ /dev/null @@ -1,321 +0,0 @@ -""" - -Path tracking simulation with LQR speed and steering control - -author Atsushi Sakai (@Atsushi_twi) - -""" -import math -import sys -import matplotlib.pyplot as plt -import numpy as np -import scipy.linalg as la -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod -from PathPlanning.CubicSpline import cubic_spline_planner - -# === Parameters ===== - -# LQR parameter -lqr_Q = np.eye(5) -lqr_R = np.eye(2) -dt = 0.1 # time tick[s] -L = 0.5 # Wheel base of the vehicle [m] -max_steer = np.deg2rad(45.0) # maximum steering angle[rad] - -show_animation = True - - -class State: - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - - -def update(state, a, delta): - - if delta >= max_steer: - delta = max_steer - if delta <= - max_steer: - delta = - max_steer - - state.x = state.x + state.v * math.cos(state.yaw) * dt - state.y = state.y + state.v * math.sin(state.yaw) * dt - state.yaw = state.yaw + state.v / L * math.tan(delta) * dt - state.v = state.v + a * dt - - return state - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def solve_dare(A, B, Q, R): - """ - solve a discrete time_Algebraic Riccati equation (DARE) - """ - x = Q - x_next = Q - max_iter = 150 - eps = 0.01 - - for i in range(max_iter): - x_next = A.T @ x @ A - A.T @ x @ B @ \ - la.inv(R + B.T @ x @ B) @ B.T @ x @ A + Q - if (abs(x_next - x)).max() < eps: - break - x = x_next - - return x_next - - -def dlqr(A, B, Q, R): - """Solve the discrete time lqr controller. - x[k+1] = A x[k] + B u[k] - cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k] - # ref Bertsekas, p.151 - """ - - # first, try to solve the ricatti equation - X = solve_dare(A, B, Q, R) - - # compute the LQR gain - K = la.inv(B.T @ X @ B + R) @ (B.T @ X @ A) - - eig_result = la.eig(A - B @ K) - - return K, X, eig_result[0] - - -def lqr_speed_steering_control(state, cx, cy, cyaw, ck, pe, pth_e, sp, Q, R): - ind, e = calc_nearest_index(state, cx, cy, cyaw) - - tv = sp[ind] - - k = ck[ind] - v = state.v - th_e = pi_2_pi(state.yaw - cyaw[ind]) - - # A = [1.0, dt, 0.0, 0.0, 0.0 - # 0.0, 0.0, v, 0.0, 0.0] - # 0.0, 0.0, 1.0, dt, 0.0] - # 0.0, 0.0, 0.0, 0.0, 0.0] - # 0.0, 0.0, 0.0, 0.0, 1.0] - A = np.zeros((5, 5)) - A[0, 0] = 1.0 - A[0, 1] = dt - A[1, 2] = v - A[2, 2] = 1.0 - A[2, 3] = dt - A[4, 4] = 1.0 - - # B = [0.0, 0.0 - # 0.0, 0.0 - # 0.0, 0.0 - # v/L, 0.0 - # 0.0, dt] - B = np.zeros((5, 2)) - B[3, 0] = v / L - B[4, 1] = dt - - K, _, _ = dlqr(A, B, Q, R) - - # state vector - # x = [e, dot_e, th_e, dot_th_e, delta_v] - # e: lateral distance to the path - # dot_e: derivative of e - # th_e: angle difference to the path - # dot_th_e: derivative of th_e - # delta_v: difference between current speed and target speed - x = np.zeros((5, 1)) - x[0, 0] = e - x[1, 0] = (e - pe) / dt - x[2, 0] = th_e - x[3, 0] = (th_e - pth_e) / dt - x[4, 0] = v - tv - - # input vector - # u = [delta, accel] - # delta: steering angle - # accel: acceleration - ustar = -K @ x - - # calc steering input - ff = math.atan2(L * k, 1) # feedforward steering angle - fb = pi_2_pi(ustar[0, 0]) # feedback steering angle - delta = ff + fb - - # calc accel input - accel = ustar[1, 0] - - return delta, ind, e, th_e, accel - - -def calc_nearest_index(state, cx, cy, cyaw): - dx = [state.x - icx for icx in cx] - dy = [state.y - icy for icy in cy] - - d = [idx ** 2 + idy ** 2 for (idx, idy) in zip(dx, dy)] - - mind = min(d) - - ind = d.index(mind) - - mind = math.sqrt(mind) - - dxl = cx[ind] - state.x - dyl = cy[ind] - state.y - - angle = pi_2_pi(cyaw[ind] - math.atan2(dyl, dxl)) - if angle < 0: - mind *= -1 - - return ind, mind - - -def do_simulation(cx, cy, cyaw, ck, speed_profile, goal): - T = 500.0 # max simulation time - goal_dis = 0.3 - stop_speed = 0.05 - - state = State(x=-0.0, y=-0.0, yaw=0.0, v=0.0) - - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - - e, e_th = 0.0, 0.0 - - while T >= time: - dl, target_ind, e, e_th, ai = lqr_speed_steering_control( - state, cx, cy, cyaw, ck, e, e_th, speed_profile, lqr_Q, lqr_R) - - state = update(state, ai, dl) - - if abs(state.v) <= stop_speed: - target_ind += 1 - - time = time + dt - - # check goal - dx = state.x - goal[0] - dy = state.y - goal[1] - if math.hypot(dx, dy) <= goal_dis: - print("Goal") - break - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - - if target_ind % 1 == 0 and show_animation: - 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]) - plt.plot(cx, cy, "-r", label="course") - plt.plot(x, y, "ob", label="trajectory") - plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title("speed[km/h]:" + str(round(state.v * 3.6, 2)) - + ",target index:" + str(target_ind)) - plt.pause(0.0001) - - return t, x, y, yaw, v - - -def calc_speed_profile(cyaw, target_speed): - speed_profile = [target_speed] * len(cyaw) - - direction = 1.0 - - # Set stop point - for i in range(len(cyaw) - 1): - dyaw = abs(cyaw[i + 1] - cyaw[i]) - switch = math.pi / 4.0 <= dyaw < math.pi / 2.0 - - if switch: - direction *= -1 - - if direction != 1.0: - speed_profile[i] = - target_speed - else: - speed_profile[i] = target_speed - - if switch: - speed_profile[i] = 0.0 - - # speed down - for i in range(40): - speed_profile[-i] = target_speed / (50 - i) - if speed_profile[-i] <= 1.0 / 3.6: - speed_profile[-i] = 1.0 / 3.6 - - return speed_profile - - -def main(): - print("LQR steering control tracking start!!") - ax = [0.0, 6.0, 12.5, 10.0, 17.5, 20.0, 25.0] - ay = [0.0, -3.0, -5.0, 6.5, 3.0, 0.0, 0.0] - goal = [ax[-1], ay[-1]] - - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=0.1) - target_speed = 10.0 / 3.6 # simulation parameter km/h -> m/s - - sp = calc_speed_profile(cyaw, target_speed) - - t, x, y, yaw, v = do_simulation(cx, cy, cyaw, ck, sp, goal) - - if show_animation: # pragma: no cover - plt.close() - plt.subplots(1) - plt.plot(ax, ay, "xb", label="waypoints") - plt.plot(cx, cy, "-r", label="target course") - plt.plot(x, y, "-g", label="tracking") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - plt.subplots(1) - - plt.plot(t, np.array(v)*3.6, label="speed") - plt.grid(True) - plt.xlabel("Time [sec]") - plt.ylabel("Speed [m/s]") - plt.legend() - - plt.subplots(1) - plt.plot(s, [np.rad2deg(iyaw) for iyaw in cyaw], "-r", label="yaw") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("yaw angle[deg]") - - plt.subplots(1) - plt.plot(s, ck, "-r", label="curvature") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("curvature [1/m]") - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathTracking/lqr_steer_control/__init__.py b/PathTracking/lqr_steer_control/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathTracking/lqr_steer_control/lqr_steer_control.py b/PathTracking/lqr_steer_control/lqr_steer_control.py deleted file mode 100644 index 3c066917ff..0000000000 --- a/PathTracking/lqr_steer_control/lqr_steer_control.py +++ /dev/null @@ -1,291 +0,0 @@ -""" - -Path tracking simulation with LQR steering control and PID speed control. - -author Atsushi Sakai (@Atsushi_twi) - -""" -import scipy.linalg as la -import matplotlib.pyplot as plt -import math -import numpy as np -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod -from PathPlanning.CubicSpline import cubic_spline_planner - -Kp = 1.0 # speed proportional gain - -# LQR parameter -Q = np.eye(4) -R = np.eye(1) - -# parameters -dt = 0.1 # time tick[s] -L = 0.5 # Wheelbase of the vehicle [m] -max_steer = np.deg2rad(45.0) # maximum steering angle[rad] - -show_animation = True -# show_animation = False - - -class State: - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - - -def update(state, a, delta): - - if delta >= max_steer: - delta = max_steer - if delta <= - max_steer: - delta = - max_steer - - state.x = state.x + state.v * math.cos(state.yaw) * dt - state.y = state.y + state.v * math.sin(state.yaw) * dt - state.yaw = state.yaw + state.v / L * math.tan(delta) * dt - state.v = state.v + a * dt - - return state - - -def pid_control(target, current): - a = Kp * (target - current) - - return a - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def solve_DARE(A, B, Q, R): - """ - solve a discrete time_Algebraic Riccati equation (DARE) - """ - X = Q - Xn = Q - max_iter = 150 - eps = 0.01 - - for i in range(max_iter): - Xn = A.T @ X @ A - A.T @ X @ B @ \ - la.inv(R + B.T @ X @ B) @ B.T @ X @ A + Q - if (abs(Xn - X)).max() < eps: - break - X = Xn - - return Xn - - -def dlqr(A, B, Q, R): - """Solve the discrete time lqr controller. - x[k+1] = A x[k] + B u[k] - cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k] - # ref Bertsekas, p.151 - """ - - # first, try to solve the ricatti equation - X = solve_DARE(A, B, Q, R) - - # compute the LQR gain - K = la.inv(B.T @ X @ B + R) @ (B.T @ X @ A) - - eigVals, eigVecs = la.eig(A - B @ K) - - return K, X, eigVals - - -def lqr_steering_control(state, cx, cy, cyaw, ck, pe, pth_e): - ind, e = calc_nearest_index(state, cx, cy, cyaw) - - k = ck[ind] - v = state.v - th_e = pi_2_pi(state.yaw - cyaw[ind]) - - A = np.zeros((4, 4)) - A[0, 0] = 1.0 - A[0, 1] = dt - A[1, 2] = v - A[2, 2] = 1.0 - A[2, 3] = dt - # print(A) - - B = np.zeros((4, 1)) - B[3, 0] = v / L - - K, _, _ = dlqr(A, B, Q, R) - - x = np.zeros((4, 1)) - - x[0, 0] = e - x[1, 0] = (e - pe) / dt - x[2, 0] = th_e - x[3, 0] = (th_e - pth_e) / dt - - ff = math.atan2(L * k, 1) - fb = pi_2_pi((-K @ x)[0, 0]) - - delta = ff + fb - - return delta, ind, e, th_e - - -def calc_nearest_index(state, cx, cy, cyaw): - dx = [state.x - icx for icx in cx] - dy = [state.y - icy for icy in cy] - - d = [idx ** 2 + idy ** 2 for (idx, idy) in zip(dx, dy)] - - mind = min(d) - - ind = d.index(mind) - - mind = math.sqrt(mind) - - dxl = cx[ind] - state.x - dyl = cy[ind] - state.y - - angle = pi_2_pi(cyaw[ind] - math.atan2(dyl, dxl)) - if angle < 0: - mind *= -1 - - return ind, mind - - -def closed_loop_prediction(cx, cy, cyaw, ck, speed_profile, goal): - T = 500.0 # max simulation time - goal_dis = 0.3 - stop_speed = 0.05 - - state = State(x=-0.0, y=-0.0, yaw=0.0, v=0.0) - - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - - e, e_th = 0.0, 0.0 - - while T >= time: - dl, target_ind, e, e_th = lqr_steering_control( - state, cx, cy, cyaw, ck, e, e_th) - - ai = pid_control(speed_profile[target_ind], state.v) - state = update(state, ai, dl) - - if abs(state.v) <= stop_speed: - target_ind += 1 - - time = time + dt - - # check goal - dx = state.x - goal[0] - dy = state.y - goal[1] - if math.hypot(dx, dy) <= goal_dis: - print("Goal") - break - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - - if target_ind % 1 == 0 and show_animation: - 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]) - plt.plot(cx, cy, "-r", label="course") - plt.plot(x, y, "ob", label="trajectory") - plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title("speed[km/h]:" + str(round(state.v * 3.6, 2)) - + ",target index:" + str(target_ind)) - plt.pause(0.0001) - - return t, x, y, yaw, v - - -def calc_speed_profile(cx, cy, cyaw, target_speed): - speed_profile = [target_speed] * len(cx) - - direction = 1.0 - - # Set stop point - for i in range(len(cx) - 1): - dyaw = abs(cyaw[i + 1] - cyaw[i]) - switch = math.pi / 4.0 <= dyaw < math.pi / 2.0 - - if switch: - direction *= -1 - - if direction != 1.0: - speed_profile[i] = - target_speed - else: - speed_profile[i] = target_speed - - if switch: - speed_profile[i] = 0.0 - - speed_profile[-1] = 0.0 - - return speed_profile - - -def main(): - print("LQR steering control tracking start!!") - ax = [0.0, 6.0, 12.5, 10.0, 7.5, 3.0, -1.0] - ay = [0.0, -3.0, -5.0, 6.5, 3.0, 5.0, -2.0] - goal = [ax[-1], ay[-1]] - - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=0.1) - target_speed = 10.0 / 3.6 # simulation parameter km/h -> m/s - - sp = calc_speed_profile(cx, cy, cyaw, target_speed) - - t, x, y, yaw, v = closed_loop_prediction(cx, cy, cyaw, ck, sp, goal) - - if show_animation: # pragma: no cover - plt.close() - plt.subplots(1) - plt.plot(ax, ay, "xb", label="input") - plt.plot(cx, cy, "-r", label="spline") - plt.plot(x, y, "-g", label="tracking") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - - plt.subplots(1) - plt.plot(s, [np.rad2deg(iyaw) for iyaw in cyaw], "-r", label="yaw") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("yaw angle[deg]") - - plt.subplots(1) - plt.plot(s, ck, "-r", label="curvature") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("curvature [1/m]") - - plt.show() - - -if __name__ == '__main__': - main() diff --git a/PathTracking/model_predictive_speed_and_steer_control/model_predictive_speed_and_steer_control.py b/PathTracking/model_predictive_speed_and_steer_control/model_predictive_speed_and_steer_control.py deleted file mode 100644 index eb2d7b6a73..0000000000 --- a/PathTracking/model_predictive_speed_and_steer_control/model_predictive_speed_and_steer_control.py +++ /dev/null @@ -1,628 +0,0 @@ -""" - -Path tracking simulation with iterative linear model predictive control for speed and steer control - -author: Atsushi Sakai (@Atsushi_twi) - -""" -import matplotlib.pyplot as plt -import time -import cvxpy -import math -import numpy as np -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -from utils.angle import angle_mod - -from PathPlanning.CubicSpline import cubic_spline_planner - -NX = 4 # x = x, y, v, yaw -NU = 2 # a = [accel, steer] -T = 5 # horizon length - -# mpc parameters -R = np.diag([0.01, 0.01]) # input cost matrix -Rd = np.diag([0.01, 1.0]) # input difference cost matrix -Q = np.diag([1.0, 1.0, 0.5, 0.5]) # state cost matrix -Qf = Q # state final matrix -GOAL_DIS = 1.5 # goal distance -STOP_SPEED = 0.5 / 3.6 # stop speed -MAX_TIME = 500.0 # max simulation time - -# iterative paramter -MAX_ITER = 3 # Max iteration -DU_TH = 0.1 # iteration finish param - -TARGET_SPEED = 10.0 / 3.6 # [m/s] target speed -N_IND_SEARCH = 10 # Search index number - -DT = 0.2 # [s] time tick - -# Vehicle parameters -LENGTH = 4.5 # [m] -WIDTH = 2.0 # [m] -BACKTOWHEEL = 1.0 # [m] -WHEEL_LEN = 0.3 # [m] -WHEEL_WIDTH = 0.2 # [m] -TREAD = 0.7 # [m] -WB = 2.5 # [m] - -MAX_STEER = np.deg2rad(45.0) # maximum steering angle [rad] -MAX_DSTEER = np.deg2rad(30.0) # maximum steering speed [rad/s] -MAX_SPEED = 55.0 / 3.6 # maximum speed [m/s] -MIN_SPEED = -20.0 / 3.6 # minimum speed [m/s] -MAX_ACCEL = 1.0 # maximum accel [m/ss] - -show_animation = True - - -class State: - """ - vehicle state class - """ - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - self.predelta = None - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def get_linear_model_matrix(v, phi, delta): - - A = np.zeros((NX, NX)) - A[0, 0] = 1.0 - A[1, 1] = 1.0 - A[2, 2] = 1.0 - A[3, 3] = 1.0 - A[0, 2] = DT * math.cos(phi) - A[0, 3] = - DT * v * math.sin(phi) - A[1, 2] = DT * math.sin(phi) - A[1, 3] = DT * v * math.cos(phi) - A[3, 2] = DT * math.tan(delta) / WB - - B = np.zeros((NX, NU)) - B[2, 0] = DT - B[3, 1] = DT * v / (WB * math.cos(delta) ** 2) - - C = np.zeros(NX) - C[0] = DT * v * math.sin(phi) * phi - C[1] = - DT * v * math.cos(phi) * phi - C[3] = - DT * v * delta / (WB * math.cos(delta) ** 2) - - return A, B, C - - -def plot_car(x, y, yaw, steer=0.0, cabcolor="-r", truckcolor="-k"): # pragma: no cover - - outline = np.array([[-BACKTOWHEEL, (LENGTH - BACKTOWHEEL), (LENGTH - BACKTOWHEEL), -BACKTOWHEEL, -BACKTOWHEEL], - [WIDTH / 2, WIDTH / 2, - WIDTH / 2, -WIDTH / 2, WIDTH / 2]]) - - fr_wheel = np.array([[WHEEL_LEN, -WHEEL_LEN, -WHEEL_LEN, WHEEL_LEN, WHEEL_LEN], - [-WHEEL_WIDTH - TREAD, -WHEEL_WIDTH - TREAD, WHEEL_WIDTH - TREAD, WHEEL_WIDTH - TREAD, -WHEEL_WIDTH - TREAD]]) - - rr_wheel = np.copy(fr_wheel) - - fl_wheel = np.copy(fr_wheel) - fl_wheel[1, :] *= -1 - rl_wheel = np.copy(rr_wheel) - rl_wheel[1, :] *= -1 - - Rot1 = np.array([[math.cos(yaw), math.sin(yaw)], - [-math.sin(yaw), math.cos(yaw)]]) - Rot2 = np.array([[math.cos(steer), math.sin(steer)], - [-math.sin(steer), math.cos(steer)]]) - - fr_wheel = (fr_wheel.T.dot(Rot2)).T - fl_wheel = (fl_wheel.T.dot(Rot2)).T - fr_wheel[0, :] += WB - fl_wheel[0, :] += WB - - fr_wheel = (fr_wheel.T.dot(Rot1)).T - fl_wheel = (fl_wheel.T.dot(Rot1)).T - - outline = (outline.T.dot(Rot1)).T - rr_wheel = (rr_wheel.T.dot(Rot1)).T - rl_wheel = (rl_wheel.T.dot(Rot1)).T - - outline[0, :] += x - outline[1, :] += y - fr_wheel[0, :] += x - fr_wheel[1, :] += y - rr_wheel[0, :] += x - rr_wheel[1, :] += y - fl_wheel[0, :] += x - fl_wheel[1, :] += y - rl_wheel[0, :] += x - rl_wheel[1, :] += y - - plt.plot(np.array(outline[0, :]).flatten(), - np.array(outline[1, :]).flatten(), truckcolor) - plt.plot(np.array(fr_wheel[0, :]).flatten(), - np.array(fr_wheel[1, :]).flatten(), truckcolor) - plt.plot(np.array(rr_wheel[0, :]).flatten(), - np.array(rr_wheel[1, :]).flatten(), truckcolor) - plt.plot(np.array(fl_wheel[0, :]).flatten(), - np.array(fl_wheel[1, :]).flatten(), truckcolor) - plt.plot(np.array(rl_wheel[0, :]).flatten(), - np.array(rl_wheel[1, :]).flatten(), truckcolor) - plt.plot(x, y, "*") - - -def update_state(state, a, delta): - - # input check - if delta >= MAX_STEER: - delta = MAX_STEER - elif delta <= -MAX_STEER: - delta = -MAX_STEER - - state.x = state.x + state.v * math.cos(state.yaw) * DT - state.y = state.y + state.v * math.sin(state.yaw) * DT - state.yaw = state.yaw + state.v / WB * math.tan(delta) * DT - state.v = state.v + a * DT - - if state.v > MAX_SPEED: - state.v = MAX_SPEED - elif state.v < MIN_SPEED: - state.v = MIN_SPEED - - return state - - -def get_nparray_from_matrix(x): - return np.array(x).flatten() - - -def calc_nearest_index(state, cx, cy, cyaw, pind): - - dx = [state.x - icx for icx in cx[pind:(pind + N_IND_SEARCH)]] - dy = [state.y - icy for icy in cy[pind:(pind + N_IND_SEARCH)]] - - d = [idx ** 2 + idy ** 2 for (idx, idy) in zip(dx, dy)] - - mind = min(d) - - ind = d.index(mind) + pind - - mind = math.sqrt(mind) - - dxl = cx[ind] - state.x - dyl = cy[ind] - state.y - - angle = pi_2_pi(cyaw[ind] - math.atan2(dyl, dxl)) - if angle < 0: - mind *= -1 - - return ind, mind - - -def predict_motion(x0, oa, od, xref): - xbar = xref * 0.0 - for i, _ in enumerate(x0): - xbar[i, 0] = x0[i] - - state = State(x=x0[0], y=x0[1], yaw=x0[3], v=x0[2]) - for (ai, di, i) in zip(oa, od, range(1, T + 1)): - state = update_state(state, ai, di) - xbar[0, i] = state.x - xbar[1, i] = state.y - xbar[2, i] = state.v - xbar[3, i] = state.yaw - - return xbar - - -def iterative_linear_mpc_control(xref, x0, dref, oa, od): - """ - MPC control with updating operational point iteratively - """ - ox, oy, oyaw, ov = None, None, None, None - - if oa is None or od is None: - oa = [0.0] * T - od = [0.0] * T - - for i in range(MAX_ITER): - xbar = predict_motion(x0, oa, od, xref) - poa, pod = oa[:], od[:] - oa, od, ox, oy, oyaw, ov = linear_mpc_control(xref, xbar, x0, dref) - du = sum(abs(oa - poa)) + sum(abs(od - pod)) # calc u change value - if du <= DU_TH: - break - else: - print("Iterative is max iter") - - return oa, od, ox, oy, oyaw, ov - - -def linear_mpc_control(xref, xbar, x0, dref): - """ - linear mpc control - - xref: reference point - xbar: operational point - x0: initial state - dref: reference steer angle - """ - - x = cvxpy.Variable((NX, T + 1)) - u = cvxpy.Variable((NU, T)) - - cost = 0.0 - constraints = [] - - for t in range(T): - cost += cvxpy.quad_form(u[:, t], R) - - if t != 0: - cost += cvxpy.quad_form(xref[:, t] - x[:, t], Q) - - A, B, C = get_linear_model_matrix( - xbar[2, t], xbar[3, t], dref[0, t]) - constraints += [x[:, t + 1] == A @ x[:, t] + B @ u[:, t] + C] - - if t < (T - 1): - cost += cvxpy.quad_form(u[:, t + 1] - u[:, t], Rd) - constraints += [cvxpy.abs(u[1, t + 1] - u[1, t]) <= - MAX_DSTEER * DT] - - cost += cvxpy.quad_form(xref[:, T] - x[:, T], Qf) - - constraints += [x[:, 0] == x0] - constraints += [x[2, :] <= MAX_SPEED] - constraints += [x[2, :] >= MIN_SPEED] - constraints += [cvxpy.abs(u[0, :]) <= MAX_ACCEL] - constraints += [cvxpy.abs(u[1, :]) <= MAX_STEER] - - prob = cvxpy.Problem(cvxpy.Minimize(cost), constraints) - prob.solve(solver=cvxpy.CLARABEL, verbose=False) - - if prob.status == cvxpy.OPTIMAL or prob.status == cvxpy.OPTIMAL_INACCURATE: - ox = get_nparray_from_matrix(x.value[0, :]) - oy = get_nparray_from_matrix(x.value[1, :]) - ov = get_nparray_from_matrix(x.value[2, :]) - oyaw = get_nparray_from_matrix(x.value[3, :]) - oa = get_nparray_from_matrix(u.value[0, :]) - odelta = get_nparray_from_matrix(u.value[1, :]) - - else: - print("Error: Cannot solve mpc..") - oa, odelta, ox, oy, oyaw, ov = None, None, None, None, None, None - - return oa, odelta, ox, oy, oyaw, ov - - -def calc_ref_trajectory(state, cx, cy, cyaw, ck, sp, dl, pind): - xref = np.zeros((NX, T + 1)) - dref = np.zeros((1, T + 1)) - ncourse = len(cx) - - ind, _ = calc_nearest_index(state, cx, cy, cyaw, pind) - - if pind >= ind: - ind = pind - - xref[0, 0] = cx[ind] - xref[1, 0] = cy[ind] - xref[2, 0] = sp[ind] - xref[3, 0] = cyaw[ind] - dref[0, 0] = 0.0 # steer operational point should be 0 - - travel = 0.0 - - for i in range(T + 1): - travel += abs(state.v) * DT - dind = int(round(travel / dl)) - - if (ind + dind) < ncourse: - xref[0, i] = cx[ind + dind] - xref[1, i] = cy[ind + dind] - xref[2, i] = sp[ind + dind] - xref[3, i] = cyaw[ind + dind] - dref[0, i] = 0.0 - else: - xref[0, i] = cx[ncourse - 1] - xref[1, i] = cy[ncourse - 1] - xref[2, i] = sp[ncourse - 1] - xref[3, i] = cyaw[ncourse - 1] - dref[0, i] = 0.0 - - return xref, ind, dref - - -def check_goal(state, goal, tind, nind): - - # check goal - dx = state.x - goal[0] - dy = state.y - goal[1] - d = math.hypot(dx, dy) - - isgoal = (d <= GOAL_DIS) - - if abs(tind - nind) >= 5: - isgoal = False - - isstop = (abs(state.v) <= STOP_SPEED) - - if isgoal and isstop: - return True - - return False - - -def do_simulation(cx, cy, cyaw, ck, sp, dl, initial_state): - """ - Simulation - - cx: course x position list - cy: course y position list - cy: course yaw position list - ck: course curvature list - sp: speed profile - dl: course tick [m] - - """ - - goal = [cx[-1], cy[-1]] - - state = initial_state - - # initial yaw compensation - if state.yaw - cyaw[0] >= math.pi: - state.yaw -= math.pi * 2.0 - elif state.yaw - cyaw[0] <= -math.pi: - state.yaw += math.pi * 2.0 - - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - d = [0.0] - a = [0.0] - target_ind, _ = calc_nearest_index(state, cx, cy, cyaw, 0) - - odelta, oa = None, None - - cyaw = smooth_yaw(cyaw) - - while MAX_TIME >= time: - xref, target_ind, dref = calc_ref_trajectory( - state, cx, cy, cyaw, ck, sp, dl, target_ind) - - x0 = [state.x, state.y, state.v, state.yaw] # current state - - oa, odelta, ox, oy, oyaw, ov = iterative_linear_mpc_control( - xref, x0, dref, oa, odelta) - - di, ai = 0.0, 0.0 - if odelta is not None: - di, ai = odelta[0], oa[0] - state = update_state(state, ai, di) - - time = time + DT - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - d.append(di) - a.append(ai) - - if check_goal(state, goal, target_ind, len(cx)): - print("Goal") - break - - if show_animation: # pragma: no cover - 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]) - if ox is not None: - plt.plot(ox, oy, "xr", label="MPC") - plt.plot(cx, cy, "-r", label="course") - plt.plot(x, y, "ob", label="trajectory") - plt.plot(xref[0, :], xref[1, :], "xk", label="xref") - plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - plot_car(state.x, state.y, state.yaw, steer=di) - plt.axis("equal") - plt.grid(True) - plt.title("Time[s]:" + str(round(time, 2)) - + ", speed[km/h]:" + str(round(state.v * 3.6, 2))) - plt.pause(0.0001) - - return t, x, y, yaw, v, d, a - - -def calc_speed_profile(cx, cy, cyaw, target_speed): - - speed_profile = [target_speed] * len(cx) - direction = 1.0 # forward - - # Set stop point - for i in range(len(cx) - 1): - dx = cx[i + 1] - cx[i] - dy = cy[i + 1] - cy[i] - - move_direction = math.atan2(dy, dx) - - if dx != 0.0 and dy != 0.0: - dangle = abs(pi_2_pi(move_direction - cyaw[i])) - if dangle >= math.pi / 4.0: - direction = -1.0 - else: - direction = 1.0 - - if direction != 1.0: - speed_profile[i] = - target_speed - else: - speed_profile[i] = target_speed - - speed_profile[-1] = 0.0 - - return speed_profile - - -def smooth_yaw(yaw): - - for i in range(len(yaw) - 1): - dyaw = yaw[i + 1] - yaw[i] - - while dyaw >= math.pi / 2.0: - yaw[i + 1] -= math.pi * 2.0 - dyaw = yaw[i + 1] - yaw[i] - - while dyaw <= -math.pi / 2.0: - yaw[i + 1] += math.pi * 2.0 - dyaw = yaw[i + 1] - yaw[i] - - return yaw - - -def get_straight_course(dl): - ax = [0.0, 5.0, 10.0, 20.0, 30.0, 40.0, 50.0] - ay = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - - return cx, cy, cyaw, ck - - -def get_straight_course2(dl): - ax = [0.0, -10.0, -20.0, -40.0, -50.0, -60.0, -70.0] - ay = [0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0] - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - - return cx, cy, cyaw, ck - - -def get_straight_course3(dl): - ax = [0.0, -10.0, -20.0, -40.0, -50.0, -60.0, -70.0] - ay = [0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0] - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - - cyaw = [i - math.pi for i in cyaw] - - return cx, cy, cyaw, ck - - -def get_forward_course(dl): - ax = [0.0, 60.0, 125.0, 50.0, 75.0, 30.0, -10.0] - ay = [0.0, 0.0, 50.0, 65.0, 30.0, 50.0, -20.0] - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - - return cx, cy, cyaw, ck - - -def get_switch_back_course(dl): - ax = [0.0, 30.0, 6.0, 20.0, 35.0] - ay = [0.0, 0.0, 20.0, 35.0, 20.0] - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - ax = [35.0, 10.0, 0.0, 0.0] - ay = [20.0, 30.0, 5.0, 0.0] - cx2, cy2, cyaw2, ck2, s2 = cubic_spline_planner.calc_spline_course( - ax, ay, ds=dl) - cyaw2 = [i - math.pi for i in cyaw2] - cx.extend(cx2) - cy.extend(cy2) - cyaw.extend(cyaw2) - ck.extend(ck2) - - return cx, cy, cyaw, ck - - -def main(): - print(__file__ + " start!!") - start = time.time() - - dl = 1.0 # course tick - # cx, cy, cyaw, ck = get_straight_course(dl) - # cx, cy, cyaw, ck = get_straight_course2(dl) - # cx, cy, cyaw, ck = get_straight_course3(dl) - # cx, cy, cyaw, ck = get_forward_course(dl) - cx, cy, cyaw, ck = get_switch_back_course(dl) - - sp = calc_speed_profile(cx, cy, cyaw, TARGET_SPEED) - - initial_state = State(x=cx[0], y=cy[0], yaw=cyaw[0], v=0.0) - - t, x, y, yaw, v, d, a = do_simulation( - cx, cy, cyaw, ck, sp, dl, initial_state) - - elapsed_time = time.time() - start - print(f"calc time:{elapsed_time:.6f} [sec]") - - if show_animation: # pragma: no cover - plt.close("all") - plt.subplots() - plt.plot(cx, cy, "-r", label="spline") - plt.plot(x, y, "-g", label="tracking") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - - plt.subplots() - plt.plot(t, v, "-r", label="speed") - plt.grid(True) - plt.xlabel("Time [s]") - plt.ylabel("Speed [kmh]") - - plt.show() - - -def main2(): - print(__file__ + " start!!") - start = time.time() - - dl = 1.0 # course tick - cx, cy, cyaw, ck = get_straight_course3(dl) - - sp = calc_speed_profile(cx, cy, cyaw, TARGET_SPEED) - - initial_state = State(x=cx[0], y=cy[0], yaw=0.0, v=0.0) - - t, x, y, yaw, v, d, a = do_simulation( - cx, cy, cyaw, ck, sp, dl, initial_state) - - elapsed_time = time.time() - start - print(f"calc time:{elapsed_time:.6f} [sec]") - - if show_animation: # pragma: no cover - plt.close("all") - plt.subplots() - plt.plot(cx, cy, "-r", label="spline") - plt.plot(x, y, "-g", label="tracking") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - - plt.subplots() - plt.plot(t, v, "-r", label="speed") - plt.grid(True) - plt.xlabel("Time [s]") - plt.ylabel("Speed [kmh]") - - plt.show() - - -if __name__ == '__main__': - main() - # main2() diff --git a/PathTracking/move_to_pose/__init__.py b/PathTracking/move_to_pose/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/PathTracking/move_to_pose/move_to_pose.py b/PathTracking/move_to_pose/move_to_pose.py deleted file mode 100644 index faf1264953..0000000000 --- a/PathTracking/move_to_pose/move_to_pose.py +++ /dev/null @@ -1,223 +0,0 @@ -""" - -Move to specified pose - -Author: Daniel Ingram (daniel-s-ingram) - Atsushi Sakai (@Atsushi_twi) - Seied Muhammad Yazdian (@Muhammad-Yazdian) - Wang Zheng (@Aglargil) - -P. I. Corke, "Robotics, Vision & Control", Springer 2017, ISBN 978-3-319-54413-7 - -""" - -import matplotlib.pyplot as plt -import numpy as np -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod -from random import random - - -class PathFinderController: - """ - Constructs an instantiate of the PathFinderController for navigating a - 3-DOF wheeled robot on a 2D plane - - Parameters - ---------- - Kp_rho : The linear velocity gain to translate the robot along a line - towards the goal - Kp_alpha : The angular velocity gain to rotate the robot towards the goal - Kp_beta : The offset angular velocity gain accounting for smooth merging to - the goal angle (i.e., it helps the robot heading to be parallel - to the target angle.) - """ - - def __init__(self, Kp_rho, Kp_alpha, Kp_beta): - self.Kp_rho = Kp_rho - self.Kp_alpha = Kp_alpha - self.Kp_beta = Kp_beta - - def calc_control_command(self, x_diff, y_diff, theta, theta_goal): - """ - Returns the control command for the linear and angular velocities as - well as the distance to goal - - Parameters - ---------- - x_diff : The position of target with respect to current robot position - in x direction - y_diff : The position of target with respect to current robot position - in y direction - theta : The current heading angle of robot with respect to x axis - theta_goal: The target angle of robot with respect to x axis - - Returns - ------- - rho : The distance between the robot and the goal position - v : Command linear velocity - w : Command angular velocity - """ - - # Description of local variables: - # - alpha is the angle to the goal relative to the heading of the robot - # - beta is the angle between the robot's position and the goal - # position plus the goal angle - # - Kp_rho*rho and Kp_alpha*alpha drive the robot along a line towards - # the goal - # - Kp_beta*beta rotates the line so that it is parallel to the goal - # angle - # - # Note: - # we restrict alpha and beta (angle differences) to the range - # [-pi, pi] to prevent unstable behavior e.g. difference going - # from 0 rad to 2*pi rad with slight turn - - # The velocity v always has a constant sign which depends on the initial value of α. - rho = np.hypot(x_diff, y_diff) - v = self.Kp_rho * rho - - alpha = angle_mod(np.arctan2(y_diff, x_diff) - theta) - beta = angle_mod(theta_goal - theta - alpha) - if alpha > np.pi / 2 or alpha < -np.pi / 2: - # recalculate alpha to make alpha in the range of [-pi/2, pi/2] - alpha = angle_mod(np.arctan2(-y_diff, -x_diff) - theta) - beta = angle_mod(theta_goal - theta - alpha) - w = self.Kp_alpha * alpha - self.Kp_beta * beta - v = -v - else: - w = self.Kp_alpha * alpha - self.Kp_beta * beta - - return rho, v, w - - -# simulation parameters -controller = PathFinderController(9, 15, 3) -dt = 0.01 -MAX_SIM_TIME = 5 # seconds, robot will stop moving when time exceeds this value - -# Robot specifications -MAX_LINEAR_SPEED = 15 -MAX_ANGULAR_SPEED = 7 - -show_animation = True - - -def move_to_pose(x_start, y_start, theta_start, x_goal, y_goal, theta_goal): - x = x_start - y = y_start - theta = theta_start - - x_diff = x_goal - x - y_diff = y_goal - y - - x_traj, y_traj, v_traj, w_traj = [x], [y], [0], [0] - - rho = np.hypot(x_diff, y_diff) - t = 0 - while rho > 0.001 and t < MAX_SIM_TIME: - t += dt - x_traj.append(x) - y_traj.append(y) - - x_diff = x_goal - x - y_diff = y_goal - y - - rho, v, w = controller.calc_control_command(x_diff, y_diff, theta, theta_goal) - - if abs(v) > MAX_LINEAR_SPEED: - v = np.sign(v) * MAX_LINEAR_SPEED - - if abs(w) > MAX_ANGULAR_SPEED: - w = np.sign(w) * MAX_ANGULAR_SPEED - - v_traj.append(v) - w_traj.append(w) - - theta = theta + w * dt - x = x + v * np.cos(theta) * dt - y = y + v * np.sin(theta) * dt - - if show_animation: # pragma: no cover - plt.cla() - plt.arrow( - x_start, - y_start, - np.cos(theta_start), - np.sin(theta_start), - color="r", - width=0.1, - ) - plt.arrow( - x_goal, - y_goal, - np.cos(theta_goal), - np.sin(theta_goal), - color="g", - width=0.1, - ) - plot_vehicle(x, y, theta, x_traj, y_traj) - - return x_traj, y_traj, v_traj, w_traj - - -def plot_vehicle(x, y, theta, x_traj, y_traj): # pragma: no cover - # Corners of triangular vehicle when pointing to the right (0 radians) - p1_i = np.array([0.5, 0, 1]).T - p2_i = np.array([-0.5, 0.25, 1]).T - p3_i = np.array([-0.5, -0.25, 1]).T - - T = transformation_matrix(x, y, theta) - p1 = np.matmul(T, p1_i) - p2 = np.matmul(T, p2_i) - p3 = np.matmul(T, p3_i) - - plt.plot([p1[0], p2[0]], [p1[1], p2[1]], "k-") - plt.plot([p2[0], p3[0]], [p2[1], p3[1]], "k-") - plt.plot([p3[0], p1[0]], [p3[1], p1[1]], "k-") - - plt.plot(x_traj, y_traj, "b--") - - # 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] - ) - - plt.xlim(0, 20) - plt.ylim(0, 20) - - plt.pause(dt) - - -def transformation_matrix(x, y, theta): - return np.array( - [ - [np.cos(theta), -np.sin(theta), x], - [np.sin(theta), np.cos(theta), y], - [0, 0, 1], - ] - ) - - -def main(): - for i in range(5): - x_start = 20.0 * random() - y_start = 20.0 * random() - theta_start: float = 2 * np.pi * random() - np.pi - x_goal = 20 * random() - y_goal = 20 * random() - theta_goal = 2 * np.pi * random() - np.pi - print( - f"Initial x: {round(x_start, 2)} m\nInitial y: {round(y_start, 2)} m\nInitial theta: {round(theta_start, 2)} rad\n" - ) - print( - f"Goal x: {round(x_goal, 2)} m\nGoal y: {round(y_goal, 2)} m\nGoal theta: {round(theta_goal, 2)} rad\n" - ) - move_to_pose(x_start, y_start, theta_start, x_goal, y_goal, theta_goal) - - -if __name__ == "__main__": - main() diff --git a/PathTracking/move_to_pose/move_to_pose_robot.py b/PathTracking/move_to_pose/move_to_pose_robot.py deleted file mode 100644 index fe9f0d06b3..0000000000 --- a/PathTracking/move_to_pose/move_to_pose_robot.py +++ /dev/null @@ -1,240 +0,0 @@ -""" - -Move to specified pose (with Robot class) - -Author: Daniel Ingram (daniel-s-ingram) - Atsushi Sakai (@Atsushi_twi) - Seied Muhammad Yazdian (@Muhammad-Yazdian) - -P.I. Corke, "Robotics, Vision & Control", Springer 2017, ISBN 978-3-319-54413-7 - -""" - -import matplotlib.pyplot as plt -import numpy as np -import copy -from move_to_pose import PathFinderController - -# Simulation parameters -TIME_DURATION = 1000 -TIME_STEP = 0.01 -AT_TARGET_ACCEPTANCE_THRESHOLD = 0.01 -SHOW_ANIMATION = True -PLOT_WINDOW_SIZE_X = 20 -PLOT_WINDOW_SIZE_Y = 20 -PLOT_FONT_SIZE = 8 - -simulation_running = True -all_robots_are_at_target = False - - -class Pose: - """2D pose""" - - def __init__(self, x, y, theta): - self.x = x - self.y = y - self.theta = theta - - -class Robot: - """ - Constructs an instantiate of the 3-DOF wheeled Robot navigating on a - 2D plane - - Parameters - ---------- - name : (string) - The name of the robot - color : (string) - The color of the robot - max_linear_speed : (float) - The maximum linear speed that the robot can go - max_angular_speed : (float) - The maximum angular speed that the robot can rotate about its vertical - axis - path_finder_controller : (PathFinderController) - A configurable controller to finds the path and calculates command - linear and angular velocities. - """ - - def __init__(self, name, color, max_linear_speed, max_angular_speed, - path_finder_controller): - self.name = name - self.color = color - self.MAX_LINEAR_SPEED = max_linear_speed - self.MAX_ANGULAR_SPEED = max_angular_speed - self.path_finder_controller = path_finder_controller - self.x_traj = [] - self.y_traj = [] - self.pose = Pose(0, 0, 0) - self.pose_start = Pose(0, 0, 0) - self.pose_target = Pose(0, 0, 0) - self.is_at_target = False - - def set_start_target_poses(self, pose_start, pose_target): - """ - Sets the start and target positions of the robot - - Parameters - ---------- - pose_start : (Pose) - Start position of the robot (see the Pose class) - pose_target : (Pose) - Target position of the robot (see the Pose class) - """ - self.pose_start = copy.copy(pose_start) - self.pose_target = pose_target - self.pose = pose_start - - def move(self, dt): - """ - Moves the robot for one time step increment - - Parameters - ---------- - dt : (float) - time step - """ - self.x_traj.append(self.pose.x) - self.y_traj.append(self.pose.y) - - rho, linear_velocity, angular_velocity = \ - self.path_finder_controller.calc_control_command( - self.pose_target.x - self.pose.x, - self.pose_target.y - self.pose.y, - self.pose.theta, self.pose_target.theta) - - if rho < AT_TARGET_ACCEPTANCE_THRESHOLD: - self.is_at_target = True - - if abs(linear_velocity) > self.MAX_LINEAR_SPEED: - linear_velocity = (np.sign(linear_velocity) - * self.MAX_LINEAR_SPEED) - - if abs(angular_velocity) > self.MAX_ANGULAR_SPEED: - angular_velocity = (np.sign(angular_velocity) - * self.MAX_ANGULAR_SPEED) - - self.pose.theta = self.pose.theta + angular_velocity * dt - self.pose.x = self.pose.x + linear_velocity * \ - np.cos(self.pose.theta) * dt - self.pose.y = self.pose.y + linear_velocity * \ - np.sin(self.pose.theta) * dt - - -def run_simulation(robots): - """Simulates all robots simultaneously""" - global all_robots_are_at_target - global simulation_running - - robot_names = [] - for instance in robots: - robot_names.append(instance.name) - - time = 0 - while simulation_running and time < TIME_DURATION: - time += TIME_STEP - robots_are_at_target = [] - - for instance in robots: - if not instance.is_at_target: - instance.move(TIME_STEP) - robots_are_at_target.append(instance.is_at_target) - - if all(robots_are_at_target): - simulation_running = False - - if SHOW_ANIMATION: - plt.cla() - plt.xlim(0, PLOT_WINDOW_SIZE_X) - plt.ylim(0, PLOT_WINDOW_SIZE_Y) - - # 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]) - - plt.text(0.3, PLOT_WINDOW_SIZE_Y - 1, - 'Time: {:.2f}'.format(time), - fontsize=PLOT_FONT_SIZE) - - plt.text(0.3, PLOT_WINDOW_SIZE_Y - 2, - 'Reached target: {} = '.format(robot_names) - + str(robots_are_at_target), - fontsize=PLOT_FONT_SIZE) - - for instance in robots: - plt.arrow(instance.pose_start.x, - instance.pose_start.y, - np.cos(instance.pose_start.theta), - np.sin(instance.pose_start.theta), - color='r', - width=0.1) - plt.arrow(instance.pose_target.x, - instance.pose_target.y, - np.cos(instance.pose_target.theta), - np.sin(instance.pose_target.theta), - color='g', - width=0.1) - plot_vehicle(instance.pose.x, - instance.pose.y, - instance.pose.theta, - instance.x_traj, - instance.y_traj, instance.color) - - plt.pause(TIME_STEP) - - -def plot_vehicle(x, y, theta, x_traj, y_traj, color): - # Corners of triangular vehicle when pointing to the right (0 radians) - p1_i = np.array([0.5, 0, 1]).T - p2_i = np.array([-0.5, 0.25, 1]).T - p3_i = np.array([-0.5, -0.25, 1]).T - - T = transformation_matrix(x, y, theta) - p1 = T @ p1_i - p2 = T @ p2_i - p3 = T @ p3_i - - plt.plot([p1[0], p2[0]], [p1[1], p2[1]], color+'-') - plt.plot([p2[0], p3[0]], [p2[1], p3[1]], color+'-') - plt.plot([p3[0], p1[0]], [p3[1], p1[1]], color+'-') - - plt.plot(x_traj, y_traj, color+'--') - - -def transformation_matrix(x, y, theta): - return np.array([ - [np.cos(theta), -np.sin(theta), x], - [np.sin(theta), np.cos(theta), y], - [0, 0, 1] - ]) - - -def main(): - pose_target = Pose(15, 15, -1) - - pose_start_1 = Pose(5, 2, 0) - pose_start_2 = Pose(5, 2, 0) - pose_start_3 = Pose(5, 2, 0) - - controller_1 = PathFinderController(5, 8, 2) - controller_2 = PathFinderController(5, 16, 4) - controller_3 = PathFinderController(10, 25, 6) - - robot_1 = Robot("Yellow Robot", "y", 12, 5, controller_1) - robot_2 = Robot("Black Robot", "k", 16, 5, controller_2) - robot_3 = Robot("Blue Robot", "b", 20, 5, controller_3) - - robot_1.set_start_target_poses(pose_start_1, pose_target) - robot_2.set_start_target_poses(pose_start_2, pose_target) - robot_3.set_start_target_poses(pose_start_3, pose_target) - - robots: list[Robot] = [robot_1, robot_2, robot_3] - - run_simulation(robots) - - -if __name__ == '__main__': - main() diff --git a/PathTracking/pure_pursuit/pure_pursuit.py b/PathTracking/pure_pursuit/pure_pursuit.py deleted file mode 100644 index 48453927ab..0000000000 --- a/PathTracking/pure_pursuit/pure_pursuit.py +++ /dev/null @@ -1,349 +0,0 @@ -""" - -Path tracking simulation with pure pursuit steering and PID speed control. - -author: Atsushi Sakai (@Atsushi_twi) - Guillaume Jacquenot (@Gjacquenot) - -""" -import numpy as np -import math -import matplotlib.pyplot as plt - -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod - -# Parameters -k = 0.1 # look forward gain -Lfc = 2.0 # [m] look-ahead distance -Kp = 1.0 # speed proportional gain -dt = 0.1 # [s] time tick -WB = 2.9 # [m] wheel base of vehicle - - -# Vehicle parameters -LENGTH = WB + 1.0 # Vehicle length -WIDTH = 2.0 # Vehicle width -WHEEL_LEN = 0.6 # Wheel length -WHEEL_WIDTH = 0.2 # Wheel width -MAX_STEER = math.pi / 4 # Maximum steering angle [rad] - - -show_animation = True -pause_simulation = False # Flag for pause simulation -is_reverse_mode = False # Flag for reverse driving mode - -class State: - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0, is_reverse=False): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - self.direction = -1 if is_reverse else 1 # Direction based on reverse flag - self.rear_x = self.x - self.direction * ((WB / 2) * math.cos(self.yaw)) - self.rear_y = self.y - self.direction * ((WB / 2) * math.sin(self.yaw)) - - def update(self, a, delta): - self.x += self.v * math.cos(self.yaw) * dt - self.y += self.v * math.sin(self.yaw) * dt - self.yaw += self.direction * self.v / WB * math.tan(delta) * dt - self.yaw = angle_mod(self.yaw) - self.v += a * dt - self.rear_x = self.x - self.direction * ((WB / 2) * math.cos(self.yaw)) - self.rear_y = self.y - self.direction * ((WB / 2) * math.sin(self.yaw)) - - def calc_distance(self, point_x, point_y): - dx = self.rear_x - point_x - dy = self.rear_y - point_y - return math.hypot(dx, dy) - - -class States: - - def __init__(self): - self.x = [] - self.y = [] - self.yaw = [] - self.v = [] - self.direction = [] - self.t = [] - - def append(self, t, state): - self.x.append(state.x) - self.y.append(state.y) - self.yaw.append(state.yaw) - self.v.append(state.v) - self.direction.append(state.direction) - self.t.append(t) - - -def proportional_control(target, current): - a = Kp * (target - current) - - return a - - -class TargetCourse: - - def __init__(self, cx, cy): - self.cx = cx - self.cy = cy - self.old_nearest_point_index = None - - def search_target_index(self, state): - - # To speed up nearest point search, doing it at only first time. - if self.old_nearest_point_index is None: - # search nearest point index - dx = [state.rear_x - icx for icx in self.cx] - dy = [state.rear_y - icy for icy in self.cy] - d = np.hypot(dx, dy) - ind = np.argmin(d) - self.old_nearest_point_index = ind - else: - ind = self.old_nearest_point_index - distance_this_index = state.calc_distance(self.cx[ind], - self.cy[ind]) - while True: - distance_next_index = state.calc_distance(self.cx[ind + 1], - self.cy[ind + 1]) - if distance_this_index < distance_next_index: - break - ind = ind + 1 if (ind + 1) < len(self.cx) else ind - distance_this_index = distance_next_index - self.old_nearest_point_index = ind - - Lf = k * state.v + Lfc # update look ahead distance - - # search look ahead target point index - while Lf > state.calc_distance(self.cx[ind], self.cy[ind]): - if (ind + 1) >= len(self.cx): - break # not exceed goal - ind += 1 - - return ind, Lf - - -def pure_pursuit_steer_control(state, trajectory, pind): - ind, Lf = trajectory.search_target_index(state) - - if pind >= ind: - ind = pind - - if ind < len(trajectory.cx): - tx = trajectory.cx[ind] - ty = trajectory.cy[ind] - else: - tx = trajectory.cx[-1] - ty = trajectory.cy[-1] - ind = len(trajectory.cx) - 1 - - alpha = math.atan2(ty - state.rear_y, tx - state.rear_x) - state.yaw - - # Reverse steering angle when reversing - delta = state.direction * math.atan2(2.0 * WB * math.sin(alpha) / Lf, 1.0) - - # Limit steering angle to max value - delta = np.clip(delta, -MAX_STEER, MAX_STEER) - - return delta, ind - - -def plot_arrow(x, y, yaw, length=1.0, 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_vehicle(x, y, yaw, steer=0.0, color='blue', is_reverse=False): - """ - Plot vehicle model with four wheels - Args: - x, y: Vehicle center position - yaw: Vehicle heading angle - steer: Steering angle - color: Vehicle color - is_reverse: Flag for reverse mode - """ - # Adjust heading angle in reverse mode - if is_reverse: - yaw = angle_mod(yaw + math.pi) # Rotate heading by 180 degrees - steer = -steer # Reverse steering direction - - def plot_wheel(x, y, yaw, steer=0.0, color=color): - """Plot single wheel""" - wheel = np.array([ - [-WHEEL_LEN/2, WHEEL_WIDTH/2], - [WHEEL_LEN/2, WHEEL_WIDTH/2], - [WHEEL_LEN/2, -WHEEL_WIDTH/2], - [-WHEEL_LEN/2, -WHEEL_WIDTH/2], - [-WHEEL_LEN/2, WHEEL_WIDTH/2] - ]) - - # Rotate wheel if steering - if steer != 0: - c, s = np.cos(steer), np.sin(steer) - rot_steer = np.array([[c, -s], [s, c]]) - wheel = wheel @ rot_steer.T - - # Apply vehicle heading rotation - c, s = np.cos(yaw), np.sin(yaw) - rot_yaw = np.array([[c, -s], [s, c]]) - wheel = wheel @ rot_yaw.T - - # Translate to position - wheel[:, 0] += x - wheel[:, 1] += y - - # Plot wheel with color - plt.plot(wheel[:, 0], wheel[:, 1], color=color) - - # Calculate vehicle body corners - corners = np.array([ - [-LENGTH/2, WIDTH/2], - [LENGTH/2, WIDTH/2], - [LENGTH/2, -WIDTH/2], - [-LENGTH/2, -WIDTH/2], - [-LENGTH/2, WIDTH/2] - ]) - - # Rotation matrix - c, s = np.cos(yaw), np.sin(yaw) - Rot = np.array([[c, -s], [s, c]]) - - # Rotate and translate vehicle body - rotated = corners @ Rot.T - rotated[:, 0] += x - rotated[:, 1] += y - - # Plot vehicle body - plt.plot(rotated[:, 0], rotated[:, 1], color=color) - - # Plot wheels (darker color for front wheels) - front_color = 'darkblue' - rear_color = color - - # Plot four wheels - # Front left - plot_wheel(x + LENGTH/4 * c - WIDTH/2 * s, - y + LENGTH/4 * s + WIDTH/2 * c, - yaw, steer, front_color) - # Front right - plot_wheel(x + LENGTH/4 * c + WIDTH/2 * s, - y + LENGTH/4 * s - WIDTH/2 * c, - yaw, steer, front_color) - # Rear left - plot_wheel(x - LENGTH/4 * c - WIDTH/2 * s, - y - LENGTH/4 * s + WIDTH/2 * c, - yaw, color=rear_color) - # Rear right - plot_wheel(x - LENGTH/4 * c + WIDTH/2 * s, - y - LENGTH/4 * s - WIDTH/2 * c, - yaw, color=rear_color) - - # Add direction arrow - arrow_length = LENGTH/3 - plt.arrow(x, y, - -arrow_length * math.cos(yaw) if is_reverse else arrow_length * math.cos(yaw), - -arrow_length * math.sin(yaw) if is_reverse else arrow_length * math.sin(yaw), - head_width=WIDTH/4, head_length=WIDTH/4, - fc='r', ec='r', alpha=0.5) - -# Keyboard event handler -def on_key(event): - global pause_simulation - if event.key == ' ': # Space key - pause_simulation = not pause_simulation - elif event.key == 'escape': - exit(0) - -def main(): - # target course - cx = -1 * np.arange(0, 50, 0.5) if is_reverse_mode else np.arange(0, 50, 0.5) - cy = [math.sin(ix / 5.0) * ix / 2.0 for ix in cx] - - target_speed = 10.0 / 3.6 # [m/s] - - T = 100.0 # max simulation time - - # initial state - state = State(x=-0.0, y=-3.0, yaw=math.pi if is_reverse_mode else 0.0, v=0.0, is_reverse=is_reverse_mode) - - lastIndex = len(cx) - 1 - time = 0.0 - states = States() - states.append(time, state) - target_course = TargetCourse(cx, cy) - target_ind, _ = target_course.search_target_index(state) - - while T >= time and lastIndex > target_ind: - - # Calc control input - ai = proportional_control(target_speed, state.v) - di, target_ind = pure_pursuit_steer_control( - state, target_course, target_ind) - - state.update(ai, di) # Control vehicle - - time += dt - states.append(time, state) - if show_animation: # pragma: no cover - plt.cla() - # for stopping simulation with the esc key. - plt.gcf().canvas.mpl_connect('key_release_event', on_key) - # Pass is_reverse parameter - plot_vehicle(state.x, state.y, state.yaw, di, is_reverse=is_reverse_mode) - plt.plot(cx, cy, "-r", label="course") - plt.plot(states.x, states.y, "-b", label="trajectory") - plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title("Speed[km/h]:" + str(state.v * 3.6)[:4]) - plt.legend() # Add legend display - - # Add pause state display - if pause_simulation: - plt.text(0.02, 0.95, 'PAUSED', transform=plt.gca().transAxes, - bbox=dict(facecolor='red', alpha=0.5)) - - plt.pause(0.001) - - # Handle pause state - while pause_simulation: - plt.pause(0.1) # Reduce CPU usage - if not plt.get_fignums(): # Check if window is closed - exit(0) - - # Test - assert lastIndex >= target_ind, "Cannot goal" - - if show_animation: # pragma: no cover - plt.cla() - plt.plot(cx, cy, ".r", label="course") - plt.plot(states.x, states.y, "-b", label="trajectory") - plt.legend() - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.axis("equal") - plt.grid(True) - - plt.subplots(1) - plt.plot(states.t, [iv * 3.6 for iv in states.v], "-r") - plt.xlabel("Time[s]") - plt.ylabel("Speed[km/h]") - plt.grid(True) - plt.show() - - -if __name__ == '__main__': - print("Pure pursuit path tracking simulation start") - main() diff --git a/PathTracking/rear_wheel_feedback_control/rear_wheel_feedback_control.py b/PathTracking/rear_wheel_feedback_control/rear_wheel_feedback_control.py deleted file mode 100644 index fd04fb6d17..0000000000 --- a/PathTracking/rear_wheel_feedback_control/rear_wheel_feedback_control.py +++ /dev/null @@ -1,234 +0,0 @@ -""" - -Path tracking simulation with rear wheel feedback steering control and PID speed control. - -author: Atsushi Sakai(@Atsushi_twi) - -""" -import matplotlib.pyplot as plt -import math -import numpy as np -import sys -import pathlib -from scipy import interpolate -from scipy import optimize - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod - -Kp = 1.0 # speed proportional gain -# steering control parameter -KTH = 1.0 -KE = 0.5 - -dt = 0.1 # [s] -L = 2.9 # [m] - -show_animation = True - -class State: - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0, direction=1): - self.x = x - self.y = y - self.yaw = yaw - self.v = v - self.direction = direction - - def update(self, a, delta, dt): - self.x = self.x + self.v * math.cos(self.yaw) * dt - self.y = self.y + self.v * math.sin(self.yaw) * dt - self.yaw = self.yaw + self.v / L * math.tan(delta) * dt - self.v = self.v + a * dt - -class CubicSplinePath: - def __init__(self, x, y): - x, y = map(np.asarray, (x, y)) - s = np.append([0],(np.cumsum(np.diff(x)**2) + np.cumsum(np.diff(y)**2))**0.5) - - self.X = interpolate.CubicSpline(s, x) - self.Y = interpolate.CubicSpline(s, y) - - self.dX = self.X.derivative(1) - self.ddX = self.X.derivative(2) - - self.dY = self.Y.derivative(1) - self.ddY = self.Y.derivative(2) - - self.length = s[-1] - - def calc_yaw(self, s): - dx, dy = self.dX(s), self.dY(s) - return np.arctan2(dy, dx) - - def calc_curvature(self, s): - dx, dy = self.dX(s), self.dY(s) - ddx, ddy = self.ddX(s), self.ddY(s) - return (ddy * dx - ddx * dy) / ((dx ** 2 + dy ** 2)**(3 / 2)) - - def __find_nearest_point(self, s0, x, y): - def calc_distance(_s, *args): - _x, _y= self.X(_s), self.Y(_s) - return (_x - args[0])**2 + (_y - args[1])**2 - - def calc_distance_jacobian(_s, *args): - _x, _y = self.X(_s), self.Y(_s) - _dx, _dy = self.dX(_s), self.dY(_s) - return 2*_dx*(_x - args[0])+2*_dy*(_y-args[1]) - - minimum = optimize.fmin_cg(calc_distance, s0, calc_distance_jacobian, args=(x, y), full_output=True, disp=False) - return minimum - - def calc_track_error(self, x, y, s0): - ret = self.__find_nearest_point(s0, x, y) - - s = ret[0][0] - e = ret[1] - - k = self.calc_curvature(s) - yaw = self.calc_yaw(s) - - dxl = self.X(s) - x - dyl = self.Y(s) - y - angle = pi_2_pi(yaw - math.atan2(dyl, dxl)) - if angle < 0: - e*= -1 - - return e, k, yaw, s - -def pid_control(target, current): - a = Kp * (target - current) - return a - -def pi_2_pi(angle): - return angle_mod(angle) - -def rear_wheel_feedback_control(state, e, k, yaw_ref): - v = state.v - th_e = pi_2_pi(state.yaw - yaw_ref) - - omega = v * k * math.cos(th_e) / (1.0 - k * e) - \ - KTH * abs(v) * th_e - KE * v * math.sin(th_e) * e / th_e - - if th_e == 0.0 or omega == 0.0: - return 0.0 - - delta = math.atan2(L * omega / v, 1.0) - - return delta - - -def simulate(path_ref, goal): - T = 500.0 # max simulation time - goal_dis = 0.3 - - state = State(x=-0.0, y=-0.0, yaw=0.0, v=0.0) - - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - goal_flag = False - - s = np.arange(0, path_ref.length, 0.1) - e, k, yaw_ref, s0 = path_ref.calc_track_error(state.x, state.y, 0.0) - - while T >= time: - e, k, yaw_ref, s0 = path_ref.calc_track_error(state.x, state.y, s0) - di = rear_wheel_feedback_control(state, e, k, yaw_ref) - - speed_ref = calc_target_speed(state, yaw_ref) - ai = pid_control(speed_ref, state.v) - state.update(ai, di, dt) - - time = time + dt - - # check goal - dx = state.x - goal[0] - dy = state.y - goal[1] - if math.hypot(dx, dy) <= goal_dis: - print("Goal") - goal_flag = True - break - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - - if show_animation: - 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]) - plt.plot(path_ref.X(s), path_ref.Y(s), "-r", label="course") - plt.plot(x, y, "ob", label="trajectory") - plt.plot(path_ref.X(s0), path_ref.Y(s0), "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title(f"speed[km/h]:{round(state.v * 3.6, 2):.2f}, target s-param:{s0:.2f}") - plt.pause(0.0001) - - return t, x, y, yaw, v, goal_flag - -def calc_target_speed(state, yaw_ref): - target_speed = 10.0 / 3.6 - - dyaw = yaw_ref - state.yaw - switch = math.pi / 4.0 <= dyaw < math.pi / 2.0 - - if switch: - state.direction *= -1 - return 0.0 - - if state.direction != 1: - return -target_speed - - return target_speed - -def main(): - print("rear wheel feedback tracking start!!") - ax = [0.0, 6.0, 12.5, 5.0, 7.5, 3.0, -1.0] - ay = [0.0, 0.0, 5.0, 6.5, 3.0, 5.0, -2.0] - goal = [ax[-1], ay[-1]] - - reference_path = CubicSplinePath(ax, ay) - s = np.arange(0, reference_path.length, 0.1) - - t, x, y, yaw, v, goal_flag = simulate(reference_path, goal) - - # Test - assert goal_flag, "Cannot goal" - - if show_animation: # pragma: no cover - plt.close() - plt.subplots(1) - plt.plot(ax, ay, "xb", label="input") - plt.plot(reference_path.X(s), reference_path.Y(s), "-r", label="spline") - plt.plot(x, y, "-g", label="tracking") - plt.grid(True) - plt.axis("equal") - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.legend() - - plt.subplots(1) - plt.plot(s, np.rad2deg(reference_path.calc_yaw(s)), "-r", label="yaw") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("yaw angle[deg]") - - plt.subplots(1) - plt.plot(s, reference_path.calc_curvature(s), "-r", label="curvature") - plt.grid(True) - plt.legend() - plt.xlabel("line length[m]") - plt.ylabel("curvature [1/m]") - - plt.show() - -if __name__ == '__main__': - main() diff --git a/PathTracking/stanley_control/stanley_control.py b/PathTracking/stanley_control/stanley_control.py deleted file mode 100644 index 01c2ec0229..0000000000 --- a/PathTracking/stanley_control/stanley_control.py +++ /dev/null @@ -1,212 +0,0 @@ -""" - -Path tracking simulation with Stanley steering control and PID speed control. - -author: Atsushi Sakai (@Atsushi_twi) - -Reference: - - [Stanley: The robot that won the DARPA grand challenge](http://isl.ecst.csuchico.edu/DOCS/darpa2005/DARPA%202005%20Stanley.pdf) - - [Autonomous Automobile Path Tracking](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) - -""" -import numpy as np -import matplotlib.pyplot as plt -import sys -import pathlib - -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) -from utils.angle import angle_mod -from PathPlanning.CubicSpline import cubic_spline_planner - -k = 0.5 # control gain -Kp = 1.0 # speed proportional gain -dt = 0.1 # [s] time difference -L = 2.9 # [m] Wheel base of vehicle -max_steer = np.radians(30.0) # [rad] max steering angle - -show_animation = True - - -class State: - """ - Class representing the state of a vehicle. - - :param x: (float) x-coordinate - :param y: (float) y-coordinate - :param yaw: (float) yaw angle - :param v: (float) speed - """ - - def __init__(self, x=0.0, y=0.0, yaw=0.0, v=0.0): - """Instantiate the object.""" - super().__init__() - self.x = x - self.y = y - self.yaw = yaw - self.v = v - - def update(self, acceleration, delta): - """ - Update the state of the vehicle. - - Stanley Control uses bicycle model. - - :param acceleration: (float) Acceleration - :param delta: (float) Steering - """ - delta = np.clip(delta, -max_steer, max_steer) - - self.x += self.v * np.cos(self.yaw) * dt - self.y += self.v * np.sin(self.yaw) * dt - self.yaw += self.v / L * np.tan(delta) * dt - self.yaw = normalize_angle(self.yaw) - self.v += acceleration * dt - - -def pid_control(target, current): - """ - Proportional control for the speed. - - :param target: (float) - :param current: (float) - :return: (float) - """ - return Kp * (target - current) - - -def stanley_control(state, cx, cy, cyaw, last_target_idx): - """ - Stanley steering control. - - :param state: (State object) - :param cx: ([float]) - :param cy: ([float]) - :param cyaw: ([float]) - :param last_target_idx: (int) - :return: (float, int) - """ - current_target_idx, error_front_axle = calc_target_index(state, cx, cy) - - if last_target_idx >= current_target_idx: - current_target_idx = last_target_idx - - # theta_e corrects the heading error - theta_e = normalize_angle(cyaw[current_target_idx] - state.yaw) - # theta_d corrects the cross track error - theta_d = np.arctan2(k * error_front_axle, state.v) - # Steering control - delta = theta_e + theta_d - - return delta, current_target_idx - - -def normalize_angle(angle): - """ - Normalize an angle to [-pi, pi]. - - :param angle: (float) - :return: (float) Angle in radian in [-pi, pi] - """ - return angle_mod(angle) - - -def calc_target_index(state, cx, cy): - """ - Compute index in the trajectory list of the target. - - :param state: (State object) - :param cx: [float] - :param cy: [float] - :return: (int, float) - """ - # Calc front axle position - fx = state.x + L * np.cos(state.yaw) - fy = state.y + L * np.sin(state.yaw) - - # Search nearest point index - dx = [fx - icx for icx in cx] - dy = [fy - icy for icy in cy] - d = np.hypot(dx, dy) - target_idx = np.argmin(d) - - # Project RMS error onto front axle vector - front_axle_vec = [-np.cos(state.yaw + np.pi / 2), - -np.sin(state.yaw + np.pi / 2)] - error_front_axle = np.dot([dx[target_idx], dy[target_idx]], front_axle_vec) - - return target_idx, error_front_axle - - -def main(): - """Plot an example of Stanley steering control on a cubic spline.""" - # target course - ax = [0.0, 100.0, 100.0, 50.0, 60.0] - ay = [0.0, 0.0, -30.0, -20.0, 0.0] - - cx, cy, cyaw, ck, s = cubic_spline_planner.calc_spline_course( - ax, ay, ds=0.1) - - target_speed = 30.0 / 3.6 # [m/s] - - max_simulation_time = 100.0 - - # Initial state - state = State(x=-0.0, y=5.0, yaw=np.radians(20.0), v=0.0) - - last_idx = len(cx) - 1 - time = 0.0 - x = [state.x] - y = [state.y] - yaw = [state.yaw] - v = [state.v] - t = [0.0] - target_idx, _ = calc_target_index(state, cx, cy) - - while max_simulation_time >= time and last_idx > target_idx: - ai = pid_control(target_speed, state.v) - di, target_idx = stanley_control(state, cx, cy, cyaw, target_idx) - state.update(ai, di) - - time += dt - - x.append(state.x) - y.append(state.y) - yaw.append(state.yaw) - v.append(state.v) - t.append(time) - - if show_animation: # pragma: no cover - 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]) - plt.plot(cx, cy, ".r", label="course") - plt.plot(x, y, "-b", label="trajectory") - plt.plot(cx[target_idx], cy[target_idx], "xg", label="target") - plt.axis("equal") - plt.grid(True) - plt.title("Speed[km/h]:" + str(state.v * 3.6)[:4]) - plt.pause(0.001) - - # Test - assert last_idx >= target_idx, "Cannot reach goal" - - if show_animation: # pragma: no cover - plt.plot(cx, cy, ".r", label="course") - plt.plot(x, y, "-b", label="trajectory") - plt.legend() - plt.xlabel("x[m]") - plt.ylabel("y[m]") - plt.axis("equal") - plt.grid(True) - - plt.subplots(1) - plt.plot(t, [iv * 3.6 for iv in v], "-r") - plt.xlabel("Time[s]") - plt.ylabel("Speed[km/h]") - plt.grid(True) - plt.show() - - -if __name__ == '__main__': - main() diff --git a/README.md b/README.md deleted file mode 100644 index d1b801f219..0000000000 --- a/README.md +++ /dev/null @@ -1,658 +0,0 @@ -header pic - -# PythonRobotics -![GitHub_Action_Linux_CI](https://github.com/AtsushiSakai/PythonRobotics/workflows/Linux_CI/badge.svg) -![GitHub_Action_MacOS_CI](https://github.com/AtsushiSakai/PythonRobotics/workflows/MacOS_CI/badge.svg) -![GitHub_Action_Windows_CI](https://github.com/AtsushiSakai/PythonRobotics/workflows/Windows_CI/badge.svg) -[![Build status](https://ci.appveyor.com/api/projects/status/sb279kxuv1be391g?svg=true)](https://ci.appveyor.com/project/AtsushiSakai/pythonrobotics) - -Python codes and [textbook](https://atsushisakai.github.io/PythonRobotics/index.html) for robotics algorithm. - - -# Table of Contents - * [What is this?](#what-is-this) - * [Requirements](#requirements) - * [Documentation](#documentation) - * [How to use](#how-to-use) - * [Localization](#localization) - * [Extended Kalman Filter localization](#extended-kalman-filter-localization) - * [Particle filter localization](#particle-filter-localization) - * [Histogram filter localization](#histogram-filter-localization) - * [Mapping](#mapping) - * [Gaussian grid map](#gaussian-grid-map) - * [Ray casting grid map](#ray-casting-grid-map) - * [Lidar to grid map](#lidar-to-grid-map) - * [k-means object clustering](#k-means-object-clustering) - * [Rectangle fitting](#rectangle-fitting) - * [SLAM](#slam) - * [Iterative Closest Point (ICP) Matching](#iterative-closest-point-icp-matching) - * [FastSLAM 1.0](#fastslam-10) - * [Path Planning](#path-planning) - * [Dynamic Window Approach](#dynamic-window-approach) - * [Grid based search](#grid-based-search) - * [Dijkstra algorithm](#dijkstra-algorithm) - * [A* algorithm](#a-algorithm) - * [D* algorithm](#d-algorithm) - * [D* Lite algorithm](#d-lite-algorithm) - * [Potential Field algorithm](#potential-field-algorithm) - * [Grid based coverage path planning](#grid-based-coverage-path-planning) - * [State Lattice Planning](#state-lattice-planning) - * [Biased polar sampling](#biased-polar-sampling) - * [Lane sampling](#lane-sampling) - * [Probabilistic Road-Map (PRM) planning](#probabilistic-road-map-prm-planning) - * [Rapidly-Exploring Random Trees (RRT)](#rapidly-exploring-random-trees-rrt) - * [RRT*](#rrt) - * [RRT* with reeds-shepp path](#rrt-with-reeds-shepp-path) - * [LQR-RRT*](#lqr-rrt) - * [Quintic polynomials planning](#quintic-polynomials-planning) - * [Reeds Shepp planning](#reeds-shepp-planning) - * [LQR based path planning](#lqr-based-path-planning) - * [Optimal Trajectory in a Frenet Frame](#optimal-trajectory-in-a-frenet-frame) - * [Path Tracking](#path-tracking) - * [move to a pose control](#move-to-a-pose-control) - * [Stanley control](#stanley-control) - * [Rear wheel feedback control](#rear-wheel-feedback-control) - * [Linear–quadratic regulator (LQR) speed and steering control](#linearquadratic-regulator-lqr-speed-and-steering-control) - * [Model predictive speed and steering control](#model-predictive-speed-and-steering-control) - * [Nonlinear Model predictive control with C-GMRES](#nonlinear-model-predictive-control-with-c-gmres) - * [Arm Navigation](#arm-navigation) - * [N joint arm to point control](#n-joint-arm-to-point-control) - * [Arm navigation with obstacle avoidance](#arm-navigation-with-obstacle-avoidance) - * [Aerial Navigation](#aerial-navigation) - * [drone 3d trajectory following](#drone-3d-trajectory-following) - * [rocket powered landing](#rocket-powered-landing) - * [Bipedal](#bipedal) - * [bipedal planner with inverted pendulum](#bipedal-planner-with-inverted-pendulum) - * [License](#license) - * [Use-case](#use-case) - * [Contribution](#contribution) - * [Citing](#citing) - * [Support](#support) - * [Sponsors](#sponsors) - * [JetBrains](#JetBrains) - * [1Password](#1password) - * [Authors](#authors) - -# What is PythonRobotics? - -PythonRobotics is a Python code collection and a [textbook](https://atsushisakai.github.io/PythonRobotics/index.html) of robotics algorithms. - -Features: - -1. Easy to read for understanding each algorithm's basic idea. - -2. Widely used and practical algorithms are selected. - -3. Minimum dependency. - -See this documentation - -- [Getting Started — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/modules/0_getting_started/1_what_is_python_robotics.html) - -or this Youtube video: - -- [PythonRobotics project audio overview](https://www.youtube.com/watch?v=uMeRnNoJAfU) - -or this paper for more details: - -- [\[1808\.10703\] PythonRobotics: a Python code collection of robotics algorithms](https://arxiv.org/abs/1808.10703) ([BibTeX](https://github.com/AtsushiSakai/PythonRoboticsPaper/blob/master/python_robotics.bib)) - - -# Requirements to run the code - -For running each sample code: - -- [Python 3.13.x](https://www.python.org/) - -- [NumPy](https://numpy.org/) - -- [SciPy](https://scipy.org/) - -- [Matplotlib](https://matplotlib.org/) - -- [cvxpy](https://www.cvxpy.org/) - -For development: - -- [pytest](https://pytest.org/) (for unit tests) - -- [pytest-xdist](https://pypi.org/project/pytest-xdist/) (for parallel unit tests) - -- [mypy](https://mypy-lang.org/) (for type check) - -- [sphinx](https://www.sphinx-doc.org/) (for document generation) - -- [pycodestyle](https://pypi.org/project/pycodestyle/) (for code style check) - -# Documentation (Textbook) - -This README only shows some examples of this project. - -If you are interested in other examples or mathematical backgrounds of each algorithm, - -You can check the full documentation (textbook) online: [Welcome to PythonRobotics’s documentation\! — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/index.html) - -All animation gifs are stored here: [AtsushiSakai/PythonRoboticsGifs: Animation gifs of PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs) - -# How to use - -1. Clone this repo. - - ```terminal - git clone https://github.com/AtsushiSakai/PythonRobotics.git - ``` - - -2. Install the required libraries. - -- using conda : - - ```terminal - conda env create -f requirements/environment.yml - ``` - -- using pip : - - ```terminal - pip install -r requirements/requirements.txt - ``` - - -3. Execute python script in each directory. - -4. Add star to this repo if you like it :smiley:. - -# Localization - -## Extended Kalman Filter localization - -EKF pic - -Reference - -- [documentation](https://atsushisakai.github.io/PythonRobotics/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization.html) - -## Particle filter localization - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/particle_filter/animation.gif) - -This is a sensor fusion localization with Particle Filter(PF). - -The blue line is true trajectory, the black line is dead reckoning trajectory, - -and the red line is an estimated trajectory with PF. - -It is assumed that the robot can measure a distance from landmarks (RFID). - -These measurements are used for PF localization. - -Reference - -- [PROBABILISTIC ROBOTICS](http://www.probabilistic-robotics.org/) - - -## Histogram filter localization - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/histogram_filter/animation.gif) - -This is a 2D localization example with Histogram filter. - -The red cross is true position, black points are RFID positions. - -The blue grid shows a position probability of histogram filter. - -In this simulation, x,y are unknown, yaw is known. - -The filter integrates speed input and range observations from RFID for localization. - -Initial position is not needed. - -Reference - -- [PROBABILISTIC ROBOTICS](http://www.probabilistic-robotics.org/) - -# Mapping - -## Gaussian grid map - -This is a 2D Gaussian grid mapping example. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/gaussian_grid_map/animation.gif) - -## Ray casting grid map - -This is a 2D ray casting grid mapping example. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/raycasting_grid_map/animation.gif) - -## Lidar to grid map - -This example shows how to convert a 2D range measurement to a grid map. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/lidar_to_grid_map/animation.gif) - -## k-means object clustering - -This is a 2D object clustering with k-means algorithm. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/kmeans_clustering/animation.gif) - -## Rectangle fitting - -This is a 2D rectangle fitting for vehicle detection. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/rectangle_fitting/animation.gif) - - -# SLAM - -Simultaneous Localization and Mapping(SLAM) examples - -## Iterative Closest Point (ICP) Matching - -This is a 2D ICP matching example with singular value decomposition. - -It can calculate a rotation matrix, and a translation vector between points and points. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/iterative_closest_point/animation.gif) - -Reference - -- [Introduction to Mobile Robotics: Iterative Closest Point Algorithm](https://cs.gmu.edu/~kosecka/cs685/cs685-icp.pdf) - - -## FastSLAM 1.0 - -This is a feature based SLAM example using FastSLAM 1.0. - -The blue line is ground truth, the black line is dead reckoning, the red line is the estimated trajectory with FastSLAM. - -The red points are particles of FastSLAM. - -Black points are landmarks, blue crosses are estimated landmark positions by FastSLAM. - - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/FastSLAM1/animation.gif) - - -Reference - -- [PROBABILISTIC ROBOTICS](http://www.probabilistic-robotics.org/) - -- [SLAM simulations by Tim Bailey](http://www-personal.acfr.usyd.edu.au/tbailey/software/slam_simulations.htm) - - -# Path Planning - -## Dynamic Window Approach - -This is a 2D navigation sample code with Dynamic Window Approach. - -- [The Dynamic Window Approach to Collision Avoidance](https://www.ri.cmu.edu/pub_files/pub1/fox_dieter_1997_1/fox_dieter_1997_1.pdf) - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DynamicWindowApproach/animation.gif) - - -## Grid based search - -### Dijkstra algorithm - -This is a 2D grid based the shortest path planning with Dijkstra's algorithm. - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/Dijkstra/animation.gif) - -In the animation, cyan points are searched nodes. - -### A\* algorithm - -This is a 2D grid based the shortest path planning with A star algorithm. - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/AStar/animation.gif) - -In the animation, cyan points are searched nodes. - -Its heuristic is 2D Euclid distance. - -### D\* algorithm - -This is a 2D grid based the shortest path planning with D star algorithm. - -![figure at master · nirnayroy/intelligentrobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DStar/animation.gif) - -The animation shows a robot finding its path avoiding an obstacle using the D* search algorithm. - -Reference - -- [D* Algorithm Wikipedia](https://en.wikipedia.org/wiki/D*) - -### D\* Lite algorithm - -This algorithm finds the shortest path between two points while rerouting when obstacles are discovered. It has been implemented here for a 2D grid. - -![D* Lite](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DStarLite/animation.gif) - -The animation shows a robot finding its path and rerouting to avoid obstacles as they are discovered using the D* Lite search algorithm. - -Refs: - -- [D* Lite](http://idm-lab.org/bib/abstracts/papers/aaai02b.pdf) -- [Improved Fast Replanning for Robot Navigation in Unknown Terrain](http://www.cs.cmu.edu/~maxim/files/dlite_icra02.pdf) - -### Potential Field algorithm - -This is a 2D grid based path planning with Potential Field algorithm. - -![PotentialField](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/PotentialFieldPlanning/animation.gif) - -In the animation, the blue heat map shows potential value on each grid. - -Reference - -- [Robotic Motion Planning:Potential Functions](https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf) - -### Grid based coverage path planning - -This is a 2D grid based coverage path planning simulation. - -![PotentialField](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/GridBasedSweepCPP/animation.gif) - -## State Lattice Planning - -This script is a path planning code with state lattice planning. - -This code uses the model predictive trajectory generator to solve boundary problem. - -Reference - -- [Optimal rough terrain trajectory generation for wheeled mobile robots](https://journals.sagepub.com/doi/pdf/10.1177/0278364906075328) - -- [State Space Sampling of Feasible Motions for High-Performance Mobile Robot Navigation in Complex Environments](https://www.cs.cmu.edu/~alonzo/pubs/papers/JFR_08_SS_Sampling.pdf) - - -### Biased polar sampling - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/BiasedPolarSampling.gif) - - -### Lane sampling - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/LaneSampling.gif) - -## Probabilistic Road-Map (PRM) planning - -![PRM](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ProbabilisticRoadMap/animation.gif) - -This PRM planner uses Dijkstra method for graph search. - -In the animation, blue points are sampled points, - -Cyan crosses means searched points with Dijkstra method, - -The red line is the final path of PRM. - -Reference - -- [Probabilistic roadmap \- Wikipedia](https://en.wikipedia.org/wiki/Probabilistic_roadmap) - -   - -## Rapidly-Exploring Random Trees (RRT) - -### RRT\* - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTstar/animation.gif) - -This is a path planning code with RRT\* - -Black circles are obstacles, green line is a searched tree, red crosses are start and goal positions. - -Reference - -- [Incremental Sampling-based Algorithms for Optimal Motion Planning](https://arxiv.org/abs/1005.0416) - -- [Sampling-based Algorithms for Optimal Motion Planning](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=bddbc99f97173430aa49a0ada53ab5bade5902fa) - -### RRT\* with reeds-shepp path - -![Robotics/animation.gif at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTStarReedsShepp/animation.gif) - -Path planning for a car robot with RRT\* and reeds shepp path planner. - -### LQR-RRT\* - -This is a path planning simulation with LQR-RRT\*. - -A double integrator motion model is used for LQR local planner. - -![LQR_RRT](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/LQRRRTStar/animation.gif) - -Reference - -- [LQR\-RRT\*: Optimal Sampling\-Based Motion Planning with Automatically Derived Extension Heuristics](https://lis.csail.mit.edu/pubs/perez-icra12.pdf) - -- [MahanFathi/LQR\-RRTstar: LQR\-RRT\* method is used for random motion planning of a simple pendulum in its phase plot](https://github.com/MahanFathi/LQR-RRTstar) - - -## Quintic polynomials planning - -Motion planning with quintic polynomials. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/QuinticPolynomialsPlanner/animation.gif) - -It can calculate a 2D path, velocity, and acceleration profile based on quintic polynomials. - -Reference - -- [Local Path Planning And Motion Control For Agv In Positioning](https://ieeexplore.ieee.org/document/637936/) - -## Reeds Shepp planning - -A sample code with Reeds Shepp path planning. - -![RSPlanning](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ReedsSheppPath/animation.gif?raw=true) - -Reference - -- [15.3.2 Reeds\-Shepp Curves](http://planning.cs.uiuc.edu/node822.html) - -- [optimal paths for a car that goes both forwards and backwards](https://pdfs.semanticscholar.org/932e/c495b1d0018fd59dee12a0bf74434fac7af4.pdf) - -- [ghliu/pyReedsShepp: Implementation of Reeds Shepp curve\.](https://github.com/ghliu/pyReedsShepp) - - -## LQR based path planning - -A sample code using LQR based path planning for double integrator model. - -![RSPlanning](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/LQRPlanner/animation.gif?raw=true) - - -## Optimal Trajectory in a Frenet Frame - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/animation.gif) - -This is optimal trajectory generation in a Frenet Frame. - -The cyan line is the target course and black crosses are obstacles. - -The red line is the predicted path. - -Reference - -- [Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame](https://www.researchgate.net/profile/Moritz_Werling/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame/links/54f749df0cf210398e9277af.pdf) - -- [Optimal trajectory generation for dynamic street scenarios in a Frenet Frame](https://www.youtube.com/watch?v=Cj6tAQe7UCY) - - -# Path Tracking - -## move to a pose control - -This is a simulation of moving to a pose control - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/move_to_pose/animation.gif) - -Reference - -- [P. I. Corke, "Robotics, Vision and Control" \| SpringerLink p102](https://link.springer.com/book/10.1007/978-3-642-20144-8) - - -## Stanley control - -Path tracking simulation with Stanley steering control and PID speed control. - -![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/stanley_controller/animation.gif) - -Reference - -- [Stanley: The robot that won the DARPA grand challenge](http://robots.stanford.edu/papers/thrun.stanley05.pdf) - -- [Automatic Steering Methods for Autonomous Automobile Path Tracking](https://www.ri.cmu.edu/pub_files/2009/2/Automatic_Steering_Methods_for_Autonomous_Automobile_Path_Tracking.pdf) - - - -## Rear wheel feedback control - -Path tracking simulation with rear wheel feedback steering control and PID speed control. - -![PythonRobotics/figure_1.png at master · AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/rear_wheel_feedback/animation.gif) - -Reference - -- [A Survey of Motion Planning and Control Techniques for Self-driving Urban Vehicles](https://arxiv.org/abs/1604.07446) - - -## Linear–quadratic regulator (LQR) speed and steering control - -Path tracking simulation with LQR speed and steering control. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_speed_steer_control/animation.gif) - -Reference - -- [Towards fully autonomous driving: Systems and algorithms \- IEEE Conference Publication](https://ieeexplore.ieee.org/document/5940562/) - - -## Model predictive speed and steering control - -Path tracking simulation with iterative linear model predictive speed and steering control. - -MPC pic - -Reference - -- [documentation](https://atsushisakai.github.io/PythonRobotics/modules/6_path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control.html) - -- [Real\-time Model Predictive Control \(MPC\), ACADO, Python \| Work\-is\-Playing](http://grauonline.de/wordpress/?page_id=3244) - -## Nonlinear Model predictive control with C-GMRES - -A motion planning and path tracking simulation with NMPC of C-GMRES - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/cgmres_nmpc/animation.gif) - -Reference - -- [documentation](https://atsushisakai.github.io/PythonRobotics/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc.html) - - -# Arm Navigation - -## N joint arm to point control - -N joint arm to a point control simulation. - -This is an interactive simulation. - -You can set the goal position of the end effector with left-click on the plotting area. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/n_joint_arm_to_point_control/animation.gif) - -In this simulation N = 10, however, you can change it. - -## Arm navigation with obstacle avoidance - -Arm navigation with obstacle avoidance simulation. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/arm_obstacle_navigation/animation.gif) - - -# Aerial Navigation - -## drone 3d trajectory following - -This is a 3d trajectory following simulation for a quadrotor. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/AerialNavigation/drone_3d_trajectory_following/animation.gif) - -## rocket powered landing - -This is a 3d trajectory generation simulation for a rocket powered landing. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/AerialNavigation/rocket_powered_landing/animation.gif) - -Reference - -- [documentation](https://atsushisakai.github.io/PythonRobotics/modules/8_aerial_navigation/rocket_powered_landing/rocket_powered_landing.html) - -# Bipedal - -## bipedal planner with inverted pendulum - -This is a bipedal planner for modifying footsteps for an inverted pendulum. - -You can set the footsteps, and the planner will modify those automatically. - -![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Bipedal/bipedal_planner/animation.gif) - -# License - -MIT - -# Use-case - -If this project helps your robotics project, please let me know with creating an issue. - -Your robot's video, which is using PythonRobotics, is very welcome!! - -This is a list of user's comment and references:[users\_comments](https://github.com/AtsushiSakai/PythonRobotics/blob/master/users_comments.md) - -# Contribution - -Any contribution is welcome!! - -Please check this document:[How To Contribute — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/modules/0_getting_started/3_how_to_contribute.html) - -# Citing - -If you use this project's code for your academic work, we encourage you to cite [our papers](https://arxiv.org/abs/1808.10703) - -If you use this project's code in industry, we'd love to hear from you as well; feel free to reach out to the developers directly. - -# Supporting this project - -If you or your company would like to support this project, please consider: - -- [Sponsor @AtsushiSakai on GitHub Sponsors](https://github.com/sponsors/AtsushiSakai) - -- [Become a backer or sponsor on Patreon](https://www.patreon.com/myenigma) - -- [One-time donation via PayPal](https://www.paypal.com/paypalme/myenigmapay/) - -If you would like to support us in some other way, please contact with creating an issue. - -## Sponsors - -### [JetBrains](https://www.jetbrains.com/) - -They are providing a free license of their IDEs for this OSS development. - -### [1Password](https://github.com/1Password/for-open-source) - -They are providing a free license of their 1Password team license for this OSS project. - - -# Authors - -- [Contributors to AtsushiSakai/PythonRobotics](https://github.com/AtsushiSakai/PythonRobotics/graphs/contributors) - diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 53dcafa450..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy - -## Supported Versions - -In this project, we are only support latest code. - -| Version | Supported | -| ------- | ------------------ | -| latest | :white_check_mark: | - -## Reporting a Vulnerability - -If you find any security related problem, let us know by creating an issue about it. diff --git a/SLAM/EKFSLAM/ekf_slam.py b/SLAM/EKFSLAM/ekf_slam.py deleted file mode 100644 index 5c4417d7c3..0000000000 --- a/SLAM/EKFSLAM/ekf_slam.py +++ /dev/null @@ -1,263 +0,0 @@ -""" -Extended Kalman Filter SLAM example - -author: Atsushi Sakai (@Atsushi_twi) -""" - -import math - -import matplotlib.pyplot as plt -import numpy as np -from utils.angle import angle_mod - -# EKF state covariance -Cx = np.diag([0.5, 0.5, np.deg2rad(30.0)]) ** 2 - -# Simulation parameter -Q_sim = np.diag([0.2, np.deg2rad(1.0)]) ** 2 -R_sim = np.diag([1.0, np.deg2rad(10.0)]) ** 2 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] -MAX_RANGE = 20.0 # maximum observation range -M_DIST_TH = 2.0 # Threshold of Mahalanobis distance for data association. -STATE_SIZE = 3 # State size [x,y,yaw] -LM_SIZE = 2 # LM state size [x,y] - -show_animation = True - - -def ekf_slam(xEst, PEst, u, z): - # Predict - G, Fx = jacob_motion(xEst, u) - xEst[0:STATE_SIZE] = motion_model(xEst[0:STATE_SIZE], u) - PEst = G.T @ PEst @ G + Fx.T @ Cx @ Fx - initP = np.eye(2) - - # Update - for iz in range(len(z[:, 0])): # for each observation - min_id = search_correspond_landmark_id(xEst, PEst, z[iz, 0:2]) - - nLM = calc_n_lm(xEst) - if min_id == nLM: - print("New LM") - # Extend state and covariance matrix - xAug = np.vstack((xEst, calc_landmark_position(xEst, z[iz, :]))) - PAug = np.vstack((np.hstack((PEst, np.zeros((len(xEst), LM_SIZE)))), - np.hstack((np.zeros((LM_SIZE, len(xEst))), initP)))) - xEst = xAug - PEst = PAug - lm = get_landmark_position_from_state(xEst, min_id) - y, S, H = calc_innovation(lm, xEst, PEst, z[iz, 0:2], min_id) - - K = (PEst @ H.T) @ np.linalg.inv(S) - xEst = xEst + (K @ y) - PEst = (np.eye(len(xEst)) - (K @ H)) @ PEst - - xEst[2] = pi_2_pi(xEst[2]) - - return xEst, PEst - - -def calc_input(): - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - u = np.array([[v, yaw_rate]]).T - return u - - -def observation(xTrue, xd, u, RFID): - xTrue = motion_model(xTrue, u) - - # add noise to gps x-y - z = np.zeros((0, 3)) - - for i in range(len(RFID[:, 0])): - - dx = RFID[i, 0] - xTrue[0, 0] - dy = RFID[i, 1] - xTrue[1, 0] - d = math.hypot(dx, dy) - angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0]) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_sim[0, 0] ** 0.5 # add noise - angle_n = angle + np.random.randn() * Q_sim[1, 1] ** 0.5 # add noise - zi = np.array([dn, angle_n, i]) - z = np.vstack((z, zi)) - - # add noise to input - ud = np.array([[ - u[0, 0] + np.random.randn() * R_sim[0, 0] ** 0.5, - u[1, 0] + np.random.randn() * R_sim[1, 1] ** 0.5]]).T - - xd = motion_model(xd, ud) - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0], - [0, 1.0, 0], - [0, 0, 1.0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT]]) - - x = (F @ x) + (B @ u) - return x - - -def calc_n_lm(x): - n = int((len(x) - STATE_SIZE) / LM_SIZE) - return n - - -def jacob_motion(x, u): - Fx = np.hstack((np.eye(STATE_SIZE), np.zeros( - (STATE_SIZE, LM_SIZE * calc_n_lm(x))))) - - jF = np.array([[0.0, 0.0, -DT * u[0, 0] * math.sin(x[2, 0])], - [0.0, 0.0, DT * u[0, 0] * math.cos(x[2, 0])], - [0.0, 0.0, 0.0]], dtype=float) - - G = np.eye(len(x)) + Fx.T @ jF @ Fx - - return G, Fx, - - -def calc_landmark_position(x, z): - zp = np.zeros((2, 1)) - - zp[0, 0] = x[0, 0] + z[0] * math.cos(x[2, 0] + z[1]) - zp[1, 0] = x[1, 0] + z[0] * math.sin(x[2, 0] + z[1]) - - return zp - - -def get_landmark_position_from_state(x, ind): - lm = x[STATE_SIZE + LM_SIZE * ind: STATE_SIZE + LM_SIZE * (ind + 1), :] - - return lm - - -def search_correspond_landmark_id(xAug, PAug, zi): - """ - Landmark association with Mahalanobis distance - """ - - nLM = calc_n_lm(xAug) - - min_dist = [] - - for i in range(nLM): - lm = get_landmark_position_from_state(xAug, i) - y, S, H = calc_innovation(lm, xAug, PAug, zi, i) - min_dist.append(y.T @ np.linalg.inv(S) @ y) - - min_dist.append(M_DIST_TH) # new landmark - - min_id = min_dist.index(min(min_dist)) - - return min_id - - -def calc_innovation(lm, xEst, PEst, z, LMid): - delta = lm - xEst[0:2] - q = (delta.T @ delta)[0, 0] - z_angle = math.atan2(delta[1, 0], delta[0, 0]) - xEst[2, 0] - zp = np.array([[math.sqrt(q), pi_2_pi(z_angle)]]) - y = (z - zp).T - y[1] = pi_2_pi(y[1]) - H = jacob_h(q, delta, xEst, LMid + 1) - S = H @ PEst @ H.T + Cx[0:2, 0:2] - - return y, S, H - - -def jacob_h(q, delta, x, i): - sq = math.sqrt(q) - G = np.array([[-sq * delta[0, 0], - sq * delta[1, 0], 0, sq * delta[0, 0], sq * delta[1, 0]], - [delta[1, 0], - delta[0, 0], - q, - delta[1, 0], delta[0, 0]]]) - - G = G / q - nLM = calc_n_lm(x) - F1 = np.hstack((np.eye(3), np.zeros((3, 2 * nLM)))) - F2 = np.hstack((np.zeros((2, 3)), np.zeros((2, 2 * (i - 1))), - np.eye(2), np.zeros((2, 2 * nLM - 2 * i)))) - - F = np.vstack((F1, F2)) - - H = G @ F - - return H - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RFID positions [x, y] - RFID = np.array([[10.0, -2.0], - [15.0, 10.0], - [3.0, 15.0], - [-5.0, 20.0]]) - - # State Vector [x y yaw v]' - xEst = np.zeros((STATE_SIZE, 1)) - xTrue = np.zeros((STATE_SIZE, 1)) - PEst = np.eye(STATE_SIZE) - - xDR = np.zeros((STATE_SIZE, 1)) # Dead reckoning - - # history - hxEst = xEst - hxTrue = xTrue - hxDR = xTrue - - while SIM_TIME >= time: - time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) - - xEst, PEst = ekf_slam(xEst, PEst, ud, z) - - x_state = xEst[0:STATE_SIZE] - - # store data history - hxEst = np.hstack((hxEst, x_state)) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - - if show_animation: # pragma: no cover - 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]) - - plt.plot(RFID[:, 0], RFID[:, 1], "*k") - plt.plot(xEst[0], xEst[1], ".r") - - # plot landmark - for i in range(calc_n_lm(xEst)): - plt.plot(xEst[STATE_SIZE + i * 2], - xEst[STATE_SIZE + i * 2 + 1], "xg") - - plt.plot(hxTrue[0, :], - hxTrue[1, :], "-b") - plt.plot(hxDR[0, :], - hxDR[1, :], "-k") - plt.plot(hxEst[0, :], - hxEst[1, :], "-r") - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/SLAM/FastSLAM1/fast_slam1.py b/SLAM/FastSLAM1/fast_slam1.py deleted file mode 100644 index 98f8a66417..0000000000 --- a/SLAM/FastSLAM1/fast_slam1.py +++ /dev/null @@ -1,394 +0,0 @@ -""" - -FastSLAM 1.0 example - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math - -import matplotlib.pyplot as plt -import numpy as np -from utils.angle import angle_mod - -# Fast SLAM covariance -Q = np.diag([3.0, np.deg2rad(10.0)]) ** 2 -R = np.diag([1.0, np.deg2rad(20.0)]) ** 2 - -# Simulation parameter -Q_SIM = np.diag([0.3, np.deg2rad(2.0)]) ** 2 -R_SIM = np.diag([0.5, np.deg2rad(10.0)]) ** 2 -OFFSET_YAW_RATE_NOISE = 0.01 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] -MAX_RANGE = 20.0 # maximum observation range -M_DIST_TH = 2.0 # Threshold of Mahalanobis distance for data association. -STATE_SIZE = 3 # State size [x,y,yaw] -LM_SIZE = 2 # LM state size [x,y] -N_PARTICLE = 100 # number of particle -NTH = N_PARTICLE / 1.5 # Number of particle for re-sampling - -show_animation = True - - -class Particle: - - def __init__(self, n_landmark): - self.w = 1.0 / N_PARTICLE - self.x = 0.0 - self.y = 0.0 - self.yaw = 0.0 - # landmark x-y positions - self.lm = np.zeros((n_landmark, LM_SIZE)) - # landmark position covariance - self.lmP = np.zeros((n_landmark * LM_SIZE, LM_SIZE)) - - -def fast_slam1(particles, u, z): - particles = predict_particles(particles, u) - - particles = update_with_observation(particles, z) - - particles = resampling(particles) - - return particles - - -def normalize_weight(particles): - sum_w = sum([p.w for p in particles]) - - try: - for i in range(N_PARTICLE): - particles[i].w /= sum_w - except ZeroDivisionError: - for i in range(N_PARTICLE): - particles[i].w = 1.0 / N_PARTICLE - - return particles - - return particles - - -def calc_final_state(particles): - x_est = np.zeros((STATE_SIZE, 1)) - - particles = normalize_weight(particles) - - for i in range(N_PARTICLE): - x_est[0, 0] += particles[i].w * particles[i].x - x_est[1, 0] += particles[i].w * particles[i].y - x_est[2, 0] += particles[i].w * particles[i].yaw - - x_est[2, 0] = pi_2_pi(x_est[2, 0]) - - return x_est - - -def predict_particles(particles, u): - for i in range(N_PARTICLE): - px = np.zeros((STATE_SIZE, 1)) - px[0, 0] = particles[i].x - px[1, 0] = particles[i].y - px[2, 0] = particles[i].yaw - ud = u + (np.random.randn(1, 2) @ R ** 0.5).T # add noise - px = motion_model(px, ud) - particles[i].x = px[0, 0] - particles[i].y = px[1, 0] - particles[i].yaw = px[2, 0] - - return particles - - -def add_new_landmark(particle, z, Q_cov): - r = z[0] - b = z[1] - lm_id = int(z[2]) - - s = math.sin(pi_2_pi(particle.yaw + b)) - c = math.cos(pi_2_pi(particle.yaw + b)) - - particle.lm[lm_id, 0] = particle.x + r * c - particle.lm[lm_id, 1] = particle.y + r * s - - # covariance - dx = r * c - dy = r * s - d2 = dx**2 + dy**2 - d = math.sqrt(d2) - Gz = np.array([[dx / d, dy / d], - [-dy / d2, dx / d2]]) - particle.lmP[2 * lm_id:2 * lm_id + 2] = np.linalg.inv( - Gz) @ Q_cov @ np.linalg.inv(Gz.T) - - return particle - - -def compute_jacobians(particle, xf, Pf, Q_cov): - dx = xf[0, 0] - particle.x - dy = xf[1, 0] - particle.y - d2 = dx ** 2 + dy ** 2 - d = math.sqrt(d2) - - zp = np.array( - [d, pi_2_pi(math.atan2(dy, dx) - particle.yaw)]).reshape(2, 1) - - Hv = np.array([[-dx / d, -dy / d, 0.0], - [dy / d2, -dx / d2, -1.0]]) - - Hf = np.array([[dx / d, dy / d], - [-dy / d2, dx / d2]]) - - Sf = Hf @ Pf @ Hf.T + Q_cov - - return zp, Hv, Hf, Sf - - -def update_kf_with_cholesky(xf, Pf, v, Q_cov, Hf): - PHt = Pf @ Hf.T - S = Hf @ PHt + Q_cov - - S = (S + S.T) * 0.5 - s_chol = np.linalg.cholesky(S).T - s_chol_inv = np.linalg.inv(s_chol) - W1 = PHt @ s_chol_inv - W = W1 @ s_chol_inv.T - - x = xf + W @ v - P = Pf - W1 @ W1.T - - return x, P - - -def update_landmark(particle, z, Q_cov): - lm_id = int(z[2]) - xf = np.array(particle.lm[lm_id, :]).reshape(2, 1) - Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2, :]) - - zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q) - - dz = z[0:2].reshape(2, 1) - zp - dz[1, 0] = pi_2_pi(dz[1, 0]) - - xf, Pf = update_kf_with_cholesky(xf, Pf, dz, Q_cov, Hf) - - particle.lm[lm_id, :] = xf.T - particle.lmP[2 * lm_id:2 * lm_id + 2, :] = Pf - - return particle - - -def compute_weight(particle, z, Q_cov): - lm_id = int(z[2]) - xf = np.array(particle.lm[lm_id, :]).reshape(2, 1) - Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2]) - zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q_cov) - - dx = z[0:2].reshape(2, 1) - zp - dx[1, 0] = pi_2_pi(dx[1, 0]) - - try: - invS = np.linalg.inv(Sf) - except np.linalg.linalg.LinAlgError: - print("singular") - return 1.0 - - num = np.exp(-0.5 * (dx.T @ invS @ dx))[0, 0] - den = 2.0 * math.pi * math.sqrt(np.linalg.det(Sf)) - - w = num / den - - return w - - -def update_with_observation(particles, z): - for iz in range(len(z[0, :])): - - landmark_id = int(z[2, iz]) - - for ip in range(N_PARTICLE): - # new landmark - if abs(particles[ip].lm[landmark_id, 0]) <= 0.01: - particles[ip] = add_new_landmark(particles[ip], z[:, iz], Q) - # known landmark - else: - w = compute_weight(particles[ip], z[:, iz], Q) - particles[ip].w *= w - particles[ip] = update_landmark(particles[ip], z[:, iz], Q) - - return particles - - -def resampling(particles): - """ - low variance re-sampling - """ - - particles = normalize_weight(particles) - - pw = [] - for i in range(N_PARTICLE): - pw.append(particles[i].w) - - pw = np.array(pw) - - n_eff = 1.0 / (pw @ pw.T) # Effective particle number - - if n_eff < NTH: # resampling - w_cum = np.cumsum(pw) - base = np.cumsum(pw * 0.0 + 1 / N_PARTICLE) - 1 / N_PARTICLE - resample_id = base + np.random.rand(base.shape[0]) / N_PARTICLE - - indexes = [] - index = 0 - for ip in range(N_PARTICLE): - while (index < w_cum.shape[0] - 1) \ - and (resample_id[ip] > w_cum[index]): - index += 1 - indexes.append(index) - - tmp_particles = particles[:] - for i in range(len(indexes)): - particles[i].x = tmp_particles[indexes[i]].x - particles[i].y = tmp_particles[indexes[i]].y - particles[i].yaw = tmp_particles[indexes[i]].yaw - particles[i].lm = tmp_particles[indexes[i]].lm[:, :] - particles[i].lmP = tmp_particles[indexes[i]].lmP[:, :] - particles[i].w = 1.0 / N_PARTICLE - - return particles - - -def calc_input(time): - if time <= 3.0: # wait at first - v = 0.0 - yaw_rate = 0.0 - else: - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - - u = np.array([v, yaw_rate]).reshape(2, 1) - - return u - - -def observation(x_true, xd, u, rfid): - # calc true state - x_true = motion_model(x_true, u) - - # add noise to range observation - z = np.zeros((3, 0)) - for i in range(len(rfid[:, 0])): - - dx = rfid[i, 0] - x_true[0, 0] - dy = rfid[i, 1] - x_true[1, 0] - d = math.hypot(dx, dy) - angle = pi_2_pi(math.atan2(dy, dx) - x_true[2, 0]) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_SIM[0, 0] ** 0.5 # add noise - angle_with_noize = angle + np.random.randn() * Q_SIM[ - 1, 1] ** 0.5 # add noise - zi = np.array([dn, pi_2_pi(angle_with_noize), i]).reshape(3, 1) - z = np.hstack((z, zi)) - - # add noise to input - ud1 = u[0, 0] + np.random.randn() * R_SIM[0, 0] ** 0.5 - ud2 = u[1, 0] + np.random.randn() * R_SIM[ - 1, 1] ** 0.5 + OFFSET_YAW_RATE_NOISE - ud = np.array([ud1, ud2]).reshape(2, 1) - - xd = motion_model(xd, ud) - - return x_true, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0], - [0, 1.0, 0], - [0, 0, 1.0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT]]) - - x = F @ x + B @ u - - x[2, 0] = pi_2_pi(x[2, 0]) - - return x - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RFID positions [x, y] - rfid = np.array([[10.0, -2.0], - [15.0, 10.0], - [15.0, 15.0], - [10.0, 20.0], - [3.0, 15.0], - [-5.0, 20.0], - [-5.0, 5.0], - [-10.0, 15.0] - ]) - n_landmark = rfid.shape[0] - - # State Vector [x y yaw v]' - x_est = np.zeros((STATE_SIZE, 1)) # SLAM estimation - x_true = np.zeros((STATE_SIZE, 1)) # True state - x_dr = np.zeros((STATE_SIZE, 1)) # Dead reckoning - - # history - hist_x_est = x_est - hist_x_true = x_true - hist_x_dr = x_dr - - particles = [Particle(n_landmark) for _ in range(N_PARTICLE)] - - while SIM_TIME >= time: - time += DT - u = calc_input(time) - - x_true, z, x_dr, ud = observation(x_true, x_dr, u, rfid) - - particles = fast_slam1(particles, ud, z) - - x_est = calc_final_state(particles) - - x_state = x_est[0: STATE_SIZE] - - # store data history - hist_x_est = np.hstack((hist_x_est, x_state)) - hist_x_dr = np.hstack((hist_x_dr, x_dr)) - hist_x_true = np.hstack((hist_x_true, x_true)) - - if show_animation: # pragma: no cover - 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]) - plt.plot(rfid[:, 0], rfid[:, 1], "*k") - - for i in range(N_PARTICLE): - plt.plot(particles[i].x, particles[i].y, ".r") - plt.plot(particles[i].lm[:, 0], particles[i].lm[:, 1], "xb") - - plt.plot(hist_x_true[0, :], hist_x_true[1, :], "-b") - plt.plot(hist_x_dr[0, :], hist_x_dr[1, :], "-k") - plt.plot(hist_x_est[0, :], hist_x_est[1, :], "-r") - plt.plot(x_est[0], x_est[1], "xk") - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/SLAM/FastSLAM2/fast_slam2.py b/SLAM/FastSLAM2/fast_slam2.py deleted file mode 100644 index d4cf0d84dd..0000000000 --- a/SLAM/FastSLAM2/fast_slam2.py +++ /dev/null @@ -1,426 +0,0 @@ -""" - -FastSLAM 2.0 example - -author: Atsushi Sakai (@Atsushi_twi) - -""" - -import math - -import matplotlib.pyplot as plt -import numpy as np -from utils.angle import angle_mod - -# Fast SLAM covariance -Q = np.diag([3.0, np.deg2rad(10.0)]) ** 2 -R = np.diag([1.0, np.deg2rad(20.0)]) ** 2 - -# Simulation parameter -Q_SIM = np.diag([0.3, np.deg2rad(2.0)]) ** 2 -R_SIM = np.diag([0.5, np.deg2rad(10.0)]) ** 2 -OFFSET_YAW_RATE_NOISE = 0.01 - -DT = 0.1 # time tick [s] -SIM_TIME = 50.0 # simulation time [s] -MAX_RANGE = 20.0 # maximum observation range -M_DIST_TH = 2.0 # Threshold of Mahalanobis distance for data association. -STATE_SIZE = 3 # State size [x,y,yaw] -LM_SIZE = 2 # LM state size [x,y] -N_PARTICLE = 100 # number of particle -NTH = N_PARTICLE / 1.5 # Number of particle for re-sampling - -show_animation = True - - -class Particle: - - def __init__(self, n_landmark): - self.w = 1.0 / N_PARTICLE - self.x = 0.0 - self.y = 0.0 - self.yaw = 0.0 - self.P = np.eye(3) - # landmark x-y positions - self.lm = np.zeros((n_landmark, LM_SIZE)) - # landmark position covariance - self.lmP = np.zeros((n_landmark * LM_SIZE, LM_SIZE)) - - -def fast_slam2(particles, u, z): - particles = predict_particles(particles, u) - - particles = update_with_observation(particles, z) - - particles = resampling(particles) - - return particles - - -def normalize_weight(particles): - sum_w = sum([p.w for p in particles]) - - try: - for i in range(N_PARTICLE): - particles[i].w /= sum_w - except ZeroDivisionError: - for i in range(N_PARTICLE): - particles[i].w = 1.0 / N_PARTICLE - - return particles - - return particles - - -def calc_final_state(particles): - x_est = np.zeros((STATE_SIZE, 1)) - - particles = normalize_weight(particles) - - for i in range(N_PARTICLE): - x_est[0, 0] += particles[i].w * particles[i].x - x_est[1, 0] += particles[i].w * particles[i].y - x_est[2, 0] += particles[i].w * particles[i].yaw - - x_est[2, 0] = pi_2_pi(x_est[2, 0]) - - return x_est - - -def predict_particles(particles, u): - for i in range(N_PARTICLE): - px = np.zeros((STATE_SIZE, 1)) - px[0, 0] = particles[i].x - px[1, 0] = particles[i].y - px[2, 0] = particles[i].yaw - ud = u + (np.random.randn(1, 2) @ R ** 0.5).T # add noise - px = motion_model(px, ud) - particles[i].x = px[0, 0] - particles[i].y = px[1, 0] - particles[i].yaw = px[2, 0] - - return particles - - -def add_new_lm(particle, z, Q_cov): - r = z[0] - b = z[1] - lm_id = int(z[2]) - - s = math.sin(pi_2_pi(particle.yaw + b)) - c = math.cos(pi_2_pi(particle.yaw + b)) - - particle.lm[lm_id, 0] = particle.x + r * c - particle.lm[lm_id, 1] = particle.y + r * s - - # covariance - dx = r * c - dy = r * s - d2 = dx ** 2 + dy ** 2 - d = math.sqrt(d2) - Gz = np.array([[dx / d, dy / d], - [-dy / d2, dx / d2]]) - particle.lmP[2 * lm_id:2 * lm_id + 2] = np.linalg.inv( - Gz) @ Q_cov @ np.linalg.inv(Gz.T) - - return particle - - -def compute_jacobians(particle, xf, Pf, Q_cov): - dx = xf[0, 0] - particle.x - dy = xf[1, 0] - particle.y - d2 = dx ** 2 + dy ** 2 - d = math.sqrt(d2) - - zp = np.array( - [d, pi_2_pi(math.atan2(dy, dx) - particle.yaw)]).reshape(2, 1) - - Hv = np.array([[-dx / d, -dy / d, 0.0], - [dy / d2, -dx / d2, -1.0]]) - - Hf = np.array([[dx / d, dy / d], - [-dy / d2, dx / d2]]) - - Sf = Hf @ Pf @ Hf.T + Q_cov - - return zp, Hv, Hf, Sf - - -def update_kf_with_cholesky(xf, Pf, v, Q_cov, Hf): - PHt = Pf @ Hf.T - S = Hf @ PHt + Q_cov - - S = (S + S.T) * 0.5 - SChol = np.linalg.cholesky(S).T - SCholInv = np.linalg.inv(SChol) - W1 = PHt @ SCholInv - W = W1 @ SCholInv.T - - x = xf + W @ v - P = Pf - W1 @ W1.T - - return x, P - - -def update_landmark(particle, z, Q_cov): - lm_id = int(z[2]) - xf = np.array(particle.lm[lm_id, :]).reshape(2, 1) - Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2]) - - zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q_cov) - - dz = z[0:2].reshape(2, 1) - zp - dz[1, 0] = pi_2_pi(dz[1, 0]) - - xf, Pf = update_kf_with_cholesky(xf, Pf, dz, Q, Hf) - - particle.lm[lm_id, :] = xf.T - particle.lmP[2 * lm_id:2 * lm_id + 2, :] = Pf - - return particle - - -def compute_weight(particle, z, Q_cov): - lm_id = int(z[2]) - xf = np.array(particle.lm[lm_id, :]).reshape(2, 1) - Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2]) - zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q_cov) - - dz = z[0:2].reshape(2, 1) - zp - dz[1, 0] = pi_2_pi(dz[1, 0]) - - try: - invS = np.linalg.inv(Sf) - except np.linalg.linalg.LinAlgError: - return 1.0 - - num = np.exp(-0.5 * dz.T @ invS @ dz)[0, 0] - den = 2.0 * math.pi * math.sqrt(np.linalg.det(Sf)) - - w = num / den - - return w - - -def proposal_sampling(particle, z, Q_cov): - lm_id = int(z[2]) - xf = particle.lm[lm_id, :].reshape(2, 1) - Pf = particle.lmP[2 * lm_id:2 * lm_id + 2] - # State - x = np.array([particle.x, particle.y, particle.yaw]).reshape(3, 1) - P = particle.P - zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q_cov) - - Sfi = np.linalg.inv(Sf) - dz = z[0:2].reshape(2, 1) - zp - dz[1] = pi_2_pi(dz[1]) - - Pi = np.linalg.inv(P) - - particle.P = np.linalg.inv(Hv.T @ Sfi @ Hv + Pi) # proposal covariance - x += particle.P @ Hv.T @ Sfi @ dz # proposal mean - - particle.x = x[0, 0] - particle.y = x[1, 0] - particle.yaw = x[2, 0] - - return particle - - -def update_with_observation(particles, z): - for iz in range(len(z[0, :])): - landmark_id = int(z[2, iz]) - - for ip in range(N_PARTICLE): - # new landmark - if abs(particles[ip].lm[landmark_id, 0]) <= 0.01: - particles[ip] = add_new_lm(particles[ip], z[:, iz], Q) - # known landmark - else: - w = compute_weight(particles[ip], z[:, iz], Q) - particles[ip].w *= w - - particles[ip] = update_landmark(particles[ip], z[:, iz], Q) - particles[ip] = proposal_sampling(particles[ip], z[:, iz], Q) - - return particles - - -def resampling(particles): - """ - low variance re-sampling - """ - - particles = normalize_weight(particles) - - pw = [] - for i in range(N_PARTICLE): - pw.append(particles[i].w) - - pw = np.array(pw) - - n_eff = 1.0 / (pw @ pw.T) # Effective particle number - - if n_eff < NTH: # resampling - w_cum = np.cumsum(pw) - base = np.cumsum(pw * 0.0 + 1 / N_PARTICLE) - 1 / N_PARTICLE - resample_id = base + np.random.rand(base.shape[0]) / N_PARTICLE - - indexes = [] - index = 0 - for ip in range(N_PARTICLE): - while (index < w_cum.shape[0] - 1) \ - and (resample_id[ip] > w_cum[index]): - index += 1 - indexes.append(index) - - tmp_particles = particles[:] - for i in range(len(indexes)): - particles[i].x = tmp_particles[indexes[i]].x - particles[i].y = tmp_particles[indexes[i]].y - particles[i].yaw = tmp_particles[indexes[i]].yaw - particles[i].lm = tmp_particles[indexes[i]].lm[:, :] - particles[i].lmP = tmp_particles[indexes[i]].lmP[:, :] - particles[i].w = 1.0 / N_PARTICLE - - return particles - - -def calc_input(time): - if time <= 3.0: # wait at first - v = 0.0 - yaw_rate = 0.0 - else: - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - - u = np.array([v, yaw_rate]).reshape(2, 1) - - return u - - -def observation(x_true, xd, u, rfid): - # calc true state - x_true = motion_model(x_true, u) - - # add noise to range observation - z = np.zeros((3, 0)) - - for i in range(len(rfid[:, 0])): - - dx = rfid[i, 0] - x_true[0, 0] - dy = rfid[i, 1] - x_true[1, 0] - d = math.hypot(dx, dy) - angle = pi_2_pi(math.atan2(dy, dx) - x_true[2, 0]) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_SIM[0, 0] ** 0.5 # add noise - angle_noise = np.random.randn() * Q_SIM[1, 1] ** 0.5 - angle_with_noise = angle + angle_noise # add noise - zi = np.array([dn, pi_2_pi(angle_with_noise), i]).reshape(3, 1) - z = np.hstack((z, zi)) - - # add noise to input - ud1 = u[0, 0] + np.random.randn() * R_SIM[0, 0] ** 0.5 - ud2 = u[1, 0] + np.random.randn() * R_SIM[ - 1, 1] ** 0.5 + OFFSET_YAW_RATE_NOISE - ud = np.array([ud1, ud2]).reshape(2, 1) - - xd = motion_model(xd, ud) - - return x_true, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0], - [0, 1.0, 0], - [0, 0, 1.0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT]]) - - x = F @ x + B @ u - - x[2, 0] = pi_2_pi(x[2, 0]) - - return x - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RFID positions [x, y] - rfid = np.array([[10.0, -2.0], - [15.0, 10.0], - [15.0, 15.0], - [10.0, 20.0], - [3.0, 15.0], - [-5.0, 20.0], - [-5.0, 5.0], - [-10.0, 15.0] - ]) - n_landmark = rfid.shape[0] - - # State Vector [x y yaw v]' - x_est = np.zeros((STATE_SIZE, 1)) # SLAM estimation - x_true = np.zeros((STATE_SIZE, 1)) # True state - x_dr = np.zeros((STATE_SIZE, 1)) # Dead reckoning - - # history - hist_x_est = x_est - hist_x_true = x_true - hist_x_dr = x_dr - - particles = [Particle(n_landmark) for _ in range(N_PARTICLE)] - - while SIM_TIME >= time: - time += DT - u = calc_input(time) - - x_true, z, x_dr, ud = observation(x_true, x_dr, u, rfid) - - particles = fast_slam2(particles, ud, z) - - x_est = calc_final_state(particles) - - x_state = x_est[0: STATE_SIZE] - - # store data history - hist_x_est = np.hstack((hist_x_est, x_state)) - hist_x_dr = np.hstack((hist_x_dr, x_dr)) - hist_x_true = np.hstack((hist_x_true, x_true)) - - if show_animation: # pragma: no cover - 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]) - plt.plot(rfid[:, 0], rfid[:, 1], "*k") - - for iz in range(len(z[:, 0])): - landmark_id = int(z[2, iz]) - plt.plot([x_est[0][0], rfid[landmark_id, 0]], [ - x_est[1][0], rfid[landmark_id, 1]], "-k") - - for i in range(N_PARTICLE): - plt.plot(particles[i].x, particles[i].y, ".r") - plt.plot(particles[i].lm[:, 0], particles[i].lm[:, 1], "xb") - - plt.plot(hist_x_true[0, :], hist_x_true[1, :], "-b") - plt.plot(hist_x_dr[0, :], hist_x_dr[1, :], "-k") - plt.plot(hist_x_est[0, :], hist_x_est[1, :], "-r") - plt.plot(x_est[0], x_est[1], "xk") - plt.axis("equal") - plt.grid(True) - plt.pause(0.001) - - -if __name__ == '__main__': - main() diff --git a/SLAM/GraphBasedSLAM/data/README.rst b/SLAM/GraphBasedSLAM/data/README.rst deleted file mode 100644 index 15bc5b6c03..0000000000 --- a/SLAM/GraphBasedSLAM/data/README.rst +++ /dev/null @@ -1,6 +0,0 @@ -Acknowledgments and References -============================== - -Thanks to Luca Larlone for allowing inclusion of the `Intel dataset `_ in this repo. - -1. Carlone, L. and Censi, A., 2014. `From angular manifolds to the integer lattice: Guaranteed orientation estimation with application to pose graph optimization `_. IEEE Transactions on Robotics, 30(2), pp.475-492. diff --git a/SLAM/GraphBasedSLAM/data/input_INTEL.g2o b/SLAM/GraphBasedSLAM/data/input_INTEL.g2o deleted file mode 100644 index 16f3a2600c..0000000000 --- a/SLAM/GraphBasedSLAM/data/input_INTEL.g2o +++ /dev/null @@ -1,2711 +0,0 @@ -VERTEX_SE2 0 0.000000 0.000000 0.000000 -VERTEX_SE2 1 0.000000 0.000000 -0.000642 -VERTEX_SE2 2 0.000000 0.000000 -0.001180 -VERTEX_SE2 3 0.011002 -0.000975 -0.003562 -VERTEX_SE2 4 0.641008 -0.011200 -0.007444 -VERTEX_SE2 5 0.696016 -0.011716 -0.098726 -VERTEX_SE2 6 0.700269 -0.015435 -0.895998 -VERTEX_SE2 7 0.693956 0.004213 -1.511535 -VERTEX_SE2 8 0.699263 0.026693 -2.125069 -VERTEX_SE2 9 0.716114 0.041122 -2.763139 -VERTEX_SE2 10 0.728981 0.043219 2.905515 -VERTEX_SE2 11 0.733203 0.040537 2.231236 -VERTEX_SE2 12 0.733186 0.041545 1.581762 -VERTEX_SE2 13 0.738753 0.059070 0.922264 -VERTEX_SE2 14 0.759658 0.073750 0.314025 -VERTEX_SE2 15 0.768572 0.074357 -0.210903 -VERTEX_SE2 16 1.062493 0.014152 -0.229595 -VERTEX_SE2 17 1.663647 -0.133115 -0.260823 -VERTEX_SE2 18 2.287001 -0.296480 -0.271000 -VERTEX_SE2 19 2.914298 -0.494254 -0.319576 -VERTEX_SE2 20 3.527220 -0.693985 -0.308933 -VERTEX_SE2 21 4.152895 -0.850915 -0.191238 -VERTEX_SE2 22 4.779015 -0.992339 -0.168556 -VERTEX_SE2 23 5.423978 -1.030005 -0.054484 -VERTEX_SE2 24 6.064382 -1.090997 -0.105423 -VERTEX_SE2 25 6.707928 -1.144401 -0.096821 -VERTEX_SE2 26 7.315680 -1.224079 -0.137153 -VERTEX_SE2 27 7.929761 -1.294250 -0.121079 -VERTEX_SE2 28 8.510243 -1.372635 -0.125240 -VERTEX_SE2 29 8.886549 -1.416447 -0.094136 -VERTEX_SE2 30 8.893462 -1.418023 -0.625522 -VERTEX_SE2 31 9.005697 -1.557779 -0.893355 -VERTEX_SE2 32 9.398300 -2.067296 -0.911506 -VERTEX_SE2 33 9.800909 -2.566880 -0.858368 -VERTEX_SE2 34 10.230894 -3.045076 -0.834898 -VERTEX_SE2 35 10.670845 -3.512974 -0.797906 -VERTEX_SE2 36 11.118351 -3.980273 -0.803680 -VERTEX_SE2 37 11.413882 -4.282108 -0.793023 -VERTEX_SE2 38 11.416535 -4.285316 -1.109584 -VERTEX_SE2 39 11.425445 -4.316267 -1.276487 -VERTEX_SE2 40 11.612551 -4.916547 -1.255922 -VERTEX_SE2 41 11.808527 -5.541274 -1.284316 -VERTEX_SE2 42 11.976726 -6.133098 -1.303279 -VERTEX_SE2 43 12.132495 -6.760248 -1.339357 -VERTEX_SE2 44 12.273339 -7.389611 -1.372003 -VERTEX_SE2 45 12.381155 -8.023418 -1.427426 -VERTEX_SE2 46 12.464524 -8.661631 -1.446702 -VERTEX_SE2 47 12.536773 -9.300726 -1.488240 -VERTEX_SE2 48 12.585187 -9.946461 -1.494639 -VERTEX_SE2 49 12.629508 -10.588502 -1.545288 -VERTEX_SE2 50 12.623735 -11.202499 -1.589586 -VERTEX_SE2 51 12.613473 -11.816834 -1.619614 -VERTEX_SE2 52 12.558110 -12.427134 -1.697131 -VERTEX_SE2 53 12.497320 -13.013155 -1.685264 -VERTEX_SE2 54 12.418711 -13.557683 -1.747661 -VERTEX_SE2 55 12.419724 -13.549747 -1.742676 -VERTEX_SE2 56 12.414345 -13.580353 -1.732396 -VERTEX_SE2 57 12.310914 -14.233877 -1.743844 -VERTEX_SE2 58 12.203756 -14.876538 -1.697418 -VERTEX_SE2 59 12.115088 -15.515348 -1.720561 -VERTEX_SE2 60 12.025760 -16.150800 -1.698500 -VERTEX_SE2 61 11.930749 -16.787980 -1.748266 -VERTEX_SE2 62 11.833006 -17.390733 -1.709239 -VERTEX_SE2 63 11.733602 -17.998517 -1.746382 -VERTEX_SE2 64 11.649132 -18.607583 -1.685617 -VERTEX_SE2 65 11.563367 -19.214882 -1.740522 -VERTEX_SE2 66 11.460058 -19.848621 -1.752656 -VERTEX_SE2 67 11.343148 -20.481952 -1.756181 -VERTEX_SE2 68 11.257571 -20.955257 -1.774331 -VERTEX_SE2 69 11.255185 -20.964979 -1.932353 -VERTEX_SE2 70 11.251937 -20.972069 -2.613265 -VERTEX_SE2 71 11.258227 -20.971402 3.020985 -VERTEX_SE2 72 11.258227 -20.971402 3.009195 -VERTEX_SE2 73 11.254702 -20.970679 2.941681 -VERTEX_SE2 74 11.251643 -20.968771 2.313797 -VERTEX_SE2 75 11.250298 -20.968291 1.957266 -VERTEX_SE2 76 11.239515 -20.956998 2.644600 -VERTEX_SE2 77 11.227570 -20.952338 2.874132 -VERTEX_SE2 78 10.929861 -20.969392 -3.076000 -VERTEX_SE2 79 10.295660 -21.019100 -3.083625 -VERTEX_SE2 80 9.648073 -21.026221 3.122276 -VERTEX_SE2 81 9.008652 -21.005804 3.093707 -VERTEX_SE2 82 8.367330 -20.952196 3.043418 -VERTEX_SE2 83 7.730227 -20.888698 3.024388 -VERTEX_SE2 84 7.095779 -20.785072 2.952781 -VERTEX_SE2 85 6.465179 -20.660326 2.966718 -VERTEX_SE2 86 5.750805 -20.513125 2.886729 -VERTEX_SE2 87 5.163554 -20.341011 2.848617 -VERTEX_SE2 88 4.815868 -20.234565 2.823505 -VERTEX_SE2 89 4.792324 -20.235107 -3.054605 -VERTEX_SE2 90 4.149209 -20.288628 -3.081395 -VERTEX_SE2 91 3.497178 -20.337577 -3.028289 -VERTEX_SE2 92 2.861629 -20.387679 -3.140035 -VERTEX_SE2 93 2.221011 -20.339858 3.045531 -VERTEX_SE2 94 1.585959 -20.258625 3.005168 -VERTEX_SE2 95 0.940663 -20.228767 -3.139936 -VERTEX_SE2 96 0.302216 -20.232542 -3.119921 -VERTEX_SE2 97 -0.314018 -20.219530 3.087656 -VERTEX_SE2 98 -0.922581 -20.164719 3.024330 -VERTEX_SE2 99 -1.648795 -20.093526 3.065410 -VERTEX_SE2 100 -2.230833 -20.023281 3.007277 -VERTEX_SE2 101 -2.871354 -20.005103 3.102942 -VERTEX_SE2 102 -3.516483 -19.955697 3.008603 -VERTEX_SE2 103 -4.152223 -19.878149 3.115724 -VERTEX_SE2 104 -4.795838 -19.847155 3.090758 -VERTEX_SE2 105 -5.440965 -19.834033 -3.137000 -VERTEX_SE2 106 -6.074366 -19.819256 3.099547 -VERTEX_SE2 107 -6.721384 -19.784913 3.102775 -VERTEX_SE2 108 -7.140409 -19.781530 -3.121388 -VERTEX_SE2 109 -7.153321 -19.779550 2.708464 -VERTEX_SE2 110 -7.164494 -19.769929 2.111343 -VERTEX_SE2 111 -7.174123 -19.662635 1.600354 -VERTEX_SE2 112 -7.201482 -19.000514 1.652438 -VERTEX_SE2 113 -7.269988 -18.356059 1.697296 -VERTEX_SE2 114 -7.351668 -17.722124 1.732079 -VERTEX_SE2 115 -7.477063 -17.090635 1.787906 -VERTEX_SE2 116 -7.655600 -16.348269 1.827325 -VERTEX_SE2 117 -7.838440 -15.740356 1.917383 -VERTEX_SE2 118 -7.847180 -15.710500 1.574139 -VERTEX_SE2 119 -7.822729 -15.211952 1.515457 -VERTEX_SE2 120 -7.806541 -14.573234 1.599604 -VERTEX_SE2 121 -7.846755 -13.965257 1.661084 -VERTEX_SE2 122 -7.908309 -13.357352 1.710992 -VERTEX_SE2 123 -7.944931 -13.145472 1.692222 -VERTEX_SE2 124 -7.867047 -12.909702 1.255856 -VERTEX_SE2 125 -7.662551 -12.318719 1.238631 -VERTEX_SE2 126 -7.536130 -11.694942 1.435606 -VERTEX_SE2 127 -7.446823 -11.065323 1.432524 -VERTEX_SE2 128 -7.378220 -10.424022 1.466677 -VERTEX_SE2 129 -7.297629 -9.790420 1.431058 -VERTEX_SE2 130 -7.233612 -9.152396 1.479951 -VERTEX_SE2 131 -7.188908 -8.512333 1.563552 -VERTEX_SE2 132 -7.169731 -7.871150 1.508512 -VERTEX_SE2 133 -7.117635 -7.291335 1.466418 -VERTEX_SE2 134 -7.055851 -6.684237 1.442294 -VERTEX_SE2 135 -6.963757 -6.111663 1.409225 -VERTEX_SE2 136 -6.874804 -5.474955 1.421265 -VERTEX_SE2 137 -6.760504 -4.850881 1.369865 -VERTEX_SE2 138 -6.660597 -4.217332 1.438397 -VERTEX_SE2 139 -6.600415 -3.595492 1.503413 -VERTEX_SE2 140 -6.582744 -2.945853 1.533669 -VERTEX_SE2 141 -6.543894 -2.313106 1.484948 -VERTEX_SE2 142 -6.498552 -1.671107 1.480848 -VERTEX_SE2 143 -6.373361 -1.045821 1.369956 -VERTEX_SE2 144 -6.308206 -0.684059 1.387205 -VERTEX_SE2 145 -6.303725 -0.673188 0.888171 -VERTEX_SE2 146 -6.303130 -0.671010 0.294225 -VERTEX_SE2 147 -6.246280 -0.679152 -0.192923 -VERTEX_SE2 148 -5.818303 -0.770932 -0.228866 -VERTEX_SE2 149 -5.184464 -0.911850 -0.204032 -VERTEX_SE2 150 -4.555178 -1.023116 -0.190122 -VERTEX_SE2 151 -3.919217 -1.117808 -0.104723 -VERTEX_SE2 152 -3.282062 -1.199418 -0.172903 -VERTEX_SE2 153 -2.645312 -1.282994 -0.087181 -VERTEX_SE2 154 -2.011150 -1.359207 -0.164419 -VERTEX_SE2 155 -1.375820 -1.453297 -0.137992 -VERTEX_SE2 156 -0.735790 -1.532677 -0.114327 -VERTEX_SE2 157 -0.159947 -1.613323 -0.186690 -VERTEX_SE2 158 0.023851 -1.648467 -0.157600 -VERTEX_SE2 159 0.021228 -1.647288 0.835817 -VERTEX_SE2 160 0.027488 -1.639228 1.877886 -VERTEX_SE2 161 0.027486 -1.639234 2.859828 -VERTEX_SE2 162 0.045975 -1.631965 -2.381155 -VERTEX_SE2 163 0.045893 -1.634205 -1.377922 -VERTEX_SE2 164 0.057499 -1.646265 -0.447964 -VERTEX_SE2 165 0.193934 -1.655710 -0.045831 -VERTEX_SE2 166 0.853469 -1.682017 -0.046800 -VERTEX_SE2 167 1.490484 -1.716964 -0.075973 -VERTEX_SE2 168 2.098800 -1.751671 -0.063936 -VERTEX_SE2 169 2.712806 -1.808931 -0.100989 -VERTEX_SE2 170 3.318192 -1.863258 -0.121905 -VERTEX_SE2 171 3.896234 -1.949098 -0.160646 -VERTEX_SE2 172 4.538410 -2.042425 -0.147172 -VERTEX_SE2 173 5.172424 -2.149723 -0.165214 -VERTEX_SE2 174 5.803089 -2.247662 -0.120052 -VERTEX_SE2 175 6.424807 -2.318550 -0.139998 -VERTEX_SE2 176 7.030678 -2.417232 -0.125711 -VERTEX_SE2 177 7.660479 -2.494551 -0.148623 -VERTEX_SE2 178 8.290147 -2.612123 -0.182806 -VERTEX_SE2 179 8.639509 -2.668503 -0.129585 -VERTEX_SE2 180 8.642601 -2.669171 -0.571750 -VERTEX_SE2 181 9.107062 -2.977761 -0.623138 -VERTEX_SE2 182 9.551340 -3.341562 -0.796106 -VERTEX_SE2 183 9.953562 -3.792478 -0.852123 -VERTEX_SE2 184 10.330878 -4.306658 -1.002936 -VERTEX_SE2 185 10.652963 -4.854256 -1.099221 -VERTEX_SE2 186 10.909459 -5.445570 -1.168989 -VERTEX_SE2 187 11.138453 -6.018162 -1.194573 -VERTEX_SE2 188 11.372847 -6.610777 -1.202696 -VERTEX_SE2 189 11.598101 -7.209159 -1.232095 -VERTEX_SE2 190 11.795596 -7.819838 -1.258284 -VERTEX_SE2 191 11.984938 -8.432395 -1.279246 -VERTEX_SE2 192 12.158054 -9.043198 -1.303901 -VERTEX_SE2 193 12.302583 -9.611434 -1.355161 -VERTEX_SE2 194 12.426909 -10.210284 -1.377141 -VERTEX_SE2 195 12.534319 -10.811416 -1.400368 -VERTEX_SE2 196 12.632338 -11.445843 -1.442220 -VERTEX_SE2 197 12.700713 -12.082422 -1.470453 -VERTEX_SE2 198 12.755597 -12.719166 -1.529113 -VERTEX_SE2 199 12.754761 -13.356843 -1.590711 -VERTEX_SE2 200 12.733560 -13.993458 -1.612475 -VERTEX_SE2 201 12.687026 -14.631689 -1.668718 -VERTEX_SE2 202 12.639077 -15.202899 -1.651577 -VERTEX_SE2 203 12.556158 -16.006543 -1.707054 -VERTEX_SE2 204 12.455302 -16.598850 -1.804142 -VERTEX_SE2 205 12.305924 -17.187841 -1.809172 -VERTEX_SE2 206 12.174405 -17.760149 -1.802549 -VERTEX_SE2 207 12.040676 -18.353284 -1.781423 -VERTEX_SE2 208 11.897670 -18.981299 -1.785518 -VERTEX_SE2 209 11.769917 -19.609340 -1.741125 -VERTEX_SE2 210 11.647398 -20.238835 -1.754048 -VERTEX_SE2 211 11.537207 -20.870318 -1.755716 -VERTEX_SE2 212 11.394970 -21.493361 -1.812926 -VERTEX_SE2 213 11.230953 -22.120731 -1.837752 -VERTEX_SE2 214 11.058372 -22.734177 -1.845436 -VERTEX_SE2 215 10.953834 -23.088866 -1.885350 -VERTEX_SE2 216 10.950554 -23.095138 -2.393113 -VERTEX_SE2 217 10.952753 -23.094815 -2.944927 -VERTEX_SE2 218 10.951809 -23.094487 -3.005204 -VERTEX_SE2 219 10.365803 -23.067344 3.104107 -VERTEX_SE2 220 9.737327 -23.038247 3.059963 -VERTEX_SE2 221 9.095837 -22.994717 3.111718 -VERTEX_SE2 222 8.460944 -22.975047 3.086737 -VERTEX_SE2 223 7.821250 -22.969336 -3.100192 -VERTEX_SE2 224 7.183803 -22.994444 -3.126677 -VERTEX_SE2 225 6.544582 -23.012637 -3.078368 -VERTEX_SE2 226 5.906305 -23.029186 -3.129535 -VERTEX_SE2 227 5.264179 -23.084344 -2.972817 -VERTEX_SE2 228 4.660809 -23.049652 2.951621 -VERTEX_SE2 229 4.059169 -22.969281 3.076549 -VERTEX_SE2 230 3.689211 -22.947652 3.051106 -VERTEX_SE2 231 3.819912 -22.954624 3.124109 -VERTEX_SE2 232 3.476990 -22.813142 2.710350 -VERTEX_SE2 233 2.875321 -22.565879 2.819555 -VERTEX_SE2 234 2.261463 -22.358960 2.781360 -VERTEX_SE2 235 1.979653 -22.255530 2.781875 -VERTEX_SE2 236 1.616344 -22.278805 -3.083673 -VERTEX_SE2 237 0.964219 -22.341873 -3.006771 -VERTEX_SE2 238 0.323899 -22.443278 -3.033605 -VERTEX_SE2 239 -0.317791 -22.494927 -3.094849 -VERTEX_SE2 240 -0.925503 -22.426348 2.978831 -VERTEX_SE2 241 -1.504549 -22.345735 3.071089 -VERTEX_SE2 242 -2.117312 -22.325210 3.098446 -VERTEX_SE2 243 -2.764025 -22.279874 3.087887 -VERTEX_SE2 244 -3.406724 -22.271623 -3.133223 -VERTEX_SE2 245 -4.041131 -22.254659 3.087973 -VERTEX_SE2 246 -4.684337 -22.191483 3.008717 -VERTEX_SE2 247 -5.325218 -22.128855 3.108731 -VERTEX_SE2 248 -5.890123 -22.137360 3.133698 -VERTEX_SE2 249 -5.854210 -22.141017 -2.965499 -VERTEX_SE2 250 -5.862024 -22.141342 2.710834 -VERTEX_SE2 251 -6.210340 -21.788237 2.273028 -VERTEX_SE2 252 -6.584399 -21.351579 2.275461 -VERTEX_SE2 253 -6.971866 -20.886741 2.258217 -VERTEX_SE2 254 -7.349642 -20.406697 2.210371 -VERTEX_SE2 255 -7.376562 -20.371386 2.285362 -VERTEX_SE2 256 -7.376569 -20.370011 1.785796 -VERTEX_SE2 257 -7.435125 -19.813876 1.686629 -VERTEX_SE2 258 -7.526140 -19.190499 1.778261 -VERTEX_SE2 259 -7.676213 -18.571996 1.817167 -VERTEX_SE2 260 -7.815262 -17.997419 1.807084 -VERTEX_SE2 261 -7.812802 -17.957528 1.492756 -VERTEX_SE2 262 -7.748761 -17.305023 1.497634 -VERTEX_SE2 263 -7.704459 -16.684761 1.509160 -VERTEX_SE2 264 -7.686285 -16.075835 1.603801 -VERTEX_SE2 265 -7.713685 -15.464092 1.621756 -VERTEX_SE2 266 -7.659836 -14.884512 1.489698 -VERTEX_SE2 267 -7.590637 -14.130213 1.451229 -VERTEX_SE2 268 -7.434892 -13.512524 1.330524 -VERTEX_SE2 269 -7.285843 -12.888133 1.352351 -VERTEX_SE2 270 -7.181145 -12.258185 1.457078 -VERTEX_SE2 271 -7.046662 -11.629374 1.337392 -VERTEX_SE2 272 -6.916330 -10.998172 1.394889 -VERTEX_SE2 273 -6.887272 -10.359052 1.601643 -VERTEX_SE2 274 -6.914937 -9.718617 1.621477 -VERTEX_SE2 275 -6.859410 -9.108548 1.443918 -VERTEX_SE2 276 -6.805341 -8.498288 1.552614 -VERTEX_SE2 277 -6.805090 -7.914615 1.521694 -VERTEX_SE2 278 -6.706035 -7.282637 1.426405 -VERTEX_SE2 279 -6.600956 -6.648396 1.372248 -VERTEX_SE2 280 -6.468990 -6.023366 1.375327 -VERTEX_SE2 281 -6.315271 -5.399485 1.276051 -VERTEX_SE2 282 -6.134008 -4.785899 1.316907 -VERTEX_SE2 283 -5.985222 -4.161193 1.309959 -VERTEX_SE2 284 -5.813249 -3.550243 1.337990 -VERTEX_SE2 285 -5.683475 -2.918967 1.354789 -VERTEX_SE2 286 -5.554450 -2.315719 1.403911 -VERTEX_SE2 287 -5.547714 -2.274662 1.294491 -VERTEX_SE2 288 -5.541900 -2.266525 0.674528 -VERTEX_SE2 289 -5.522271 -2.259034 0.069939 -VERTEX_SE2 290 -5.061939 -2.331133 -0.161343 -VERTEX_SE2 291 -4.422087 -2.430958 -0.151620 -VERTEX_SE2 292 -3.797538 -2.541567 -0.206901 -VERTEX_SE2 293 -3.169915 -2.679421 -0.230678 -VERTEX_SE2 294 -2.547430 -2.824259 -0.230786 -VERTEX_SE2 295 -1.930374 -2.997710 -0.344395 -VERTEX_SE2 296 -1.332265 -3.234619 -0.390911 -VERTEX_SE2 297 -0.738078 -3.478649 -0.403774 -VERTEX_SE2 298 -0.140714 -3.729757 -0.400411 -VERTEX_SE2 299 0.420679 -3.975672 -0.334750 -VERTEX_SE2 300 1.008544 -4.149557 -0.288323 -VERTEX_SE2 301 1.570135 -4.331714 -0.343252 -VERTEX_SE2 302 1.877809 -4.442855 -0.360266 -VERTEX_SE2 303 1.877809 -4.442855 -0.352308 -VERTEX_SE2 304 2.376686 -4.626875 -0.338959 -VERTEX_SE2 305 3.003258 -4.750507 -0.149877 -VERTEX_SE2 306 3.635522 -4.836601 -0.164412 -VERTEX_SE2 307 4.273138 -4.958970 -0.197471 -VERTEX_SE2 308 4.897872 -5.092864 -0.249303 -VERTEX_SE2 309 5.530724 -5.222563 -0.152307 -VERTEX_SE2 310 6.160264 -5.318301 -0.184629 -VERTEX_SE2 311 6.445365 -5.373694 -0.004883 -VERTEX_SE2 312 6.452735 -5.370466 0.998034 -VERTEX_SE2 313 6.475194 -5.146827 1.501717 -VERTEX_SE2 314 6.500598 -4.513221 1.545236 -VERTEX_SE2 315 6.514519 -3.879666 1.544361 -VERTEX_SE2 316 6.515603 -3.240166 1.596992 -VERTEX_SE2 317 6.494598 -2.602753 1.608134 -VERTEX_SE2 318 6.435460 -1.967773 1.704091 -VERTEX_SE2 319 6.377663 -1.623559 1.776803 -VERTEX_SE2 320 6.378012 -1.622652 2.750031 -VERTEX_SE2 321 6.369361 -1.625849 -2.488361 -VERTEX_SE2 322 6.365946 -1.632038 -1.487277 -VERTEX_SE2 323 6.380435 -1.647235 -0.490000 -VERTEX_SE2 324 6.388880 -1.650346 0.173199 -VERTEX_SE2 325 6.392046 -1.650203 -0.351692 -VERTEX_SE2 326 6.411655 -1.665878 -0.943866 -VERTEX_SE2 327 6.415870 -1.688728 -1.564092 -VERTEX_SE2 328 6.385292 -2.332605 -1.609333 -VERTEX_SE2 329 6.349431 -2.981039 -1.664005 -VERTEX_SE2 330 6.281461 -3.617106 -1.682877 -VERTEX_SE2 331 6.213453 -4.254537 -1.697056 -VERTEX_SE2 332 6.121245 -4.888223 -1.715483 -VERTEX_SE2 333 6.093922 -5.063750 -1.650313 -VERTEX_SE2 334 6.091827 -5.064491 -0.592795 -VERTEX_SE2 335 6.120861 -5.065606 0.404271 -VERTEX_SE2 336 6.129696 -5.059618 1.351179 -VERTEX_SE2 337 6.128718 -5.050440 2.336187 -VERTEX_SE2 338 6.132587 -5.051833 -2.965274 -VERTEX_SE2 339 6.136968 -5.047185 -1.923597 -VERTEX_SE2 340 6.142971 -5.060659 -0.911817 -VERTEX_SE2 341 6.275970 -5.101488 -0.283246 -VERTEX_SE2 342 6.910811 -5.263299 -0.183431 -VERTEX_SE2 343 7.538989 -5.324161 -0.052630 -VERTEX_SE2 344 8.173624 -5.364758 -0.062705 -VERTEX_SE2 345 8.440897 -5.378047 -0.090243 -VERTEX_SE2 346 8.445372 -5.378415 -0.737666 -VERTEX_SE2 347 8.467770 -5.462068 -1.296495 -VERTEX_SE2 348 8.621467 -6.030776 -1.313338 -VERTEX_SE2 349 8.761762 -6.631679 -1.353796 -VERTEX_SE2 350 8.929986 -7.398958 -1.373714 -VERTEX_SE2 351 8.978041 -7.665351 -1.403006 -VERTEX_SE2 352 8.978090 -7.667619 -1.691302 -VERTEX_SE2 353 8.976498 -7.670853 -2.356727 -VERTEX_SE2 354 8.981756 -7.667962 -2.960124 -VERTEX_SE2 355 8.981756 -7.667962 2.602071 -VERTEX_SE2 356 8.978992 -7.664902 2.026818 -VERTEX_SE2 357 8.977837 -7.643021 1.373746 -VERTEX_SE2 358 8.983884 -7.628618 0.758292 -VERTEX_SE2 359 8.989631 -7.624545 0.144263 -VERTEX_SE2 360 8.994126 -7.624478 -0.032038 -VERTEX_SE2 361 9.004896 -7.619118 0.887338 -VERTEX_SE2 362 9.042246 -7.366063 1.421081 -VERTEX_SE2 363 9.090049 -7.072841 1.414605 -VERTEX_SE2 364 8.954880 -6.758641 2.029221 -VERTEX_SE2 365 8.843129 -6.487231 1.532139 -VERTEX_SE2 366 8.933049 -6.331605 0.978068 -VERTEX_SE2 367 9.294391 -5.803956 0.980155 -VERTEX_SE2 368 9.641307 -5.309201 0.927211 -VERTEX_SE2 369 9.642383 -5.307246 0.668025 -VERTEX_SE2 370 9.648356 -5.305144 0.068704 -VERTEX_SE2 371 9.655229 -5.307316 -0.488502 -VERTEX_SE2 372 10.139103 -5.587085 -0.520850 -VERTEX_SE2 373 10.666751 -5.878088 -0.523177 -VERTEX_SE2 374 11.206068 -6.209159 -0.589126 -VERTEX_SE2 375 11.598593 -6.466633 -0.617897 -VERTEX_SE2 376 11.600632 -6.467490 0.080627 -VERTEX_SE2 377 11.643620 -6.425565 0.779646 -VERTEX_SE2 378 12.107190 -5.948151 0.834726 -VERTEX_SE2 379 12.510240 -5.446009 0.920455 -VERTEX_SE2 380 12.867606 -4.912917 1.040753 -VERTEX_SE2 381 12.982544 -4.709680 1.191659 -VERTEX_SE2 382 12.984649 -4.703709 2.244782 -VERTEX_SE2 383 12.988448 -4.705226 -3.058347 -VERTEX_SE2 384 12.986650 -4.707628 -2.101256 -VERTEX_SE2 385 12.982495 -4.718256 -1.078274 -VERTEX_SE2 386 13.111397 -4.840399 -0.855882 -VERTEX_SE2 387 13.631915 -5.191372 -0.527133 -VERTEX_SE2 388 14.188298 -5.499440 -0.516375 -VERTEX_SE2 389 14.722428 -5.830510 -0.596695 -VERTEX_SE2 390 14.780394 -5.868314 -0.117993 -VERTEX_SE2 391 14.643686 -5.885580 0.265004 -VERTEX_SE2 392 14.640989 -5.886114 1.312766 -VERTEX_SE2 393 14.635188 -5.855400 2.572219 -VERTEX_SE2 394 14.626068 -5.853862 -2.712409 -VERTEX_SE2 395 14.810464 -5.693039 -2.372812 -VERTEX_SE2 396 14.810464 -5.693039 -2.371562 -VERTEX_SE2 397 14.809202 -5.693678 -2.381203 -VERTEX_SE2 398 14.806796 -5.697007 -2.182098 -VERTEX_SE2 399 14.762364 -5.814932 -1.881357 -VERTEX_SE2 400 14.564819 -6.441727 -1.829942 -VERTEX_SE2 401 14.377600 -7.049960 -1.921842 -VERTEX_SE2 402 14.049396 -7.600148 -2.188305 -VERTEX_SE2 403 13.678588 -8.113304 -2.221999 -VERTEX_SE2 404 13.306297 -8.574268 -2.300702 -VERTEX_SE2 405 13.304585 -8.576945 -1.766179 -VERTEX_SE2 406 13.302371 -8.571544 -0.786819 -VERTEX_SE2 407 13.585994 -8.797369 -0.658504 -VERTEX_SE2 408 14.069560 -9.180165 -0.696311 -VERTEX_SE2 409 14.292699 -9.377696 -0.795739 -VERTEX_SE2 410 14.294947 -9.381536 -1.423249 -VERTEX_SE2 411 14.294896 -9.380539 -2.012202 -VERTEX_SE2 412 13.969601 -9.830799 -2.215053 -VERTEX_SE2 413 13.573781 -10.323212 -2.294584 -VERTEX_SE2 414 13.168212 -10.809845 -2.190851 -VERTEX_SE2 415 12.788348 -11.328437 -2.184168 -VERTEX_SE2 416 12.618672 -11.583273 -2.239720 -VERTEX_SE2 417 12.365589 -11.724804 -2.664770 -VERTEX_SE2 418 11.973570 -11.921372 -2.710238 -VERTEX_SE2 419 11.979618 -11.915306 -2.279799 -VERTEX_SE2 420 12.016291 -11.860711 -2.064037 -VERTEX_SE2 421 12.016287 -11.845604 -1.097383 -VERTEX_SE2 422 12.016575 -11.841230 0.140539 -VERTEX_SE2 423 12.021144 -11.838978 0.485675 -VERTEX_SE2 424 12.021144 -11.838978 0.485954 -VERTEX_SE2 425 12.020459 -11.840215 0.497589 -VERTEX_SE2 426 12.020459 -11.840215 0.488372 -VERTEX_SE2 427 12.049082 -11.824671 0.491399 -VERTEX_SE2 428 12.625045 -11.496891 0.485740 -VERTEX_SE2 429 13.200413 -11.202813 0.444713 -VERTEX_SE2 430 13.512701 -11.059854 0.398340 -VERTEX_SE2 431 13.491669 -11.069431 0.449977 -VERTEX_SE2 432 13.501654 -11.067336 -0.182740 -VERTEX_SE2 433 13.502954 -11.067884 -0.861102 -VERTEX_SE2 434 13.743529 -11.537644 -1.089862 -VERTEX_SE2 435 14.040604 -12.094609 -1.118566 -VERTEX_SE2 436 14.297216 -12.672347 -1.127731 -VERTEX_SE2 437 14.580075 -13.245886 -1.120167 -VERTEX_SE2 438 14.774602 -13.672697 -1.152842 -VERTEX_SE2 439 14.775466 -13.675785 -1.592980 -VERTEX_SE2 440 14.781226 -13.661057 -2.241615 -VERTEX_SE2 441 14.801541 -13.646476 -2.868987 -VERTEX_SE2 442 14.816809 -13.646193 2.779417 -VERTEX_SE2 443 14.817345 -13.647038 2.207553 -VERTEX_SE2 444 14.811569 -13.629249 1.610879 -VERTEX_SE2 445 14.815891 -13.615770 0.947107 -VERTEX_SE2 446 14.818888 -13.610711 1.145697 -VERTEX_SE2 447 14.818661 -13.580487 1.771810 -VERTEX_SE2 448 14.666760 -12.956104 1.786852 -VERTEX_SE2 449 14.494693 -12.326428 1.885308 -VERTEX_SE2 450 14.289690 -11.713731 1.896557 -VERTEX_SE2 451 14.124257 -11.267884 1.936509 -VERTEX_SE2 452 14.123115 -11.264972 1.553661 -VERTEX_SE2 453 14.123047 -11.266384 0.924731 -VERTEX_SE2 454 14.355665 -11.130372 0.497531 -VERTEX_SE2 455 14.885259 -10.850245 0.477764 -VERTEX_SE2 456 15.454443 -10.572873 0.414138 -VERTEX_SE2 457 15.786015 -10.438633 0.327481 -VERTEX_SE2 458 15.795226 -10.438701 -0.315965 -VERTEX_SE2 459 15.791330 -10.434037 -0.921822 -VERTEX_SE2 460 15.959734 -10.828507 -1.177398 -VERTEX_SE2 461 16.180144 -11.439953 -1.264281 -VERTEX_SE2 462 16.335308 -11.963645 -1.303337 -VERTEX_SE2 463 16.335291 -11.965068 -0.460067 -VERTEX_SE2 464 16.605696 -11.914838 0.220623 -VERTEX_SE2 465 17.195283 -11.771695 0.229379 -VERTEX_SE2 466 17.767104 -11.657874 0.174926 -VERTEX_SE2 467 18.369280 -11.563197 0.132707 -VERTEX_SE2 468 19.009735 -11.482106 0.096378 -VERTEX_SE2 469 19.650896 -11.453733 0.012587 -VERTEX_SE2 470 20.146853 -11.442829 0.030741 -VERTEX_SE2 471 20.150029 -11.442932 -0.170062 -VERTEX_SE2 472 20.172311 -11.454699 -0.804400 -VERTEX_SE2 473 20.175889 -11.459298 -1.446679 -VERTEX_SE2 474 20.174399 -11.466191 -2.079685 -VERTEX_SE2 475 20.170246 -11.468957 -2.655668 -VERTEX_SE2 476 20.165497 -11.470500 2.973880 -VERTEX_SE2 477 20.170525 -11.474309 2.412190 -VERTEX_SE2 478 20.175145 -11.482300 1.782778 -VERTEX_SE2 479 20.175335 -11.487697 1.326596 -VERTEX_SE2 480 20.175383 -11.489918 1.886460 -VERTEX_SE2 481 20.164295 -11.475887 2.884919 -VERTEX_SE2 482 20.162062 -11.476008 -3.063751 -VERTEX_SE2 483 20.162062 -11.476008 -3.070064 -VERTEX_SE2 484 19.701970 -11.512371 -3.035533 -VERTEX_SE2 485 19.076677 -11.593317 -3.038332 -VERTEX_SE2 486 18.439371 -11.653164 -3.029341 -VERTEX_SE2 487 17.807280 -11.746714 -3.001669 -VERTEX_SE2 488 17.172291 -11.839127 -2.966212 -VERTEX_SE2 489 16.696147 -11.938931 -2.936476 -VERTEX_SE2 490 16.692490 -11.941051 -2.452793 -VERTEX_SE2 491 16.691616 -11.944932 -1.519098 -VERTEX_SE2 492 16.776617 -12.175290 -1.217149 -VERTEX_SE2 493 16.974062 -12.765556 -1.272900 -VERTEX_SE2 494 17.150447 -13.373462 -1.334510 -VERTEX_SE2 495 17.352115 -13.978346 -1.223707 -VERTEX_SE2 496 17.581624 -14.577196 -1.102707 -VERTEX_SE2 497 17.847299 -15.159575 -1.186084 -VERTEX_SE2 498 18.054425 -15.760767 -1.285551 -VERTEX_SE2 499 18.205483 -16.382164 -1.300921 -VERTEX_SE2 500 18.520086 -16.898620 -0.969638 -VERTEX_SE2 501 18.848430 -17.411482 -1.035455 -VERTEX_SE2 502 19.181866 -18.047877 -1.113614 -VERTEX_SE2 503 19.382829 -18.470616 -1.125142 -VERTEX_SE2 504 19.382968 -18.471669 -0.242877 -VERTEX_SE2 505 19.636847 -18.373822 0.396037 -VERTEX_SE2 506 20.222463 -18.152233 0.250781 -VERTEX_SE2 507 20.842906 -18.012083 0.178603 -VERTEX_SE2 508 21.473389 -17.912172 0.170945 -VERTEX_SE2 509 22.107555 -17.811551 0.119274 -VERTEX_SE2 510 22.477732 -17.773275 0.109324 -VERTEX_SE2 511 22.480191 -17.776579 1.000327 -VERTEX_SE2 512 22.479151 -17.767197 2.221724 -VERTEX_SE2 513 22.480923 -17.768589 -3.088724 -VERTEX_SE2 514 22.392210 -17.797083 -2.856452 -VERTEX_SE2 515 21.763760 -17.994866 -2.851524 -VERTEX_SE2 516 21.150540 -18.168893 -2.831780 -VERTEX_SE2 517 20.555780 -18.389856 -2.799728 -VERTEX_SE2 518 19.960512 -18.610158 -2.771638 -VERTEX_SE2 519 19.439217 -18.815169 -2.783788 -VERTEX_SE2 520 19.254408 -18.883689 -2.378354 -VERTEX_SE2 521 19.253544 -18.891468 -1.358862 -VERTEX_SE2 522 19.446557 -19.274210 -1.088837 -VERTEX_SE2 523 19.743533 -19.846628 -1.102099 -VERTEX_SE2 524 20.002482 -20.395419 -1.149751 -VERTEX_SE2 525 20.240546 -20.920829 -1.130979 -VERTEX_SE2 526 20.481533 -21.478440 -1.194565 -VERTEX_SE2 527 20.701007 -22.071838 -1.233424 -VERTEX_SE2 528 20.883349 -22.686071 -1.313266 -VERTEX_SE2 529 21.048790 -23.294125 -1.338785 -VERTEX_SE2 530 21.186548 -23.922294 -1.365903 -VERTEX_SE2 531 21.294513 -24.545568 -1.442267 -VERTEX_SE2 532 21.321981 -24.753969 -1.388289 -VERTEX_SE2 533 21.331696 -24.762834 -0.381514 -VERTEX_SE2 534 21.336665 -24.762597 0.290307 -VERTEX_SE2 535 21.336665 -24.762597 0.291645 -VERTEX_SE2 536 21.667408 -24.621679 0.424610 -VERTEX_SE2 537 22.237616 -24.383793 0.368660 -VERTEX_SE2 538 22.783578 -24.188298 0.343558 -VERTEX_SE2 539 23.392496 -23.994630 0.281055 -VERTEX_SE2 540 24.012651 -23.846863 0.190656 -VERTEX_SE2 541 24.616500 -23.677520 0.355935 -VERTEX_SE2 542 25.198874 -23.413487 0.414186 -VERTEX_SE2 543 25.308230 -23.366534 0.386937 -VERTEX_SE2 544 25.311458 -23.363477 1.030295 -VERTEX_SE2 545 25.308953 -23.348021 2.106974 -VERTEX_SE2 546 25.370099 -23.384281 2.942044 -VERTEX_SE2 547 25.364196 -23.389842 -2.309543 -VERTEX_SE2 548 25.360603 -23.401304 -1.377667 -VERTEX_SE2 549 25.371198 -23.418104 -0.563163 -VERTEX_SE2 550 25.376228 -23.423323 -1.037842 -VERTEX_SE2 551 25.379648 -23.436304 -1.657979 -VERTEX_SE2 552 25.377719 -23.441024 -2.284180 -VERTEX_SE2 553 25.238021 -23.493388 -2.855731 -VERTEX_SE2 554 24.607784 -23.677279 -2.888859 -VERTEX_SE2 555 23.993431 -23.860841 -2.797492 -VERTEX_SE2 556 23.396241 -24.105763 -2.659796 -VERTEX_SE2 557 22.822728 -24.393516 -2.702744 -VERTEX_SE2 558 22.275040 -24.674698 -2.623823 -VERTEX_SE2 559 22.270077 -24.676821 -3.034381 -VERTEX_SE2 560 22.270077 -24.676821 2.743761 -VERTEX_SE2 561 22.260080 -24.676351 -3.038320 -VERTEX_SE2 562 22.096754 -24.820650 -2.399936 -VERTEX_SE2 563 21.592502 -25.154311 -2.594747 -VERTEX_SE2 564 21.053412 -25.498706 -2.548499 -VERTEX_SE2 565 20.537425 -25.832798 -2.600465 -VERTEX_SE2 566 20.532490 -25.835035 -1.796294 -VERTEX_SE2 567 20.533426 -25.833987 -0.940208 -VERTEX_SE2 568 20.891388 -26.294540 -0.894012 -VERTEX_SE2 569 21.276856 -26.790552 -0.946159 -VERTEX_SE2 570 21.649420 -27.310717 -0.950967 -VERTEX_SE2 571 21.849361 -27.596250 -0.994364 -VERTEX_SE2 572 21.840333 -27.646492 -1.231717 -VERTEX_SE2 573 21.868880 -27.724009 -0.949072 -VERTEX_SE2 574 21.877216 -27.725452 0.006744 -VERTEX_SE2 575 21.894503 -27.715687 1.016050 -VERTEX_SE2 576 21.893519 -27.715892 2.040189 -VERTEX_SE2 577 21.885895 -27.713246 3.008947 -VERTEX_SE2 578 21.866274 -27.704702 2.643177 -VERTEX_SE2 579 21.298924 -27.387881 2.657278 -VERTEX_SE2 580 20.716909 -27.105551 2.731333 -VERTEX_SE2 581 20.125767 -26.856860 2.772637 -VERTEX_SE2 582 19.523535 -26.633599 2.814640 -VERTEX_SE2 583 19.213810 -26.536403 2.892766 -VERTEX_SE2 584 19.210319 -26.535584 -2.550218 -VERTEX_SE2 585 18.716874 -26.893765 -2.433208 -VERTEX_SE2 586 18.264978 -27.304159 -2.336932 -VERTEX_SE2 587 17.844050 -27.776684 -2.312675 -VERTEX_SE2 588 17.404102 -28.246717 -2.309353 -VERTEX_SE2 589 17.007668 -28.742973 -2.229083 -VERTEX_SE2 590 16.625023 -29.262060 -2.155304 -VERTEX_SE2 591 16.286319 -29.796280 -2.152683 -VERTEX_SE2 592 15.939159 -30.336565 -2.150686 -VERTEX_SE2 593 15.791190 -30.564791 -1.938181 -VERTEX_SE2 594 15.794250 -30.581455 -0.945084 -VERTEX_SE2 595 16.194292 -30.997078 -0.704760 -VERTEX_SE2 596 16.636104 -31.366417 -0.722635 -VERTEX_SE2 597 17.103989 -31.796447 -0.756770 -VERTEX_SE2 598 17.538795 -32.215917 -0.796372 -VERTEX_SE2 599 17.800839 -32.499098 -0.859831 -VERTEX_SE2 600 17.808423 -32.504032 -0.045074 -VERTEX_SE2 601 17.830600 -32.493623 0.983541 -VERTEX_SE2 602 17.834247 -32.474305 2.007952 -VERTEX_SE2 603 17.607884 -32.374428 2.788384 -VERTEX_SE2 604 17.046943 -32.100473 2.547892 -VERTEX_SE2 605 16.511286 -31.761753 2.630512 -VERTEX_SE2 606 15.949994 -31.464096 2.672598 -VERTEX_SE2 607 15.464785 -31.153778 2.591564 -VERTEX_SE2 608 15.140800 -30.969820 2.636022 -VERTEX_SE2 609 15.131468 -30.966068 2.972317 -VERTEX_SE2 610 15.122044 -30.973580 -2.378251 -VERTEX_SE2 611 14.678938 -31.406105 -2.375287 -VERTEX_SE2 612 14.313602 -31.741587 -2.372510 -VERTEX_SE2 613 14.338061 -31.720011 -1.908805 -VERTEX_SE2 614 14.338698 -31.726674 -0.870968 -VERTEX_SE2 615 14.768768 -31.977559 -0.566453 -VERTEX_SE2 616 15.311642 -32.305648 -0.617609 -VERTEX_SE2 617 15.760313 -32.746217 -0.834639 -VERTEX_SE2 618 16.193436 -33.214065 -0.824521 -VERTEX_SE2 619 16.601337 -33.656810 -0.825989 -VERTEX_SE2 620 17.000072 -34.120401 -0.922318 -VERTEX_SE2 621 16.788426 -33.839458 -0.973924 -VERTEX_SE2 622 16.630958 -33.597689 -1.048966 -VERTEX_SE2 623 16.629619 -33.594321 -1.586115 -VERTEX_SE2 624 16.631309 -33.591662 -2.228805 -VERTEX_SE2 625 16.624999 -33.596266 -2.887438 -VERTEX_SE2 626 16.617480 -33.597475 2.777619 -VERTEX_SE2 627 16.349285 -33.346497 2.366813 -VERTEX_SE2 628 15.919513 -32.868045 2.265896 -VERTEX_SE2 629 15.485805 -32.395397 2.336946 -VERTEX_SE2 630 15.046318 -31.973583 2.422035 -VERTEX_SE2 631 14.646636 -31.639639 2.442255 -VERTEX_SE2 632 14.643929 -31.637238 3.118464 -VERTEX_SE2 633 14.589597 -31.712326 -2.089116 -VERTEX_SE2 634 14.266192 -32.277649 -2.063889 -VERTEX_SE2 635 13.925936 -32.819825 -2.201180 -VERTEX_SE2 636 13.554818 -33.335756 -2.220264 -VERTEX_SE2 637 13.147645 -33.820778 -2.320948 -VERTEX_SE2 638 12.855265 -34.121161 -2.351695 -VERTEX_SE2 639 12.424371 -34.596136 -2.272930 -VERTEX_SE2 640 11.985578 -35.070016 -2.342632 -VERTEX_SE2 641 11.552638 -35.529379 -2.352802 -VERTEX_SE2 642 11.112191 -35.949492 -2.400353 -VERTEX_SE2 643 10.731895 -36.277399 -2.465381 -VERTEX_SE2 644 10.729340 -36.284109 -1.701776 -VERTEX_SE2 645 10.869715 -36.451460 -0.744873 -VERTEX_SE2 646 11.382066 -36.817597 -0.447477 -VERTEX_SE2 647 11.978978 -37.021611 -0.156322 -VERTEX_SE2 648 12.612056 -37.054234 0.012089 -VERTEX_SE2 649 13.205551 -37.119561 -0.322437 -VERTEX_SE2 650 13.749716 -37.452144 -0.576811 -VERTEX_SE2 651 13.876986 -37.522144 0.229018 -VERTEX_SE2 652 13.879256 -37.519914 1.262617 -VERTEX_SE2 653 13.872372 -37.502026 2.301465 -VERTEX_SE2 654 13.581181 -37.403460 2.818912 -VERTEX_SE2 655 13.048482 -37.219840 2.815600 -VERTEX_SE2 656 12.474722 -37.010334 2.804516 -VERTEX_SE2 657 11.844104 -36.899464 3.083365 -VERTEX_SE2 658 11.204549 -36.869297 3.076381 -VERTEX_SE2 659 10.709556 -36.810477 3.005332 -VERTEX_SE2 660 10.703219 -36.809512 -2.677502 -VERTEX_SE2 661 10.609221 -37.000250 -2.023854 -VERTEX_SE2 662 10.337163 -37.568215 -1.954223 -VERTEX_SE2 663 10.139552 -38.159090 -1.853215 -VERTEX_SE2 664 10.135226 -38.165415 -1.314901 -VERTEX_SE2 665 10.170577 -38.183639 -0.436050 -VERTEX_SE2 666 10.743964 -38.430041 -0.401598 -VERTEX_SE2 667 11.280508 -38.651643 -0.399484 -VERTEX_SE2 668 11.840921 -38.888748 -0.376305 -VERTEX_SE2 669 12.435464 -39.121854 -0.404047 -VERTEX_SE2 670 13.006910 -39.403898 -0.466305 -VERTEX_SE2 671 13.282608 -39.541377 -0.414044 -VERTEX_SE2 672 12.769426 -39.275957 -0.506105 -VERTEX_SE2 673 12.545541 -39.138628 -0.639846 -VERTEX_SE2 674 12.542473 -39.133012 -1.294945 -VERTEX_SE2 675 12.544204 -39.117309 -1.910166 -VERTEX_SE2 676 12.549939 -39.112303 -2.540226 -VERTEX_SE2 677 12.562810 -39.108340 3.120460 -VERTEX_SE2 678 12.373080 -39.013683 2.669222 -VERTEX_SE2 679 11.859571 -38.764102 2.729006 -VERTEX_SE2 680 11.438738 -38.641448 3.069344 -VERTEX_SE2 681 10.825052 -38.579071 3.043558 -VERTEX_SE2 682 10.191857 -38.531816 3.093190 -VERTEX_SE2 683 9.891918 -38.522270 -3.028351 -VERTEX_SE2 684 9.884198 -38.525775 -1.959011 -VERTEX_SE2 685 9.893810 -38.971112 -1.519535 -VERTEX_SE2 686 9.862204 -39.454619 -1.666211 -VERTEX_SE2 687 9.788467 -40.084236 -1.702984 -VERTEX_SE2 688 9.681861 -40.709183 -1.716237 -VERTEX_SE2 689 9.605655 -41.306367 -1.689887 -VERTEX_SE2 690 9.568647 -41.883115 -1.616935 -VERTEX_SE2 691 9.547797 -42.313693 -1.645939 -VERTEX_SE2 692 9.547338 -42.304642 -1.113930 -VERTEX_SE2 693 9.542348 -42.304320 -0.077783 -VERTEX_SE2 694 9.537355 -42.304058 0.754255 -VERTEX_SE2 695 9.537558 -42.302067 1.551174 -VERTEX_SE2 696 9.534841 -42.288755 2.572731 -VERTEX_SE2 697 9.523163 -42.279299 2.318206 -VERTEX_SE2 698 9.451431 -41.889589 1.660453 -VERTEX_SE2 699 9.371393 -41.248546 1.772879 -VERTEX_SE2 700 9.226326 -40.627573 1.824729 -VERTEX_SE2 701 9.049173 -40.073867 1.881983 -VERTEX_SE2 702 8.937709 -39.503989 1.764440 -VERTEX_SE2 703 8.820349 -38.895181 1.796436 -VERTEX_SE2 704 8.652821 -38.280287 1.812200 -VERTEX_SE2 705 8.469818 -37.662927 1.876685 -VERTEX_SE2 706 8.352303 -37.160636 1.599049 -VERTEX_SE2 707 8.353815 -37.156710 1.065513 -VERTEX_SE2 708 8.350433 -37.159057 0.410972 -VERTEX_SE2 709 8.285591 -37.179408 0.069240 -VERTEX_SE2 710 8.271325 -37.178203 -0.506017 -VERTEX_SE2 711 8.272256 -37.177853 -1.117758 -VERTEX_SE2 712 8.271421 -37.172816 -1.692402 -VERTEX_SE2 713 8.082147 -37.631033 -1.986694 -VERTEX_SE2 714 7.781665 -38.118818 -2.191858 -VERTEX_SE2 715 7.436278 -38.650634 -2.086627 -VERTEX_SE2 716 7.101560 -39.196310 -2.222833 -VERTEX_SE2 717 6.732648 -39.710336 -2.136309 -VERTEX_SE2 718 6.249991 -40.323558 -2.309481 -VERTEX_SE2 719 5.841996 -40.813346 -2.233064 -VERTEX_SE2 720 5.439595 -41.301667 -2.290296 -VERTEX_SE2 721 5.032639 -41.793490 -2.219961 -VERTEX_SE2 722 5.017459 -41.813716 -2.151150 -VERTEX_SE2 723 5.020505 -41.823419 -1.052793 -VERTEX_SE2 724 5.031411 -41.829247 0.024382 -VERTEX_SE2 725 5.041952 -41.823519 0.663795 -VERTEX_SE2 726 5.186097 -41.536150 1.121855 -VERTEX_SE2 727 5.441157 -40.953385 1.154429 -VERTEX_SE2 728 5.708786 -40.374749 1.157456 -VERTEX_SE2 729 5.942746 -39.779976 1.216830 -VERTEX_SE2 730 6.141286 -39.181998 1.255908 -VERTEX_SE2 731 6.337960 -38.578088 1.269244 -VERTEX_SE2 732 6.370731 -38.458677 1.718146 -VERTEX_SE2 733 6.369310 -38.458803 2.719136 -VERTEX_SE2 734 5.849568 -38.267284 2.798462 -VERTEX_SE2 735 5.287090 -38.058027 2.767482 -VERTEX_SE2 736 5.282360 -38.056088 -2.913613 -VERTEX_SE2 737 5.275781 -38.066121 -2.109607 -VERTEX_SE2 738 4.978024 -38.585828 -2.089531 -VERTEX_SE2 739 4.620518 -39.102322 -2.146106 -VERTEX_SE2 740 4.357016 -39.553955 -2.081799 -VERTEX_SE2 741 4.357431 -39.553064 -2.448926 -VERTEX_SE2 742 4.347527 -39.558696 -3.099199 -VERTEX_SE2 743 4.342558 -39.557580 2.569578 -VERTEX_SE2 744 4.336078 -39.550170 2.032393 -VERTEX_SE2 745 4.335980 -39.547933 1.441244 -VERTEX_SE2 746 4.573919 -39.080308 0.968419 -VERTEX_SE2 747 4.940154 -38.557294 0.934212 -VERTEX_SE2 748 5.302092 -38.106400 0.867038 -VERTEX_SE2 749 5.345738 -38.052022 1.298344 -VERTEX_SE2 750 5.342230 -38.047361 2.229268 -VERTEX_SE2 751 5.083575 -37.967511 2.872246 -VERTEX_SE2 752 4.493353 -37.766114 2.770046 -VERTEX_SE2 753 3.897446 -37.540993 2.834236 -VERTEX_SE2 754 3.312644 -37.288841 2.673214 -VERTEX_SE2 755 2.737782 -37.020556 2.749498 -VERTEX_SE2 756 2.174638 -36.731322 2.558054 -VERTEX_SE2 757 1.632084 -36.401074 2.656949 -VERTEX_SE2 758 1.085717 -36.165115 2.783429 -VERTEX_SE2 759 0.594312 -35.911010 2.644238 -VERTEX_SE2 760 0.049628 -35.662561 2.758388 -VERTEX_SE2 761 0.027544 -35.652794 2.941901 -VERTEX_SE2 762 0.026506 -35.653762 -2.331733 -VERTEX_SE2 763 -0.256374 -36.186295 -2.039623 -VERTEX_SE2 764 -0.462375 -36.581571 -2.067616 -VERTEX_SE2 765 -0.464849 -36.589741 -1.429340 -VERTEX_SE2 766 -0.458452 -36.594227 -0.430727 -VERTEX_SE2 767 -0.455458 -36.594045 0.498539 -VERTEX_SE2 768 -0.452643 -36.586959 1.219631 -VERTEX_SE2 769 -0.342345 -35.973305 1.375930 -VERTEX_SE2 770 -0.198947 -35.353053 1.308266 -VERTEX_SE2 771 -0.182607 -35.284960 1.867596 -VERTEX_SE2 772 -0.189467 -35.280006 2.733492 -VERTEX_SE2 773 -0.733580 -35.074067 2.818690 -VERTEX_SE2 774 -1.260348 -34.911848 2.840206 -VERTEX_SE2 775 -1.264437 -34.911446 -2.599985 -VERTEX_SE2 776 -1.363996 -35.119485 -1.984041 -VERTEX_SE2 777 -1.608279 -35.704951 -1.925618 -VERTEX_SE2 778 -1.822302 -36.299953 -1.947124 -VERTEX_SE2 779 -2.047440 -36.894571 -1.929730 -VERTEX_SE2 780 -2.139238 -37.102862 -2.060860 -VERTEX_SE2 781 -2.145402 -37.110533 -2.636030 -VERTEX_SE2 782 -2.437259 -37.141405 -3.067105 -VERTEX_SE2 783 -3.047824 -37.175458 -3.068514 -VERTEX_SE2 784 -3.287219 -37.194026 -3.090710 -VERTEX_SE2 785 -3.295317 -37.191509 2.638474 -VERTEX_SE2 786 -3.298918 -37.188857 2.048008 -VERTEX_SE2 787 -3.298039 -37.190654 1.555448 -VERTEX_SE2 788 -3.294912 -37.183493 0.924060 -VERTEX_SE2 789 -3.288075 -37.179210 0.370053 -VERTEX_SE2 790 -2.810788 -37.026036 0.301996 -VERTEX_SE2 791 -2.219090 -36.811172 0.399811 -VERTEX_SE2 792 -1.939736 -36.661187 1.102401 -VERTEX_SE2 793 -1.678873 -36.103497 1.143891 -VERTEX_SE2 794 -1.429675 -35.516238 1.211715 -VERTEX_SE2 795 -1.418449 -35.555793 1.634956 -VERTEX_SE2 796 -1.418085 -35.571576 2.285912 -VERTEX_SE2 797 -1.667269 -35.397655 2.541335 -VERTEX_SE2 798 -2.187789 -35.048694 2.588216 -VERTEX_SE2 799 -2.741194 -34.735022 2.650579 -VERTEX_SE2 800 -3.307545 -34.448654 2.720068 -VERTEX_SE2 801 -3.902341 -34.216184 2.802965 -VERTEX_SE2 802 -4.505679 -34.024853 2.857856 -VERTEX_SE2 803 -5.085804 -33.763383 2.689794 -VERTEX_SE2 804 -5.662771 -33.497682 2.743005 -VERTEX_SE2 805 -6.261534 -33.279468 2.816554 -VERTEX_SE2 806 -6.840015 -33.097367 2.855463 -VERTEX_SE2 807 -7.023476 -33.048729 -3.135502 -VERTEX_SE2 808 -7.026802 -33.056076 -2.177404 -VERTEX_SE2 809 -7.281242 -33.594521 -2.029487 -VERTEX_SE2 810 -7.631595 -34.121859 -2.166451 -VERTEX_SE2 811 -7.962424 -34.637653 -2.167266 -VERTEX_SE2 812 -7.965744 -34.642706 -1.427803 -VERTEX_SE2 813 -7.950068 -34.658744 -0.442321 -VERTEX_SE2 814 -7.946972 -34.656894 0.560269 -VERTEX_SE2 815 -7.933401 -34.640091 1.489098 -VERTEX_SE2 816 -7.934735 -34.631584 2.177465 -VERTEX_SE2 817 -7.937452 -34.619639 1.515677 -VERTEX_SE2 818 -7.834770 -34.423652 1.032291 -VERTEX_SE2 819 -7.526530 -33.907724 1.048948 -VERTEX_SE2 820 -7.214976 -33.390015 1.010358 -VERTEX_SE2 821 -6.920703 -32.916256 1.028688 -VERTEX_SE2 822 -6.919798 -32.914444 1.802164 -VERTEX_SE2 823 -6.939217 -32.906160 2.735576 -VERTEX_SE2 824 -7.523314 -32.665202 2.656921 -VERTEX_SE2 825 -8.081019 -32.349406 2.627743 -VERTEX_SE2 826 -8.677362 -32.112604 2.791147 -VERTEX_SE2 827 -9.263427 -31.871967 2.742741 -VERTEX_SE2 828 -9.860125 -31.638542 2.778381 -VERTEX_SE2 829 -10.419194 -31.408540 2.739767 -VERTEX_SE2 830 -10.979677 -31.164322 2.732511 -VERTEX_SE2 831 -11.504644 -30.911305 2.686863 -VERTEX_SE2 832 -12.017274 -30.666706 2.684351 -VERTEX_SE2 833 -12.247951 -30.553587 2.823368 -VERTEX_SE2 834 -12.252957 -30.553546 -2.400674 -VERTEX_SE2 835 -12.257933 -30.565509 -1.619664 -VERTEX_SE2 836 -12.250730 -30.581427 -0.672599 -VERTEX_SE2 837 -12.229702 -30.583353 0.368164 -VERTEX_SE2 838 -12.218778 -30.573267 1.234031 -VERTEX_SE2 839 -12.217846 -30.572182 1.017315 -VERTEX_SE2 840 -12.205638 -30.563033 0.457221 -VERTEX_SE2 841 -12.186893 -30.559209 -0.157128 -VERTEX_SE2 842 -11.953603 -30.647983 -0.386378 -VERTEX_SE2 843 -11.425785 -30.867365 -0.387868 -VERTEX_SE2 844 -10.836985 -31.098902 -0.394843 -VERTEX_SE2 845 -10.253132 -31.347696 -0.408511 -VERTEX_SE2 846 -9.662939 -31.589256 -0.405754 -VERTEX_SE2 847 -9.086083 -31.851482 -0.431429 -VERTEX_SE2 848 -8.940268 -31.914214 0.052760 -VERTEX_SE2 849 -8.937884 -31.910733 0.981233 -VERTEX_SE2 850 -8.846299 -31.550192 1.337597 -VERTEX_SE2 851 -8.670841 -30.931185 1.246884 -VERTEX_SE2 852 -8.462808 -30.325041 1.262739 -VERTEX_SE2 853 -8.280607 -29.742061 1.236186 -VERTEX_SE2 854 -8.076287 -29.173781 1.239399 -VERTEX_SE2 855 -7.887388 -28.592768 1.238706 -VERTEX_SE2 856 -7.661883 -27.996043 1.201622 -VERTEX_SE2 857 -7.435282 -27.400366 1.195491 -VERTEX_SE2 858 -7.181293 -26.816604 1.144746 -VERTEX_SE2 859 -6.975384 -26.352422 1.141340 -VERTEX_SE2 860 -6.975931 -26.349425 2.005134 -VERTEX_SE2 861 -7.070368 -26.315710 2.933845 -VERTEX_SE2 862 -7.703743 -26.156922 2.877954 -VERTEX_SE2 863 -8.318477 -25.984947 2.848940 -VERTEX_SE2 864 -8.894038 -25.792766 2.850044 -VERTEX_SE2 865 -9.480815 -25.646186 2.911227 -VERTEX_SE2 866 -10.042232 -25.502772 2.913209 -VERTEX_SE2 867 -10.195104 -25.469004 3.098784 -VERTEX_SE2 868 -10.213426 -25.478919 -2.146684 -VERTEX_SE2 869 -10.215119 -25.492408 -1.155028 -VERTEX_SE2 870 -10.214332 -25.494499 -0.141931 -VERTEX_SE2 871 -10.213017 -25.492991 0.844964 -VERTEX_SE2 872 -10.207635 -25.487052 1.539524 -VERTEX_SE2 873 -10.203844 -25.478644 0.974807 -VERTEX_SE2 874 -10.191816 -25.467641 0.422903 -VERTEX_SE2 875 -10.178876 -25.463448 -0.155631 -VERTEX_SE2 876 -9.623904 -25.632105 -0.308985 -VERTEX_SE2 877 -9.083899 -25.830525 -0.389894 -VERTEX_SE2 878 -8.876969 -25.899540 -0.000578 -VERTEX_SE2 879 -8.229916 -25.842173 0.054103 -VERTEX_SE2 880 -7.774580 -25.835590 -0.054479 -VERTEX_SE2 881 -7.817933 -25.833359 -0.045761 -VERTEX_SE2 882 -7.174094 -25.927843 -0.286988 -VERTEX_SE2 883 -6.578127 -26.179696 -0.472812 -VERTEX_SE2 884 -6.240032 -26.360890 -0.535840 -VERTEX_SE2 885 -6.234259 -26.360820 0.054937 -VERTEX_SE2 886 -6.226914 -26.353411 1.089009 -VERTEX_SE2 887 -6.223165 -26.344082 2.116331 -VERTEX_SE2 888 -6.228613 -26.341527 3.115899 -VERTEX_SE2 889 -6.233959 -26.343474 -2.255359 -VERTEX_SE2 890 -6.384262 -26.679298 -1.975677 -VERTEX_SE2 891 -6.638596 -27.241278 -2.042104 -VERTEX_SE2 892 -6.943864 -27.800951 -2.074085 -VERTEX_SE2 893 -7.222566 -28.381189 -1.832614 -VERTEX_SE2 894 -7.306963 -29.019629 -1.606544 -VERTEX_SE2 895 -7.223579 -29.638727 -1.302591 -VERTEX_SE2 896 -7.217764 -29.658031 -0.821107 -VERTEX_SE2 897 -6.741710 -29.937142 -0.546756 -VERTEX_SE2 898 -6.211746 -30.256253 -0.528856 -VERTEX_SE2 899 -5.784445 -30.515658 -0.593948 -VERTEX_SE2 900 -5.213641 -30.892109 -0.593973 -VERTEX_SE2 901 -4.743942 -31.232862 -0.655646 -VERTEX_SE2 902 -4.223039 -31.613559 -0.627847 -VERTEX_SE2 903 -3.820846 -31.901295 -0.669947 -VERTEX_SE2 904 -3.815849 -31.904352 0.045079 -VERTEX_SE2 905 -3.263955 -31.843907 0.103405 -VERTEX_SE2 906 -2.639473 -31.754495 0.172374 -VERTEX_SE2 907 -2.019027 -31.639234 0.167356 -VERTEX_SE2 908 -1.390615 -31.531155 0.192144 -VERTEX_SE2 909 -0.774159 -31.409648 0.174319 -VERTEX_SE2 910 -0.621113 -31.427046 -0.195369 -VERTEX_SE2 911 -0.014492 -31.535247 -0.134304 -VERTEX_SE2 912 0.586470 -31.609953 -0.123402 -VERTEX_SE2 913 1.163336 -31.678045 -0.116774 -VERTEX_SE2 914 1.788505 -31.757735 -0.141346 -VERTEX_SE2 915 2.338537 -31.842759 -0.178606 -VERTEX_SE2 916 2.533087 -31.771891 0.368065 -VERTEX_SE2 917 3.132539 -31.589985 0.113800 -VERTEX_SE2 918 3.652121 -31.566106 0.038805 -VERTEX_SE2 919 4.280972 -31.547492 -0.003530 -VERTEX_SE2 920 4.910829 -31.560372 -0.019732 -VERTEX_SE2 921 5.544993 -31.581438 -0.071106 -VERTEX_SE2 922 6.178198 -31.632867 -0.095979 -VERTEX_SE2 923 6.782469 -31.705277 -0.156454 -VERTEX_SE2 924 7.306767 -31.788672 -0.137071 -VERTEX_SE2 925 7.316981 -31.785338 0.373816 -VERTEX_SE2 926 7.870711 -31.558218 0.392795 -VERTEX_SE2 927 8.451492 -31.319547 0.372320 -VERTEX_SE2 928 9.048413 -31.086577 0.366185 -VERTEX_SE2 929 9.642832 -30.858817 0.368104 -VERTEX_SE2 930 10.245448 -30.641674 0.289261 -VERTEX_SE2 931 10.860347 -30.480136 0.213162 -VERTEX_SE2 932 11.487766 -30.354631 0.158095 -VERTEX_SE2 933 12.113942 -30.270301 0.098329 -VERTEX_SE2 934 12.615349 -30.217794 0.093110 -VERTEX_SE2 935 12.620802 -30.215724 0.789990 -VERTEX_SE2 936 12.621200 -30.213495 1.735352 -VERTEX_SE2 937 12.506666 -29.673239 1.798228 -VERTEX_SE2 938 12.424893 -29.074588 1.624736 -VERTEX_SE2 939 12.382460 -28.442898 1.652316 -VERTEX_SE2 940 12.358048 -27.806499 1.569594 -VERTEX_SE2 941 12.373922 -27.170573 1.518896 -VERTEX_SE2 942 12.403040 -26.619551 1.526103 -VERTEX_SE2 943 12.433212 -25.976160 1.617626 -VERTEX_SE2 944 12.409024 -25.337583 1.563948 -VERTEX_SE2 945 12.425512 -24.701834 1.511054 -VERTEX_SE2 946 12.438281 -24.522578 1.577626 -VERTEX_SE2 947 12.297194 -24.116617 1.909253 -VERTEX_SE2 948 12.106012 -23.569193 1.887290 -VERTEX_SE2 949 11.964410 -22.979201 1.728071 -VERTEX_SE2 950 11.889816 -22.346608 1.484552 -VERTEX_SE2 951 11.958985 -21.712798 1.409506 -VERTEX_SE2 952 12.033066 -21.078819 1.656468 -VERTEX_SE2 953 11.960841 -20.446967 1.733995 -VERTEX_SE2 954 11.870328 -19.821157 1.720988 -VERTEX_SE2 955 11.840813 -19.182342 1.553709 -VERTEX_SE2 956 11.856933 -18.549356 1.547775 -VERTEX_SE2 957 11.900583 -17.914412 1.456986 -VERTEX_SE2 958 11.980717 -17.309521 1.428422 -VERTEX_SE2 959 12.082916 -16.713544 1.360362 -VERTEX_SE2 960 12.156646 -16.109750 1.448732 -VERTEX_SE2 961 12.237458 -15.513097 1.428643 -VERTEX_SE2 962 12.263723 -14.879685 1.501648 -VERTEX_SE2 963 12.237575 -14.246674 1.697303 -VERTEX_SE2 964 12.165060 -13.610953 1.639856 -VERTEX_SE2 965 12.127557 -12.978752 1.638378 -VERTEX_SE2 966 12.104648 -12.566160 1.581231 -VERTEX_SE2 967 12.101573 -12.558682 2.440323 -VERTEX_SE2 968 12.096363 -12.554972 -2.940213 -VERTEX_SE2 969 12.095404 -12.555254 -2.878924 -VERTEX_SE2 970 12.095404 -12.555254 -2.879037 -VERTEX_SE2 971 11.746171 -12.650445 -2.877325 -VERTEX_SE2 972 11.152947 -12.816606 -2.870194 -VERTEX_SE2 973 10.545291 -12.861291 3.130276 -VERTEX_SE2 974 9.909280 -12.851322 -3.119743 -VERTEX_SE2 975 9.273543 -12.871196 -3.113968 -VERTEX_SE2 976 8.636290 -12.899229 -3.099985 -VERTEX_SE2 977 7.998434 -12.914064 -3.126289 -VERTEX_SE2 978 7.362084 -12.942484 -3.081464 -VERTEX_SE2 979 6.727323 -12.975809 -3.095884 -VERTEX_SE2 980 6.088538 -13.025797 -3.052420 -VERTEX_SE2 981 5.456362 -13.081213 -3.049818 -VERTEX_SE2 982 4.878233 -13.153816 -3.010682 -VERTEX_SE2 983 4.276746 -13.227843 -3.016388 -VERTEX_SE2 984 3.677713 -13.322859 -2.961936 -VERTEX_SE2 985 3.077590 -13.433257 -2.944700 -VERTEX_SE2 986 2.454219 -13.573733 -2.911877 -VERTEX_SE2 987 1.831643 -13.709396 -2.952510 -VERTEX_SE2 988 1.208691 -13.826413 -2.942854 -VERTEX_SE2 989 0.584787 -13.958913 -2.938610 -VERTEX_SE2 990 -0.036556 -14.080445 -2.953180 -VERTEX_SE2 991 -0.661927 -14.209275 -2.932525 -VERTEX_SE2 992 -1.260572 -14.319726 -2.969755 -VERTEX_SE2 993 -1.888936 -14.441757 -2.941917 -VERTEX_SE2 994 -2.487885 -14.558335 -2.956980 -VERTEX_SE2 995 -3.058043 -14.679439 -2.909164 -VERTEX_SE2 996 -3.642056 -14.831099 -2.881732 -VERTEX_SE2 997 -4.264284 -14.974196 -2.946069 -VERTEX_SE2 998 -4.649607 -15.053751 -2.924278 -VERTEX_SE2 999 -4.656689 -15.065859 -2.206631 -VERTEX_SE2 1000 -4.965388 -15.623496 -2.058280 -VERTEX_SE2 1001 -5.318062 -16.158055 -2.379913 -VERTEX_SE2 1002 -5.750626 -16.618291 -2.287924 -VERTEX_SE2 1003 -6.211655 -17.061941 -2.468589 -VERTEX_SE2 1004 -6.694526 -17.473495 -2.405819 -VERTEX_SE2 1005 -7.127979 -17.892536 -2.338545 -VERTEX_SE2 1006 -7.533434 -18.348428 -2.255884 -VERTEX_SE2 1007 -7.879092 -18.809574 -2.158490 -VERTEX_SE2 1008 -8.039907 -19.059004 -1.992248 -VERTEX_SE2 1009 -8.036682 -19.246221 -1.535293 -VERTEX_SE2 1010 -8.014039 -19.881169 -1.488904 -VERTEX_SE2 1011 -7.963340 -20.508886 -1.536571 -VERTEX_SE2 1012 -7.940438 -21.138959 -1.498741 -VERTEX_SE2 1013 -7.851368 -21.773013 -1.387987 -VERTEX_SE2 1014 -7.817932 -22.401411 -1.567544 -VERTEX_SE2 1015 -7.777031 -23.038795 -1.486212 -VERTEX_SE2 1016 -7.720006 -23.818187 -1.481750 -VERTEX_SE2 1017 -7.654678 -24.418258 -1.539186 -VERTEX_SE2 1018 -7.642739 -24.996103 -1.532186 -VERTEX_SE2 1019 -7.587869 -25.606880 -1.473009 -VERTEX_SE2 1020 -7.542280 -26.239004 -1.487341 -VERTEX_SE2 1021 -7.489141 -26.876264 -1.528391 -VERTEX_SE2 1022 -7.485868 -26.947021 -1.536042 -VERTEX_SE2 1023 -7.484661 -26.958141 -1.449942 -VERTEX_SE2 1024 -7.409816 -27.598995 -1.424093 -VERTEX_SE2 1025 -7.295845 -28.237813 -1.407035 -VERTEX_SE2 1026 -7.266936 -28.415848 -1.400374 -VERTEX_SE2 1027 -7.264213 -28.420001 -0.485709 -VERTEX_SE2 1028 -7.082225 -28.411802 0.076550 -VERTEX_SE2 1029 -6.501990 -28.370465 0.054524 -VERTEX_SE2 1030 -5.899285 -28.359129 -0.023135 -VERTEX_SE2 1031 -5.264812 -28.390632 -0.074570 -VERTEX_SE2 1032 -4.627186 -28.437737 -0.067955 -VERTEX_SE2 1033 -3.988448 -28.495960 -0.110249 -VERTEX_SE2 1034 -3.830826 -28.515420 -0.320119 -VERTEX_SE2 1035 -3.824146 -28.521449 -0.932344 -VERTEX_SE2 1036 -3.823513 -28.519287 -1.496421 -VERTEX_SE2 1037 -3.907010 -28.904450 -1.730668 -VERTEX_SE2 1038 -3.935283 -29.554657 -1.636155 -VERTEX_SE2 1039 -4.005708 -30.240003 -1.664719 -VERTEX_SE2 1040 -4.005908 -30.238599 -1.026894 -VERTEX_SE2 1041 -4.005975 -30.237093 0.002348 -VERTEX_SE2 1042 -3.997565 -30.235981 1.002167 -VERTEX_SE2 1043 -3.992361 -30.227413 1.983494 -VERTEX_SE2 1044 -3.977187 -30.235628 2.989203 -VERTEX_SE2 1045 -3.975026 -30.236085 2.875365 -VERTEX_SE2 1046 -3.962563 -30.241673 2.490123 -VERTEX_SE2 1047 -3.950447 -30.258328 1.934722 -VERTEX_SE2 1048 -3.961452 -29.893794 1.621975 -VERTEX_SE2 1049 -4.005576 -29.246490 1.639262 -VERTEX_SE2 1050 -4.077126 -28.603234 1.715064 -VERTEX_SE2 1051 -4.144972 -27.965909 1.641633 -VERTEX_SE2 1052 -4.202505 -27.357765 1.700204 -VERTEX_SE2 1053 -4.242648 -26.957178 1.653111 -VERTEX_SE2 1054 -4.243150 -26.953596 2.434770 -VERTEX_SE2 1055 -4.236475 -26.953142 -2.756219 -VERTEX_SE2 1056 -4.223234 -26.936933 -1.722386 -VERTEX_SE2 1057 -4.227338 -26.917941 -0.719127 -VERTEX_SE2 1058 -4.226406 -26.918299 0.214528 -VERTEX_SE2 1059 -3.650549 -26.814433 0.137155 -VERTEX_SE2 1060 -3.017058 -26.728657 0.145123 -VERTEX_SE2 1061 -2.566458 -26.676490 0.026280 -VERTEX_SE2 1062 -2.547934 -26.680711 -0.534242 -VERTEX_SE2 1063 -2.549662 -26.679283 -1.128091 -VERTEX_SE2 1064 -2.494849 -26.986675 -1.390919 -VERTEX_SE2 1065 -2.341227 -27.585706 -1.267035 -VERTEX_SE2 1066 -2.181042 -28.156133 -1.321458 -VERTEX_SE2 1067 -2.176008 -28.167308 -0.885641 -VERTEX_SE2 1068 -2.171515 -28.170280 -0.208552 -VERTEX_SE2 1069 -2.166440 -28.170156 -0.036102 -VERTEX_SE2 1070 -2.158515 -28.171216 -0.571481 -VERTEX_SE2 1071 -1.728296 -28.528563 -0.696733 -VERTEX_SE2 1072 -1.327331 -28.874102 -0.763594 -VERTEX_SE2 1073 -1.328212 -28.872089 -1.271112 -VERTEX_SE2 1074 -1.186974 -29.415680 -1.303788 -VERTEX_SE2 1075 -1.030631 -30.004733 -1.285588 -VERTEX_SE2 1076 -1.010783 -30.067165 -0.872464 -VERTEX_SE2 1077 -0.990761 -30.068841 -0.051064 -VERTEX_SE2 1078 -0.349127 -30.042537 -0.001133 -VERTEX_SE2 1079 0.251517 -30.025133 0.066104 -VERTEX_SE2 1080 0.250521 -30.025208 -0.411064 -VERTEX_SE2 1081 0.710773 -30.299114 -0.528492 -VERTEX_SE2 1082 0.906064 -30.410866 -0.239852 -VERTEX_SE2 1083 0.893124 -30.419745 0.817776 -VERTEX_SE2 1084 0.892987 -30.410710 1.808848 -VERTEX_SE2 1085 0.877326 -30.393872 2.785923 -VERTEX_SE2 1086 0.299041 -30.311718 3.014416 -VERTEX_SE2 1087 -0.304600 -30.219651 3.001192 -VERTEX_SE2 1088 -0.901080 -30.145015 2.983479 -VERTEX_SE2 1089 -0.971780 -30.133164 2.884958 -VERTEX_SE2 1090 -0.973114 -30.132686 2.263087 -VERTEX_SE2 1091 -1.258802 -29.655877 2.082828 -VERTEX_SE2 1092 -1.565146 -29.096531 2.065302 -VERTEX_SE2 1093 -1.831232 -28.571344 1.997715 -VERTEX_SE2 1094 -1.829673 -28.566075 1.590728 -VERTEX_SE2 1095 -1.822889 -28.540013 0.997280 -VERTEX_SE2 1096 -1.811806 -28.530957 0.422426 -VERTEX_SE2 1097 -1.452357 -28.453682 0.204872 -VERTEX_SE2 1098 -0.806231 -28.342284 0.146524 -VERTEX_SE2 1099 -0.207476 -28.279115 0.048016 -VERTEX_SE2 1100 -0.187763 -28.277509 0.407350 -VERTEX_SE2 1101 0.022408 -28.110407 0.670039 -VERTEX_SE2 1102 0.031962 -28.106738 0.272412 -VERTEX_SE2 1103 0.639388 -27.958876 0.236513 -VERTEX_SE2 1104 1.275605 -27.810841 0.206756 -VERTEX_SE2 1105 1.907140 -27.685723 0.186870 -VERTEX_SE2 1106 2.395193 -27.596899 0.159641 -VERTEX_SE2 1107 2.402168 -27.592873 0.412775 -VERTEX_SE2 1108 2.958139 -27.302229 0.418732 -VERTEX_SE2 1109 3.567153 -27.081855 0.312557 -VERTEX_SE2 1110 4.153225 -26.897988 0.261047 -VERTEX_SE2 1111 4.708902 -26.834648 0.157605 -VERTEX_SE2 1112 5.252614 -26.628115 0.500855 -VERTEX_SE2 1113 5.822286 -26.335525 0.403408 -VERTEX_SE2 1114 6.390291 -26.092686 0.418701 -VERTEX_SE2 1115 6.922867 -25.852780 0.400788 -VERTEX_SE2 1116 7.511667 -25.579208 0.435293 -VERTEX_SE2 1117 7.600383 -25.536976 0.305438 -VERTEX_SE2 1118 7.602610 -25.537159 -0.306056 -VERTEX_SE2 1119 7.600066 -25.535572 -0.741091 -VERTEX_SE2 1120 8.016891 -25.978082 -0.806559 -VERTEX_SE2 1121 8.158671 -26.132496 -0.927181 -VERTEX_SE2 1122 8.158918 -26.136107 -1.481463 -VERTEX_SE2 1123 8.202585 -26.722199 -1.466214 -VERTEX_SE2 1124 8.300951 -27.294198 -1.369439 -VERTEX_SE2 1125 8.427601 -27.927776 -1.357617 -VERTEX_SE2 1126 8.448389 -28.015008 -1.020843 -VERTEX_SE2 1127 8.448797 -28.014120 0.029251 -VERTEX_SE2 1128 8.446703 -28.014495 1.009931 -VERTEX_SE2 1129 8.448367 -28.002253 2.018659 -VERTEX_SE2 1130 8.179683 -27.484075 2.030704 -VERTEX_SE2 1131 8.020715 -26.725314 1.595581 -VERTEX_SE2 1132 8.007311 -26.085207 1.574275 -VERTEX_SE2 1133 8.027146 -25.500857 1.533912 -VERTEX_SE2 1134 8.037276 -25.234473 1.435052 -VERTEX_SE2 1135 8.041135 -25.225813 0.875236 -VERTEX_SE2 1136 8.045119 -25.226197 0.309468 -VERTEX_SE2 1137 8.350426 -25.173106 0.123314 -VERTEX_SE2 1138 9.001574 -25.103517 0.105516 -VERTEX_SE2 1139 9.526482 -25.047001 0.028463 -VERTEX_SE2 1140 9.524465 -25.046883 -0.458084 -VERTEX_SE2 1141 9.526460 -25.047900 -1.013894 -VERTEX_SE2 1142 9.607360 -25.436185 -1.384688 -VERTEX_SE2 1143 9.757086 -26.048918 -1.299305 -VERTEX_SE2 1144 9.911339 -26.642229 -1.333595 -VERTEX_SE2 1145 10.051242 -27.237116 -1.362737 -VERTEX_SE2 1146 10.176396 -27.833614 -1.346612 -VERTEX_SE2 1147 10.348017 -28.420420 -1.277963 -VERTEX_SE2 1148 10.522608 -29.006300 -1.265737 -VERTEX_SE2 1149 10.546607 -29.077059 -0.762568 -VERTEX_SE2 1150 10.540261 -29.074848 0.225763 -VERTEX_SE2 1151 10.553029 -29.065002 1.213625 -VERTEX_SE2 1152 10.547143 -29.052737 2.249842 -VERTEX_SE2 1153 10.539006 -29.045893 -3.036684 -VERTEX_SE2 1154 10.279724 -29.230068 -2.499597 -VERTEX_SE2 1155 9.768765 -29.605741 -2.552981 -VERTEX_SE2 1156 9.258910 -29.939074 -2.546643 -VERTEX_SE2 1157 8.791360 -30.283703 -2.440027 -VERTEX_SE2 1158 8.293580 -30.637927 -2.573320 -VERTEX_SE2 1159 7.766648 -30.929430 -2.672819 -VERTEX_SE2 1160 7.188507 -31.197997 -2.815407 -VERTEX_SE2 1161 6.565573 -31.348475 -2.910356 -VERTEX_SE2 1162 5.944969 -31.503360 -2.857013 -VERTEX_SE2 1163 5.325232 -31.686006 -2.866763 -VERTEX_SE2 1164 4.712273 -31.878415 -2.786199 -VERTEX_SE2 1165 4.215997 -32.064635 -2.806894 -VERTEX_SE2 1166 4.211816 -32.067381 -2.313330 -VERTEX_SE2 1167 4.212397 -32.068184 -1.368491 -VERTEX_SE2 1168 4.215987 -32.071852 -0.457146 -VERTEX_SE2 1169 4.277451 -32.051395 0.371897 -VERTEX_SE2 1170 4.829613 -31.826964 0.380849 -VERTEX_SE2 1171 5.421192 -31.572841 0.415111 -VERTEX_SE2 1172 5.981253 -31.335402 0.355461 -VERTEX_SE2 1173 6.575628 -31.109741 0.367274 -VERTEX_SE2 1174 7.180922 -30.893036 0.301697 -VERTEX_SE2 1175 7.792370 -30.719051 0.271366 -VERTEX_SE2 1176 8.409541 -30.550025 0.230936 -VERTEX_SE2 1177 9.024476 -30.404549 0.236005 -VERTEX_SE2 1178 9.647424 -30.266268 0.192062 -VERTEX_SE2 1179 10.265276 -30.135370 0.224605 -VERTEX_SE2 1180 10.529482 -30.074919 0.220636 -VERTEX_SE2 1181 10.535574 -30.072977 1.058820 -VERTEX_SE2 1182 10.524149 -30.016817 1.779823 -VERTEX_SE2 1183 10.386448 -29.400748 1.808327 -VERTEX_SE2 1184 10.207851 -28.783707 1.881370 -VERTEX_SE2 1185 10.025332 -28.173735 1.852352 -VERTEX_SE2 1186 9.825515 -27.568409 1.922073 -VERTEX_SE2 1187 9.605142 -26.948356 1.888299 -VERTEX_SE2 1188 9.398682 -26.350552 1.909670 -VERTEX_SE2 1189 9.199117 -25.746642 1.931343 -VERTEX_SE2 1190 9.094298 -25.467233 1.939338 -VERTEX_SE2 1191 9.094967 -25.468452 2.859515 -VERTEX_SE2 1192 9.008026 -25.506399 -2.626152 -VERTEX_SE2 1193 8.461781 -25.785098 -2.758330 -VERTEX_SE2 1194 7.923840 -26.019541 -2.716510 -VERTEX_SE2 1195 7.347445 -26.288871 -2.664510 -VERTEX_SE2 1196 6.780364 -26.579172 -2.837709 -VERTEX_SE2 1197 6.180419 -26.774947 -2.826952 -VERTEX_SE2 1198 5.583380 -27.006326 -2.569041 -VERTEX_SE2 1199 5.035569 -27.311853 -2.784743 -VERTEX_SE2 1200 4.440692 -27.555930 -2.716631 -VERTEX_SE2 1201 3.868153 -27.824566 -2.661795 -VERTEX_SE2 1202 3.307623 -28.126760 -2.639885 -VERTEX_SE2 1203 2.784459 -28.432197 -2.731103 -VERTEX_SE2 1204 2.199445 -28.603325 -2.879452 -VERTEX_SE2 1205 1.642686 -28.765416 -2.851240 -VERTEX_SE2 1206 1.070524 -28.960362 -2.791568 -VERTEX_SE2 1207 0.458097 -29.116494 -2.879433 -VERTEX_SE2 1208 -0.157073 -29.300653 -2.816562 -VERTEX_SE2 1209 -0.759388 -29.505640 -2.820223 -VERTEX_SE2 1210 -1.349503 -29.728180 -2.801535 -VERTEX_SE2 1211 -1.962101 -29.894344 -2.871965 -VERTEX_SE2 1212 -2.570554 -30.072460 -2.819915 -VERTEX_SE2 1213 -3.165262 -30.298664 -2.744821 -VERTEX_SE2 1214 -3.865282 -30.564861 -2.965631 -VERTEX_SE2 1215 -4.462533 -30.687173 -2.905497 -VERTEX_SE2 1216 -4.996128 -30.904402 -2.687065 -VERTEX_SE2 1217 -5.548071 -31.150837 -2.705863 -VERTEX_SE2 1218 -6.079683 -31.407444 -2.756532 -VERTEX_SE2 1219 -6.680544 -31.658044 -2.725934 -VERTEX_SE2 1220 -7.206328 -31.919274 -2.635196 -VERTEX_SE2 1221 -7.204948 -31.917957 -3.087949 -VERTEX_SE2 1222 -7.218143 -31.913501 2.597929 -VERTEX_SE2 1223 -7.226028 -31.904243 2.038302 -VERTEX_SE2 1224 -7.405412 -31.351278 1.855394 -VERTEX_SE2 1225 -7.584838 -30.751843 1.869158 -VERTEX_SE2 1226 -7.616041 -30.650012 1.852427 -VERTEX_SE2 1227 -7.616041 -30.650012 1.851765 -EDGE_SE2 0 1 0.000000 0.000000 -0.000642 11.111271 -0.249667 0.000000 399.999840 0.000000 2496.793089 -EDGE_SE2 1 2 0.000000 0.000000 -0.000538 11.111224 -0.209222 0.000000 399.999887 0.000000 2497.312169 -EDGE_SE2 2 3 0.011003 -0.000962 -0.002382 5898.288051 69216.359995 0.000000 813797.504789 0.000000 2488.132420 -EDGE_SE2 3 4 0.630039 -0.007981 -0.003882 11.129692 2.115033 0.000000 251.862638 0.000000 2480.702442 -EDGE_SE2 4 5 0.055010 -0.000106 -0.091282 274.195567 -2936.281177 0.000000 32782.895793 0.000000 2099.259006 -EDGE_SE2 5 6 0.004599 -0.003282 -0.797272 97512.798743 -544001.571431 0.000000 3035217.135802 0.000000 773.949086 -EDGE_SE2 6 7 -0.019286 0.007346 -0.615537 14562.788488 -56611.041711 0.000000 220247.580117 0.000000 957.869160 -EDGE_SE2 7 8 -0.022127 0.006629 -0.613534 18833.046432 -56332.014355 0.000000 168606.730383 0.000000 960.248785 -EDGE_SE2 8 9 -0.021138 0.006734 -0.638070 21300.350989 -62226.140556 0.000000 181891.375069 0.000000 931.697855 -EDGE_SE2 9 10 -0.012731 0.002805 -0.614531 88261.230144 -210099.692864 0.000000 500201.721901 0.000000 959.062847 -EDGE_SE2 10 11 -0.004733 0.001621 -0.674279 455441.532335 -1269769.465818 0.000000 3540210.494348 0.000000 891.834664 -EDGE_SE2 11 12 0.000807 -0.000605 -0.649474 3209.969094 -560838.218926 0.000000 98328698.890567 0.000000 918.859395 -EDGE_SE2 12 13 0.017463 -0.005758 -0.659498 33087.416467 -93212.317327 0.000000 262692.689594 0.000000 907.792399 -EDGE_SE2 13 14 0.024327 -0.007794 -0.608239 13237.887356 -43033.186144 0.000000 140019.158071 0.000000 966.582281 -EDGE_SE2 14 15 0.008665 -0.002176 -0.524928 94975.828833 -331589.466697 0.000000 1157826.108740 0.000000 1075.081358 -EDGE_SE2 15 16 0.300013 0.002659 -0.018692 11.945981 -30.290393 0.000000 1110.094393 0.000000 2409.096616 -EDGE_SE2 16 17 0.618894 -0.006590 -0.031228 11.216951 -5.142163 0.000000 260.940781 0.000000 2350.880836 -EDGE_SE2 17 18 0.644399 0.002908 -0.010177 11.160679 -3.373944 0.000000 240.764207 0.000000 2449.881377 -EDGE_SE2 18 19 0.657345 -0.022632 -0.048576 11.155234 -3.115574 0.000000 231.108306 0.000000 2273.736754 -EDGE_SE2 19 20 0.644638 0.002939 0.010643 11.119605 1.396220 0.000000 240.626947 0.000000 2447.622654 -EDGE_SE2 20 21 0.643768 0.040732 0.117695 11.791489 12.469629 0.000000 239.648403 0.000000 2001.213355 -EDGE_SE2 21 22 0.641586 -0.019837 0.022682 11.775592 12.387374 0.000000 242.038634 0.000000 2390.335076 -EDGE_SE2 22 23 0.642142 0.071066 0.114072 11.114499 0.879730 0.000000 239.576730 0.000000 2014.250547 -EDGE_SE2 23 24 0.642775 -0.026027 -0.050939 11.136380 -2.413434 0.000000 241.616002 0.000000 2263.523413 -EDGE_SE2 24 25 0.645593 0.014612 0.008602 11.156105 -3.207460 0.000000 239.760661 0.000000 2457.538661 -EDGE_SE2 25 26 0.612608 -0.020554 -0.040332 11.122880 -1.732513 0.000000 266.150269 0.000000 2309.915505 -EDGE_SE2 26 27 0.617908 0.014448 0.016074 11.124480 -1.830524 0.000000 261.753630 0.000000 2421.527089 -EDGE_SE2 27 28 0.585700 -0.007698 -0.004161 11.133727 2.517886 0.000000 291.434633 0.000000 2479.324138 -EDGE_SE2 28 29 0.378831 0.003536 0.031104 11.435983 14.920983 0.000000 696.414325 0.000000 2351.446301 -EDGE_SE2 29 30 0.007031 -0.000919 -0.531386 303620.480641 -715329.968352 0.000000 1685390.466875 0.000000 1066.033037 -EDGE_SE2 30 31 0.172814 -0.047579 -0.267833 11.113242 2.570601 0.000000 3112.523002 0.000000 1555.306207 -EDGE_SE2 31 32 0.643089 -0.013458 -0.018151 11.112884 0.639397 0.000000 241.693017 0.000000 2411.657469 -EDGE_SE2 32 33 0.641505 0.012210 0.053138 11.380647 7.899680 0.000000 242.638725 0.000000 2254.080602 -EDGE_SE2 33 34 0.642958 0.012818 0.023470 11.113996 0.815809 0.000000 241.800271 0.000000 2386.655713 -EDGE_SE2 34 35 0.642138 0.012026 0.036992 11.188281 4.224347 0.000000 242.355561 0.000000 2324.819270 -EDGE_SE2 35 36 0.646989 -0.005904 -0.005774 11.113668 0.763126 0.000000 238.872355 0.000000 2471.378132 -EDGE_SE2 36 37 0.422413 0.003265 0.010657 11.115820 1.608217 0.000000 560.397185 0.000000 2447.554843 -EDGE_SE2 37 38 0.004147 -0.000362 -0.316561 298816.394178 -1278713.788579 0.000000 5472166.543975 0.000000 1442.308081 -EDGE_SE2 38 39 0.031682 -0.005795 -0.166903 29.991712 1348.903464 0.000000 96382.012864 0.000000 1835.990780 -EDGE_SE2 39 40 0.628745 0.004932 0.020565 11.150244 3.076040 0.000000 252.904983 0.000000 2400.262103 -EDGE_SE2 40 41 0.654706 -0.007135 -0.028394 11.179108 -3.886059 0.000000 233.200483 0.000000 2363.855583 -EDGE_SE2 41 42 0.615233 -0.005892 -0.018963 11.133406 -2.375163 0.000000 264.145862 0.000000 2407.815356 -EDGE_SE2 42 43 0.646018 -0.015551 -0.036078 11.144050 -2.742429 0.000000 239.441473 0.000000 2328.922864 -EDGE_SE2 43 44 0.644889 -0.007273 -0.032646 11.215795 -4.898393 0.000000 240.317651 0.000000 2344.428944 -EDGE_SE2 44 45 0.642617 -0.019475 -0.055423 11.256800 -5.797160 0.000000 241.788650 0.000000 2244.330959 -EDGE_SE2 45 46 0.643577 -0.008674 -0.019276 11.118853 -1.335200 0.000000 241.382966 0.000000 2406.336795 -EDGE_SE2 46 47 0.643124 -0.007411 -0.041538 11.318826 -6.918261 0.000000 241.534792 0.000000 2304.569286 -EDGE_SE2 47 48 0.647528 -0.005000 -0.006399 11.111509 0.300792 0.000000 238.482697 0.000000 2468.309505 -EDGE_SE2 48 49 0.643552 -0.004657 -0.050649 11.544947 -9.986829 0.000000 241.006497 0.000000 2264.773140 -EDGE_SE2 49 50 0.613650 -0.021431 -0.044298 11.133509 -2.385631 0.000000 265.211665 0.000000 2292.403781 -EDGE_SE2 50 51 0.614419 0.001282 -0.030028 11.372766 -8.144597 0.000000 264.630129 0.000000 2356.361658 -EDGE_SE2 51 52 0.612275 -0.025515 -0.077517 11.439261 -9.144885 0.000000 265.960759 0.000000 2153.236570 -EDGE_SE2 52 53 0.589010 0.013532 0.011867 11.145260 -3.075254 0.000000 288.053971 0.000000 2441.704725 -EDGE_SE2 53 54 0.549942 -0.015900 -0.062397 11.469128 -10.685144 0.000000 330.013480 0.000000 2214.962293 -EDGE_SE2 54 55 -0.007990 -0.000400 0.004985 3173.514815 -70220.700546 0.000000 1559251.248529 0.000000 2475.260146 -EDGE_SE2 55 56 0.031075 -0.000065 0.010280 26.926592 1279.597558 0.000000 103540.680562 0.000000 2449.381862 -EDGE_SE2 56 57 0.661651 0.003067 -0.011448 11.167314 -3.494319 0.000000 228.362912 0.000000 2443.728134 -EDGE_SE2 57 58 0.651514 0.005099 0.046426 11.445370 8.655456 0.000000 235.239298 0.000000 2283.089649 -EDGE_SE2 58 59 0.644894 -0.007287 -0.023143 11.143280 -2.715787 0.000000 240.386652 0.000000 2388.181524 -EDGE_SE2 59 60 0.641666 0.006484 0.022061 11.144235 2.770355 0.000000 242.816230 0.000000 2393.240674 -EDGE_SE2 60 61 0.644092 -0.013088 -0.049766 11.310377 -6.764558 0.000000 240.749901 0.000000 2268.584724 -EDGE_SE2 61 62 0.610542 0.010202 0.039027 11.239154 5.735944 0.000000 268.065336 0.000000 2315.721577 -EDGE_SE2 62 63 0.615687 -0.014578 -0.037143 11.156928 -3.401279 0.000000 263.609811 0.000000 2324.142368 -EDGE_SE2 63 64 0.614457 0.023223 0.060765 11.244984 5.822514 0.000000 264.348682 0.000000 2221.783030 -EDGE_SE2 64 65 0.613126 -0.015622 -0.054905 11.331684 -7.492495 0.000000 265.618387 0.000000 2246.535610 -EDGE_SE2 65 66 0.642083 0.005221 -0.012134 11.206140 -4.688688 0.000000 242.448428 0.000000 2440.416656 -EDGE_SE2 66 67 0.644032 -0.000439 -0.003525 11.112971 -0.654045 0.000000 241.091541 0.000000 2482.467756 -EDGE_SE2 67 68 0.480969 0.003132 -0.018150 11.367193 -10.381901 0.000000 432.006963 0.000000 2411.662206 -EDGE_SE2 68 69 0.010004 -0.000372 -0.158022 14522.704416 -119457.061100 0.000000 983362.087562 0.000000 1864.259601 -EDGE_SE2 69 70 0.007781 -0.000530 -0.680912 544035.164858 -773651.719241 0.000000 1100214.270443 0.000000 884.810063 -EDGE_SE2 70 71 -0.005768 0.002595 -0.648935 125743.539339 -546312.647396 0.000000 2373762.361377 0.000000 919.460202 -EDGE_SE2 71 72 0.000000 0.000000 -0.011790 11.165166 -4.584575 0.000000 399.945945 0.000000 2442.076380 -EDGE_SE2 72 73 0.003590 -0.000252 -0.067514 59.238767 19279.280557 0.000000 7723027.108236 0.000000 2193.777612 -EDGE_SE2 73 74 0.003377 -0.001263 -0.627884 547328.125180 -1977782.419372 0.000000 7146917.196299 0.000000 943.393977 -EDGE_SE2 74 75 0.001263 0.000666 -0.356531 27289611.550657 -24363663.505902 0.000000 21751450.848890 0.000000 1358.565441 -EDGE_SE2 75 76 0.014525 0.005731 0.687334 38537.927166 119650.043523 0.000000 371599.899935 0.000000 878.087699 -EDGE_SE2 76 77 0.012722 0.001600 0.229532 6624.040036 63075.527086 0.000000 601638.946912 0.000000 1653.713745 -EDGE_SE2 77 78 0.282617 0.095127 0.333053 11.189119 9.319517 0.000000 1124.510027 0.000000 1406.840866 -EDGE_SE2 78 79 0.636095 0.008032 -0.007625 11.207888 -4.778031 0.000000 247.011069 0.000000 2462.306663 -EDGE_SE2 79 80 0.646911 -0.030410 -0.077284 11.319895 -6.885926 0.000000 238.216160 0.000000 2154.168095 -EDGE_SE2 80 81 0.639697 -0.008063 -0.028569 11.170556 -3.722959 0.000000 244.273973 0.000000 2363.051282 -EDGE_SE2 81 82 0.643152 -0.022848 -0.050289 11.161415 -3.403565 0.000000 241.398318 0.000000 2266.325966 -EDGE_SE2 82 83 0.640259 -0.000745 -0.019030 11.185422 -4.158907 0.000000 243.868413 0.000000 2407.498744 -EDGE_SE2 83 84 0.642213 -0.028725 -0.071607 11.278239 -6.209353 0.000000 241.809620 0.000000 2177.051366 -EDGE_SE2 84 85 0.642807 -0.004171 0.013937 11.207431 4.714878 0.000000 241.906303 0.000000 2431.743719 -EDGE_SE2 85 86 0.729089 -0.020666 -0.079989 11.582543 -9.118938 0.000000 187.499441 0.000000 2143.389493 -EDGE_SE2 86 87 0.611674 -0.018501 -0.038112 11.126983 -2.015351 0.000000 267.015944 0.000000 2319.805566 -EDGE_SE2 87 88 0.363613 -0.001497 -0.025112 11.439512 -15.640496 0.000000 756.007915 0.000000 2379.016060 -EDGE_SE2 88 89 0.022194 0.007878 0.405075 748.400424 11505.714792 0.000000 179562.707200 0.000000 1266.312242 -EDGE_SE2 89 90 0.645333 -0.002553 -0.026790 11.230485 -5.227171 0.000000 239.999401 0.000000 2371.246746 -EDGE_SE2 90 91 0.653796 0.009633 0.053106 11.438992 8.540445 0.000000 233.567599 0.000000 2254.217590 -EDGE_SE2 91 92 0.637138 -0.022076 -0.111746 12.505303 -18.044302 0.000000 244.649107 0.000000 2022.687812 -EDGE_SE2 92 93 0.640542 -0.048819 -0.097619 11.218480 -4.981269 0.000000 242.212560 0.000000 2075.089230 -EDGE_SE2 93 94 0.639915 -0.019948 -0.040363 11.130823 -2.142336 0.000000 243.948392 0.000000 2309.777849 -EDGE_SE2 94 95 0.643361 0.058181 0.138081 11.634914 10.928304 0.000000 239.112551 0.000000 1930.161562 -EDGE_SE2 95 96 0.638452 0.002717 0.020015 11.169275 3.690420 0.000000 245.262988 0.000000 2402.851280 -EDGE_SE2 96 97 0.615807 -0.026362 -0.075608 11.382661 -8.269572 0.000000 262.946629 0.000000 2160.886525 -EDGE_SE2 97 98 0.610633 -0.021924 -0.063326 11.304343 -7.040699 0.000000 267.649507 0.000000 2211.093675 -EDGE_SE2 98 99 0.729556 0.014258 0.041080 11.193076 3.804772 0.000000 187.727053 0.000000 2306.597420 -EDGE_SE2 99 100 0.585696 -0.025742 -0.058133 11.167610 -3.975845 0.000000 290.892636 0.000000 2232.849702 -EDGE_SE2 100 101 0.637186 0.067759 0.095665 11.135663 -2.388768 0.000000 243.522972 0.000000 2082.497224 -EDGE_SE2 101 102 0.646556 -0.024441 -0.094339 11.838827 -12.853666 0.000000 238.145742 0.000000 2087.546965 -EDGE_SE2 102 103 0.640410 0.007435 0.107121 13.227361 22.089363 0.000000 241.679335 0.000000 2039.622675 -EDGE_SE2 103 104 0.644201 -0.014336 -0.024966 11.112805 -0.623830 0.000000 240.845479 0.000000 2379.693860 -EDGE_SE2 104 105 0.644960 0.019675 0.055427 11.253454 5.708380 0.000000 240.033921 0.000000 2244.313947 -EDGE_SE2 105 106 0.633327 -0.017685 -0.046638 11.194517 -4.454694 0.000000 249.035067 0.000000 2282.164848 -EDGE_SE2 106 107 0.647889 -0.007117 0.003228 11.156976 3.226972 0.000000 238.156676 0.000000 2483.937815 -EDGE_SE2 107 108 0.418841 0.012880 0.059022 11.557560 15.782621 0.000000 569.050200 0.000000 2229.102528 -EDGE_SE2 108 109 0.012870 -0.002240 -0.453333 45067.256701 -156114.787673 0.000000 540932.368601 0.000000 1183.612533 -EDGE_SE2 109 110 0.014179 -0.004043 -0.597121 45347.441263 -137112.106751 0.000000 414683.621097 0.000000 980.086413 -EDGE_SE2 110 111 0.096952 -0.046958 -0.510989 42.005133 -514.703903 0.000000 8586.236292 0.000000 1095.008280 -EDGE_SE2 111 112 0.662640 0.007778 0.052084 11.463505 8.729519 0.000000 227.358980 0.000000 2258.599235 -EDGE_SE2 112 113 0.647895 0.015723 0.044858 11.207376 4.673389 0.000000 237.990065 0.000000 2289.947176 -EDGE_SE2 113 114 0.639175 0.001049 0.034783 11.367673 7.738365 0.000000 244.514402 0.000000 2334.755667 -EDGE_SE2 114 115 0.643430 0.022360 0.055827 11.213458 4.852193 0.000000 241.150882 0.000000 2242.613753 -EDGE_SE2 115 116 0.763396 0.014434 0.039419 11.178609 3.289916 0.000000 171.464543 0.000000 2313.975233 -EDGE_SE2 116 117 0.634411 0.022615 0.090058 11.812568 12.875464 0.000000 247.444399 0.000000 2103.976068 -EDGE_SE2 117 118 0.031050 -0.001921 -0.343244 7981.193322 -27566.715976 0.000000 95358.161435 0.000000 1385.575483 -EDGE_SE2 118 119 0.498463 -0.026118 -0.058682 11.126767 -2.471741 0.000000 401.352854 0.000000 2230.534528 -EDGE_SE2 119 120 0.638636 0.019165 0.084147 11.796072 12.637674 0.000000 244.278828 0.000000 2126.981262 -EDGE_SE2 120 121 0.608883 0.022686 0.061480 11.262812 6.257264 0.000000 269.206349 0.000000 2218.790906 -EDGE_SE2 121 122 0.610979 0.006491 0.049908 11.507132 10.075663 0.000000 267.458443 0.000000 2267.971114 -EDGE_SE2 122 123 0.214919 0.006655 -0.018770 16.427458 -106.823784 0.000000 2157.570163 0.000000 2408.727735 -EDGE_SE2 123 124 0.224600 -0.105868 -0.436366 11.138335 6.622207 0.000000 1621.948711 0.000000 1211.740925 -EDGE_SE2 124 125 0.625260 -0.011376 -0.017225 11.111340 0.236437 0.000000 255.702505 0.000000 2416.050226 -EDGE_SE2 125 126 0.630905 0.083897 0.196975 12.098786 15.227356 0.000000 245.877115 0.000000 1744.897213 -EDGE_SE2 126 127 0.635911 -0.003633 -0.003082 11.112746 0.621349 0.000000 247.280806 0.000000 2484.660949 -EDGE_SE2 127 128 0.644637 0.020444 0.034153 11.112488 0.561814 0.000000 240.398060 0.000000 2337.601172 -EDGE_SE2 128 129 0.638546 -0.014303 -0.035619 11.152032 -3.094288 0.000000 245.089747 0.000000 2330.987741 -EDGE_SE2 129 130 0.640721 0.025473 0.048893 11.130575 2.125338 0.000000 243.187509 0.000000 2272.362609 -EDGE_SE2 130 131 0.641480 0.013548 0.083601 12.014929 14.445918 0.000000 242.003477 0.000000 2129.125269 -EDGE_SE2 131 132 0.641305 -0.014532 -0.055040 11.354232 -7.504896 0.000000 242.779959 0.000000 2245.960726 -EDGE_SE2 132 133 0.581934 -0.015905 -0.042094 11.173057 -4.193601 0.000000 295.010721 0.000000 2302.110777 -EDGE_SE2 133 134 0.610231 0.001805 -0.024124 11.299860 -6.968043 0.000000 268.350792 0.000000 2383.608476 -EDGE_SE2 134 135 0.579655 -0.017960 -0.033069 11.112368 -0.599782 0.000000 297.332685 0.000000 2342.509439 -EDGE_SE2 135 136 0.642725 0.014632 0.012040 11.137646 -2.474784 0.000000 241.922944 0.000000 2440.870018 -EDGE_SE2 136 137 0.634138 -0.020053 -0.051400 11.204026 -4.694831 0.000000 248.333522 0.000000 2261.538905 -EDGE_SE2 137 138 0.640742 0.028547 0.068532 11.244796 5.567274 0.000000 242.959321 0.000000 2189.600800 -EDGE_SE2 138 139 0.624343 0.022436 0.065016 11.318542 7.127247 0.000000 256.001043 0.000000 2204.081980 -EDGE_SE2 139 140 0.649355 0.026111 0.030256 11.133375 -2.241330 0.000000 236.751877 0.000000 2355.318828 -EDGE_SE2 140 141 0.633752 -0.015337 -0.048721 11.254069 -5.827843 0.000000 248.689278 0.000000 2273.108048 -EDGE_SE2 141 142 0.643523 0.009872 -0.004100 11.198132 -4.475923 0.000000 241.330701 0.000000 2479.625389 -EDGE_SE2 142 143 0.634003 -0.068517 -0.110892 11.113575 -0.760641 0.000000 245.906731 0.000000 2025.798896 -EDGE_SE2 143 144 0.367489 0.008323 0.017249 11.132342 -3.934021 0.000000 740.076637 0.000000 2415.936223 -EDGE_SE2 144 145 0.011506 -0.002421 -0.499034 59809.374609 -199183.039696 0.000000 663473.241646 0.000000 1112.543606 -EDGE_SE2 145 146 0.002065 0.000912 -0.593946 14068785.759218 -8838843.642003 0.000000 5553100.053245 0.000000 983.994793 -EDGE_SE2 146 147 0.052046 -0.024279 -0.487148 88.850566 -1532.993200 0.000000 30241.168070 0.000000 1130.398639 -EDGE_SE2 147 148 0.437634 -0.008022 -0.035943 11.269607 -8.996749 0.000000 521.795770 0.000000 2329.529896 -EDGE_SE2 148 149 0.649282 0.006558 0.024834 11.160187 3.330542 0.000000 237.137078 0.000000 2380.306915 -EDGE_SE2 149 150 0.638778 0.018547 0.013910 11.164529 -3.533262 0.000000 244.815913 0.000000 2431.874707 -EDGE_SE2 150 151 0.642396 0.027197 0.085399 11.539278 9.931167 0.000000 241.460866 0.000000 2122.077176 -EDGE_SE2 151 152 0.642196 -0.014560 -0.068180 11.589745 -10.509493 0.000000 241.870955 0.000000 2191.044127 -EDGE_SE2 152 153 0.641634 0.027219 0.085722 11.545124 10.011045 0.000000 242.028095 0.000000 2120.814737 -EDGE_SE2 153 154 0.638389 -0.020707 -0.077238 11.580742 -10.472604 0.000000 244.646718 0.000000 2154.352072 -EDGE_SE2 154 155 0.642162 0.011168 0.026427 11.130001 2.090263 0.000000 242.407116 0.000000 2372.924244 -EDGE_SE2 155 156 0.644865 0.009414 0.023665 11.129963 2.079071 0.000000 240.400517 0.000000 2385.746522 -EDGE_SE2 156 157 0.581283 -0.014428 -0.072363 11.754151 -13.514232 0.000000 295.128631 0.000000 2173.984116 -EDGE_SE2 157 158 0.187127 -0.000419 0.029090 13.902620 89.068131 0.000000 2852.990445 0.000000 2360.659192 -EDGE_SE2 158 159 -0.002775 0.000752 0.993417 10949150.533757 3540503.247990 0.000000 1144864.853964 0.000000 629.134778 -EDGE_SE2 159 160 0.010178 0.000761 1.042069 650920.894971 448555.873968 0.000000 309120.571532 0.000000 599.513799 -EDGE_SE2 160 161 -0.000005 0.000004 0.981942 2693538350855.096191 -157146640359.091309 0.000000 9168262482.076782 0.000000 636.440966 -EDGE_SE2 161 162 -0.015739 -0.012123 1.042202 35897.561333 88343.035344 0.000000 217488.511233 0.000000 599.435534 -EDGE_SE2 162 163 0.001603 0.001566 1.003233 1029828.017450 4408843.295789 0.000000 18875113.167768 0.000000 622.984264 -EDGE_SE2 163 164 0.014062 0.009080 0.929958 43510.337182 116762.574144 0.000000 313430.450998 0.000000 671.188169 -EDGE_SE2 164 165 0.127064 0.050581 0.402133 14.003612 124.194925 0.000000 5343.652150 0.000000 1271.632407 -EDGE_SE2 165 166 0.660048 0.003938 -0.000969 11.121614 -1.514549 0.000000 229.516161 0.000000 2495.162033 -EDGE_SE2 166 167 0.637952 -0.005107 -0.029173 11.216206 -4.964111 0.000000 245.589760 0.000000 2360.278446 -EDGE_SE2 167 168 0.609195 0.011564 0.012037 11.123559 -1.792903 0.000000 269.346120 0.000000 2440.884489 -EDGE_SE2 168 169 0.616410 -0.017912 -0.037053 11.127238 -2.015259 0.000000 262.946448 0.000000 2324.545784 -EDGE_SE2 169 170 0.607779 0.006983 -0.020916 11.383588 -8.405441 0.000000 270.404841 0.000000 2398.611924 -EDGE_SE2 170 171 0.584191 -0.014911 -0.038741 11.160361 -3.724488 0.000000 292.775082 0.000000 2316.996943 -EDGE_SE2 171 172 0.648836 0.010594 0.013474 11.112953 -0.645665 0.000000 237.471401 0.000000 2433.967559 -EDGE_SE2 172 173 0.642894 -0.013166 -0.018042 11.112478 0.561628 0.000000 241.844714 0.000000 2412.173920 -EDGE_SE2 173 174 0.638185 0.007116 0.045162 11.382152 7.965912 0.000000 245.229896 0.000000 2288.615243 -EDGE_SE2 174 175 0.625733 0.004082 -0.019946 11.282223 -6.462935 0.000000 255.218243 0.000000 2403.176400 -EDGE_SE2 175 176 0.613713 -0.013173 0.014287 11.435899 9.081745 0.000000 265.055874 0.000000 2430.067237 -EDGE_SE2 176 177 0.634525 0.002256 -0.022912 11.277271 -6.276557 0.000000 248.202540 0.000000 2389.260272 -EDGE_SE2 177 178 0.640137 -0.023037 -0.034183 11.111855 0.416076 0.000000 243.719928 0.000000 2337.465554 -EDGE_SE2 178 179 0.353790 0.008069 0.053221 11.839402 23.935910 0.000000 797.785636 0.000000 2253.725346 -EDGE_SE2 179 180 0.003152 -0.000263 -0.442165 1233046.693540 -3286666.439106 0.000000 8760647.410566 0.000000 1202.015605 -EDGE_SE2 180 181 0.557570 -0.008188 -0.051388 11.529199 -11.385701 0.000000 321.175409 0.000000 2261.590530 -EDGE_SE2 181 182 0.573085 -0.036151 -0.172968 14.630195 -31.871096 0.000000 299.756322 0.000000 1817.053357 -EDGE_SE2 182 183 0.603594 -0.027970 -0.056017 11.135892 -2.551709 0.000000 273.866587 0.000000 2241.806837 -EDGE_SE2 183 184 0.635433 -0.054532 -0.150813 12.107735 -15.262873 0.000000 244.855650 0.000000 1887.689196 -EDGE_SE2 184 185 0.634881 -0.022979 -0.096285 11.965079 -14.190439 0.000000 246.914669 0.000000 2080.142393 -EDGE_SE2 185 186 0.643298 -0.040128 -0.069768 11.123925 -1.715150 0.000000 240.694352 0.000000 2184.544034 -EDGE_SE2 186 187 0.616544 -0.013175 -0.025584 11.115593 -1.062453 0.000000 262.945790 0.000000 2376.826796 -EDGE_SE2 187 188 0.637286 0.000267 -0.008123 11.128264 -2.008140 0.000000 246.207666 0.000000 2459.874568 -EDGE_SE2 188 189 0.639354 -0.005159 -0.029399 11.217330 -4.979110 0.000000 244.512278 0.000000 2359.242182 -EDGE_SE2 189 190 0.641605 -0.016631 -0.026189 11.111129 -0.063557 0.000000 242.757543 0.000000 2374.025058 -EDGE_SE2 190 191 0.641101 -0.008160 -0.020962 11.126855 -1.911760 0.000000 243.247747 0.000000 2398.395787 -EDGE_SE2 191 192 0.634787 -0.009757 -0.024655 11.131543 -2.200448 0.000000 248.088013 0.000000 2381.138634 -EDGE_SE2 192 193 0.586234 -0.010454 -0.051260 11.423659 -9.345831 0.000000 290.571036 0.000000 2262.141300 -EDGE_SE2 193 194 0.611583 -0.006689 -0.021980 11.142360 -2.829372 0.000000 267.292453 0.000000 2393.620056 -EDGE_SE2 194 195 0.610567 -0.010284 -0.023227 11.121593 -1.641455 0.000000 268.159748 0.000000 2387.789433 -EDGE_SE2 195 196 0.641859 -0.011003 -0.041852 11.252480 -5.719563 0.000000 242.515471 0.000000 2303.180363 -EDGE_SE2 196 197 0.640092 -0.013813 -0.028233 11.121429 -1.549936 0.000000 243.946727 0.000000 2364.595903 -EDGE_SE2 197 198 0.639039 -0.009178 -0.058660 11.569437 -10.339579 0.000000 244.366482 0.000000 2230.627234 -EDGE_SE2 198 199 0.637088 -0.027408 -0.061598 11.192373 -4.367444 0.000000 245.840841 0.000000 2218.297682 -EDGE_SE2 199 200 0.636910 -0.008520 -0.021764 11.127669 -1.974000 0.000000 246.454469 0.000000 2394.632181 -EDGE_SE2 200 201 0.639616 -0.019900 -0.056243 11.258405 -5.857515 0.000000 244.050370 0.000000 2240.847600 -EDGE_SE2 201 202 0.573162 0.008125 0.017141 11.113691 0.869688 0.000000 304.336907 0.000000 2416.449299 -EDGE_SE2 202 203 0.807715 -0.017801 -0.055477 11.269967 -4.748397 0.000000 153.046283 0.000000 2244.101317 -EDGE_SE2 203 204 0.600516 -0.019464 -0.097088 12.222178 -17.152147 0.000000 275.898243 0.000000 2077.098438 -EDGE_SE2 204 205 0.607570 -0.009135 -0.005030 11.137105 2.598215 0.000000 270.811426 0.000000 2475.038492 -EDGE_SE2 205 206 0.587179 0.007336 0.006623 11.120721 -1.637028 0.000000 289.985432 0.000000 2467.211100 -EDGE_SE2 206 207 0.607993 0.006080 0.021126 11.143219 2.885684 0.000000 270.462940 0.000000 2397.625450 -EDGE_SE2 207 208 0.644034 -0.008545 -0.004095 11.130455 2.108903 0.000000 241.029670 0.000000 2479.650084 -EDGE_SE2 208 209 0.640840 0.009001 0.044393 11.325040 7.046915 0.000000 243.239179 0.000000 2291.986757 -EDGE_SE2 209 210 0.641153 -0.014043 -0.012923 11.129806 2.082671 0.000000 243.128297 0.000000 2436.616291 -EDGE_SE2 210 211 0.640990 0.006728 -0.001668 11.145470 -2.824640 0.000000 243.325777 0.000000 2491.680820 -EDGE_SE2 211 212 0.638573 -0.025254 -0.057210 11.184192 -4.132365 0.000000 244.776704 0.000000 2236.750195 -EDGE_SE2 212 213 0.648396 -0.008808 -0.024826 11.139763 -2.548475 0.000000 237.786398 0.000000 2380.344078 -EDGE_SE2 213 214 0.637242 -0.004643 -0.007684 11.111148 -0.093735 0.000000 246.245036 0.000000 2462.018335 -EDGE_SE2 214 215 0.369747 -0.004429 -0.039914 11.673090 -20.110851 0.000000 730.793592 0.000000 2311.772848 -EDGE_SE2 215 216 0.006980 -0.001179 -0.507763 222524.090867 -628114.036197 0.000000 1773063.824260 0.000000 1099.699039 -EDGE_SE2 216 217 -0.001832 0.001260 -0.551814 52174.391125 1025898.356469 0.000000 20176415.615022 0.000000 1038.151360 -EDGE_SE2 217 218 0.000862 -0.000506 -0.060277 20602967.870958 40459206.697150 0.000000 79452073.533188 0.000000 2223.828683 -EDGE_SE2 218 219 0.576874 -0.106568 -0.173874 11.132755 2.459345 0.000000 290.557594 0.000000 1814.249623 -EDGE_SE2 219 220 0.629125 -0.005523 -0.044144 11.413053 -8.534327 0.000000 252.332357 0.000000 2293.080041 -EDGE_SE2 220 221 0.642903 0.008921 0.051755 11.442103 8.733716 0.000000 241.563444 0.000000 2260.012484 -EDGE_SE2 221 222 0.635198 -0.000697 -0.024981 11.246132 -5.652071 0.000000 247.710771 0.000000 2379.624210 -EDGE_SE2 222 223 0.639045 0.029371 0.096256 11.701399 11.718901 0.000000 243.764798 0.000000 2080.252449 -EDGE_SE2 223 224 0.637940 -0.001297 -0.026485 11.251362 -5.734476 0.000000 245.578734 0.000000 2372.656095 -EDGE_SE2 224 225 0.639421 0.008657 0.048309 11.393218 8.109986 0.000000 244.256072 0.000000 2274.895125 -EDGE_SE2 225 226 0.638048 -0.023812 -0.051167 11.156122 -3.246372 0.000000 245.250378 0.000000 2262.541595 -EDGE_SE2 226 227 0.642744 0.047412 0.156718 12.692747 18.992206 0.000000 239.168545 0.000000 1868.465232 -EDGE_SE2 227 228 0.588969 -0.135551 -0.358747 15.698176 -34.406864 0.000000 269.191657 0.000000 1354.137035 -EDGE_SE2 228 229 0.605993 0.034684 0.124928 12.304340 17.583695 0.000000 270.228487 0.000000 1975.560427 -EDGE_SE2 229 230 0.370582 0.002463 -0.025443 11.849164 -22.992568 0.000000 727.398994 0.000000 2377.480475 -EDGE_SE2 230 231 -0.130796 -0.004868 0.073003 18.577316 208.431226 0.000000 5829.807352 0.000000 2171.391514 -EDGE_SE2 231 232 0.345343 -0.135465 -0.413759 12.252120 -28.551107 0.000000 725.536788 0.000000 1250.803404 -EDGE_SE2 232 233 0.649941 0.026872 0.109205 12.147339 15.241388 0.000000 235.289518 0.000000 2031.965694 -EDGE_SE2 233 234 0.647791 -0.001996 -0.038195 11.391127 -7.971100 0.000000 238.021188 0.000000 2319.434660 -EDGE_SE2 234 235 0.300180 0.002544 0.000515 11.180724 -8.744740 0.000000 1109.628316 0.000000 2497.426988 -EDGE_SE2 235 236 0.331863 0.149674 0.417637 11.138398 -4.503817 0.000000 754.488686 0.000000 1243.969525 -EDGE_SE2 236 237 0.654682 0.025212 0.076902 11.438281 8.513385 0.000000 232.640473 0.000000 2155.696624 -EDGE_SE2 237 238 0.648140 0.014417 -0.026834 11.656908 -11.112994 0.000000 237.383264 0.000000 2371.043534 -EDGE_SE2 238 239 0.643519 -0.017811 -0.061244 11.370468 -7.722173 0.000000 241.033805 0.000000 2219.777847 -EDGE_SE2 239 240 0.603843 -0.096900 -0.209505 11.761203 -12.890609 0.000000 266.717846 0.000000 1708.931569 -EDGE_SE2 240 241 0.584456 0.014283 0.092258 12.403890 19.031519 0.000000 291.281847 0.000000 2095.509047 -EDGE_SE2 241 242 0.612687 0.022692 0.027357 11.134913 -2.463133 0.000000 266.004463 0.000000 2368.630078 -EDGE_SE2 242 243 0.648066 -0.017400 -0.010559 11.171248 3.692755 0.000000 237.869261 0.000000 2448.029575 -EDGE_SE2 243 244 0.642215 0.026261 0.062075 11.214953 4.895996 0.000000 241.951020 0.000000 2216.305562 -EDGE_SE2 244 245 0.634243 -0.022273 -0.061989 11.282517 -6.373688 0.000000 248.115451 0.000000 2216.664530 -EDGE_SE2 245 246 0.645668 -0.028614 -0.079256 11.390140 -7.976362 0.000000 239.124210 0.000000 2146.303161 -EDGE_SE2 246 247 0.643529 0.022831 0.100014 12.068378 14.809059 0.000000 240.209522 0.000000 2066.063111 -EDGE_SE2 247 248 0.564321 0.027061 0.024967 11.270232 -6.932385 0.000000 313.133504 0.000000 2379.689217 -EDGE_SE2 248 249 -0.035940 0.003373 0.183988 5772.276933 20220.354842 0.000000 70979.863375 0.000000 1783.386223 -EDGE_SE2 249 250 0.007750 -0.001049 -0.606852 338497.920927 -662461.020100 0.000000 1296530.178991 0.000000 968.251297 -EDGE_SE2 250 251 0.463939 -0.175406 -0.437806 13.410741 -30.065645 0.000000 404.193089 0.000000 1209.314960 -EDGE_SE2 251 252 0.574959 0.003510 0.002433 11.115040 -1.069896 0.000000 302.485346 0.000000 2487.879253 -EDGE_SE2 252 253 0.605120 -0.005928 -0.017244 11.125640 -1.950811 0.000000 273.056569 0.000000 2415.959973 -EDGE_SE2 253 254 0.610735 -0.012632 -0.047846 11.300633 -6.974744 0.000000 267.794244 0.000000 2276.905934 -EDGE_SE2 254 255 0.044400 0.000523 0.074991 213.390433 3196.298492 0.000000 50517.134043 0.000000 2163.367754 -EDGE_SE2 255 256 0.001043 -0.000895 -0.499566 2299182.592246 10791888.202847 0.000000 50655150.120270 0.000000 1111.754353 -EDGE_SE2 256 257 0.555824 -0.061442 -0.099167 11.147977 3.373126 0.000000 319.742706 0.000000 2069.248487 -EDGE_SE2 257 258 0.629719 0.018359 0.091632 12.050310 15.010875 0.000000 251.024412 0.000000 2097.913089 -EDGE_SE2 258 259 0.636152 0.019457 0.038906 11.127473 1.964002 0.000000 246.855618 0.000000 2316.261026 -EDGE_SE2 259 260 0.591139 -0.005281 -0.010083 11.111475 -0.316152 0.000000 286.144813 0.000000 2450.337378 -EDGE_SE2 260 261 0.038207 -0.011731 -0.314328 28.012479 -1028.395525 0.000000 62585.773083 0.000000 1447.213112 -EDGE_SE2 261 262 0.655511 -0.012976 0.004878 11.245910 5.462837 0.000000 232.497259 0.000000 2475.787308 -EDGE_SE2 262 263 0.621841 0.001156 0.011526 11.134237 2.392264 0.000000 258.583295 0.000000 2443.351271 -EDGE_SE2 263 264 0.608889 0.019368 0.094641 12.130010 16.192201 0.000000 268.435238 0.000000 2086.395259 -EDGE_SE2 264 265 0.612314 0.007199 0.017955 11.120930 1.584091 0.000000 266.671128 0.000000 2412.586253 -EDGE_SE2 265 266 0.576085 -0.083302 -0.132058 11.148980 3.279437 0.000000 295.110014 0.000000 1950.754657 -EDGE_SE2 266 267 0.757425 -0.007866 -0.038469 11.239778 -4.580310 0.000000 174.161685 0.000000 2318.210856 -EDGE_SE2 267 268 0.631857 -0.080953 -0.120705 11.121739 1.581391 0.000000 246.417934 0.000000 1990.478036 -EDGE_SE2 268 269 0.641922 0.003817 0.021827 11.169502 3.676650 0.000000 242.613796 0.000000 2394.336911 -EDGE_SE2 269 270 0.637667 0.034307 0.104727 11.718968 11.913668 0.000000 244.612698 0.000000 2048.472187 -EDGE_SE2 270 271 0.640010 -0.062261 -0.119686 11.230091 -5.238185 0.000000 241.725444 0.000000 1994.102659 -EDGE_SE2 271 272 0.644232 0.019193 0.057497 11.287428 6.360389 0.000000 240.553533 0.000000 2235.536271 -EDGE_SE2 272 273 0.634342 0.083237 0.206754 12.465423 17.719715 0.000000 242.954542 0.000000 1716.732048 -EDGE_SE2 273 274 0.640984 0.007900 0.019834 11.124210 1.744119 0.000000 243.341574 0.000000 2403.704272 -EDGE_SE2 274 275 0.606472 -0.086361 -0.177559 11.443959 -9.213419 0.000000 266.144138 0.000000 1802.912528 -EDGE_SE2 275 276 0.612197 0.023588 0.108696 12.366721 17.860495 0.000000 265.168738 0.000000 2033.831865 -EDGE_SE2 276 277 0.583581 0.010361 -0.030920 11.779645 -13.724550 0.000000 292.866956 0.000000 2352.285755 -EDGE_SE2 277 278 0.636078 -0.067917 -0.095289 11.139759 2.584894 0.000000 244.345909 0.000000 2083.927264 -EDGE_SE2 278 279 0.642761 -0.012725 -0.054157 11.383584 -7.926158 0.000000 241.680475 0.000000 2249.724898 -EDGE_SE2 279 280 0.638781 -0.006089 0.003079 11.148313 2.949856 0.000000 245.014031 0.000000 2484.675811 -EDGE_SE2 280 281 0.641856 -0.029617 -0.099276 11.763757 -12.263888 0.000000 241.562200 0.000000 2068.838150 -EDGE_SE2 281 282 0.639782 0.004798 0.040856 11.370470 7.772433 0.000000 244.033931 0.000000 2307.590321 -EDGE_SE2 282 283 0.642050 0.012891 -0.006948 11.280026 -6.249342 0.000000 242.317369 0.000000 2465.618735 -EDGE_SE2 283 284 0.634635 -0.008598 0.028031 11.520810 9.848040 0.000000 247.830973 0.000000 2365.525243 -EDGE_SE2 284 285 0.644186 0.019368 0.016799 11.151476 -3.044364 0.000000 240.720053 0.000000 2418.075116 -EDGE_SE2 285 286 0.616883 0.003268 0.049122 11.594142 11.014881 0.000000 262.290847 0.000000 2271.370705 -EDGE_SE2 286 287 0.041606 0.000178 -0.109420 754.499701 -6510.220695 0.000000 57024.326077 0.000000 2031.178201 -EDGE_SE2 287 288 0.009415 -0.003373 -0.619963 74221.251106 -262085.361102 0.000000 925608.832783 0.000000 952.642204 -EDGE_SE2 288 289 0.020008 -0.006408 -0.604589 19113.152514 -62948.109898 0.000000 207447.816761 0.000000 970.984702 -EDGE_SE2 289 290 0.454169 -0.104092 -0.231282 11.127187 -2.688060 0.000000 460.591382 0.000000 1649.016296 -EDGE_SE2 290 291 0.647578 0.004260 0.009723 11.113360 0.715048 0.000000 238.447481 0.000000 2452.084944 -EDGE_SE2 291 292 0.634089 -0.015008 -0.055281 11.348399 -7.502724 0.000000 248.336711 0.000000 2244.935000 -EDGE_SE2 292 293 0.642557 -0.005982 -0.023777 11.159473 -3.342559 0.000000 242.132375 0.000000 2385.224555 -EDGE_SE2 293 294 0.639112 0.001322 -0.000108 11.112218 -0.508677 0.000000 244.817748 0.000000 2499.460087 -EDGE_SE2 294 295 0.640372 -0.027705 -0.113609 12.259570 -16.292856 0.000000 242.253178 0.000000 2015.925806 -EDGE_SE2 295 296 0.642975 -0.021060 -0.046516 11.154844 -3.174767 0.000000 241.583458 0.000000 2282.696976 -EDGE_SE2 296 297 0.642345 0.000782 -0.012863 11.156958 -3.255777 0.000000 242.314814 0.000000 2436.904980 -EDGE_SE2 297 298 0.647985 0.003784 0.003363 11.112504 -0.562400 0.000000 238.150885 0.000000 2483.269445 -EDGE_SE2 298 299 0.612845 -0.007635 0.065661 12.664708 19.847274 0.000000 264.661036 0.000000 2201.414711 -EDGE_SE2 299 300 0.612360 0.028900 0.046427 11.111248 -0.186667 0.000000 266.084327 0.000000 2283.085285 -EDGE_SE2 300 301 0.590205 -0.014952 -0.054929 11.352681 -8.158527 0.000000 286.648280 0.000000 2246.433393 -EDGE_SE2 301 302 0.327130 -0.001110 -0.017014 11.282416 -12.575459 0.000000 934.273069 0.000000 2417.052846 -EDGE_SE2 302 303 0.000000 0.000000 0.007958 11.135739 3.094647 0.000000 399.975372 0.000000 2460.679983 -EDGE_SE2 303 304 0.531735 -0.000572 0.013349 11.182383 4.940694 0.000000 353.608097 0.000000 2434.568072 -EDGE_SE2 304 305 0.632029 0.091742 0.189082 11.583394 10.503320 0.000000 244.699503 0.000000 1768.138981 -EDGE_SE2 305 306 0.638032 0.009278 -0.014535 11.309292 -6.814055 0.000000 245.399171 0.000000 2428.879337 -EDGE_SE2 306 307 0.649045 -0.016359 -0.033059 11.125080 -1.777236 0.000000 237.218596 0.000000 2342.554790 -EDGE_SE2 307 308 0.638862 -0.008726 -0.051832 11.451746 -8.918684 0.000000 244.624962 0.000000 2259.681604 -EDGE_SE2 308 309 0.645288 0.030453 0.096996 11.678208 11.369540 0.000000 239.055109 0.000000 2077.446846 -EDGE_SE2 309 310 0.636777 0.000884 -0.032322 11.378630 -7.932902 0.000000 246.350008 0.000000 2345.900799 -EDGE_SE2 310 311 0.290424 -0.002112 0.179746 51.710880 214.552149 0.000000 1144.926065 0.000000 1796.234285 -EDGE_SE2 311 312 0.007354 0.003264 1.002917 471360.416969 711283.404228 0.000000 1073363.876974 0.000000 623.180856 -EDGE_SE2 312 313 0.200119 0.102327 0.503683 13.003444 61.001684 0.000000 1977.575148 0.000000 1105.674845 -EDGE_SE2 313 314 0.633848 0.018391 0.043519 11.161147 3.447475 0.000000 248.643057 0.000000 2295.827675 -EDGE_SE2 314 315 0.633704 0.002276 -0.000875 11.115856 -1.062415 0.000000 249.007791 0.000000 2495.630735 -EDGE_SE2 315 316 0.639305 0.015820 0.052631 11.292626 6.506496 0.000000 244.340479 0.000000 2256.252482 -EDGE_SE2 316 317 0.637745 0.004302 0.011142 11.115650 1.032197 0.000000 245.854624 0.000000 2445.207439 -EDGE_SE2 317 318 0.636745 0.035394 0.095957 11.494620 9.481044 0.000000 245.500106 0.000000 2081.387677 -EDGE_SE2 318 319 0.348842 0.011538 0.072712 12.383431 32.072340 0.000000 819.582938 0.000000 2172.569763 -EDGE_SE2 319 320 0.000816 -0.000527 0.973228 106005797.819726 2536098.274165 0.000000 60685.105012 0.000000 642.074570 -EDGE_SE2 320 321 0.006777 0.006256 1.044793 102222.634261 331234.073682 0.000000 1073432.269379 0.000000 597.917382 -EDGE_SE2 321 322 0.006473 0.002839 1.001084 615262.184536 923437.444301 0.000000 1386009.040037 0.000000 624.323050 -EDGE_SE2 322 323 0.016353 0.013170 0.997277 22353.154021 67589.307695 0.000000 204482.767224 0.000000 626.705357 -EDGE_SE2 323 324 0.008916 0.001230 0.663199 311347.556233 536104.854899 0.000000 923155.252921 0.000000 903.756801 -EDGE_SE2 324 325 0.003144 -0.000405 -0.524891 1487579.272379 -3549001.141685 0.000000 8467125.043520 0.000000 1075.133530 -EDGE_SE2 325 326 0.023808 -0.007961 -0.592174 11258.329132 -40719.337055 0.000000 147431.069280 0.000000 986.186273 -EDGE_SE2 326 327 0.020978 -0.009992 -0.620226 5671.563195 -31879.996101 0.000000 179561.107529 0.000000 952.332958 -EDGE_SE2 327 328 0.643658 -0.034894 -0.045241 11.129369 2.047184 0.000000 240.647976 0.000000 2288.269306 -EDGE_SE2 328 329 0.649334 -0.010852 -0.054672 11.436629 -8.570843 0.000000 236.780423 0.000000 2247.528337 -EDGE_SE2 329 330 0.639632 -0.008474 -0.018872 11.118489 -1.311825 0.000000 244.371181 0.000000 2408.245480 -EDGE_SE2 330 331 0.641038 0.003713 -0.014179 11.203728 -4.636800 0.000000 243.249680 0.000000 2430.584821 -EDGE_SE2 331 332 0.640252 -0.011677 -0.018427 11.111120 -0.044254 0.000000 243.867115 0.000000 2410.350498 -EDGE_SE2 332 333 0.177633 -0.001729 0.065170 28.796073 235.654693 0.000000 3151.244188 0.000000 2203.444704 -EDGE_SE2 333 334 0.000905 -0.002029 1.057518 13067665.297415 -9690930.296947 0.000000 7186773.836950 0.000000 590.544626 -EDGE_SE2 334 335 0.024703 0.015296 0.997066 21742.343151 45843.877395 0.000000 96722.659299 0.000000 626.837793 -EDGE_SE2 335 336 0.010478 0.002030 0.946908 412732.144616 438156.323325 0.000000 465170.257567 0.000000 659.552164 -EDGE_SE2 336 337 0.008745 0.002954 0.985008 440385.721295 568301.950425 0.000000 733402.863015 0.000000 634.476419 -EDGE_SE2 337 338 -0.003685 -0.001824 0.981724 1470665.482891 2556461.697178 0.000000 4443948.812959 0.000000 636.580801 -EDGE_SE2 338 339 -0.005129 -0.003808 1.041677 377018.548764 884152.776785 0.000000 2073514.322517 0.000000 599.744033 -EDGE_SE2 339 340 0.010570 0.010289 1.011780 25946.779143 106050.417947 0.000000 433649.108522 0.000000 617.702041 -EDGE_SE2 340 341 0.113717 0.080150 0.628571 12.212286 75.337385 0.000000 5165.350612 0.000000 942.598218 -EDGE_SE2 341 342 0.654766 0.022058 0.099815 12.080273 14.632015 0.000000 232.019342 0.000000 2066.810844 -EDGE_SE2 342 343 0.628741 0.054742 0.130801 11.574390 10.533194 0.000000 250.595866 0.000000 1955.093991 -EDGE_SE2 343 344 0.635892 -0.007155 -0.010075 11.111438 0.277943 0.000000 247.273507 0.000000 2450.376193 -EDGE_SE2 344 345 0.267580 0.003485 -0.027538 13.388942 -56.127772 0.000000 1394.149289 0.000000 2367.795687 -EDGE_SE2 345 346 0.004489 0.000038 -0.647423 1844936.688291 -2397918.213433 0.000000 3116674.368121 0.000000 921.148732 -EDGE_SE2 346 347 0.072838 -0.046842 -0.558829 13.253512 168.933111 0.000000 13331.862665 0.000000 1028.828663 -EDGE_SE2 347 348 0.589079 -0.006098 -0.016843 11.122785 -1.798313 0.000000 288.130205 0.000000 2417.865855 -EDGE_SE2 348 349 0.616820 -0.017333 -0.040458 11.149561 -3.109556 0.000000 262.589175 0.000000 2309.356075 -EDGE_SE2 349 350 0.785503 -0.000917 -0.019918 11.164179 -2.829897 0.000000 162.017193 0.000000 2403.308352 -EDGE_SE2 350 351 0.270645 -0.005037 -0.029292 11.265595 -14.459903 0.000000 1364.581559 0.000000 2359.732718 -EDGE_SE2 351 352 0.002244 -0.000330 -0.288296 389945.087215 -2724716.373664 0.000000 19039335.129753 0.000000 1506.290344 -EDGE_SE2 352 353 0.003402 -0.001192 -0.665425 800995.211276 -2349889.351851 0.000000 6894005.592158 0.000000 901.342500 -EDGE_SE2 353 354 -0.005763 0.001670 -0.603397 277124.929036 -832459.339385 0.000000 2500747.295676 0.000000 972.428940 -EDGE_SE2 354 355 0.000000 0.000000 -0.720990 180.577269 -192.833418 0.000000 230.533842 0.000000 844.079125 -EDGE_SE2 355 356 0.003943 -0.001205 -0.575253 444990.541595 -1555501.608157 0.000000 5437532.684522 0.000000 1007.486830 -EDGE_SE2 356 357 0.020153 -0.008599 -0.653072 12735.537924 -49883.324785 0.000000 195567.746252 0.000000 914.863854 -EDGE_SE2 357 358 0.015309 -0.003110 -0.615454 66643.110857 -151211.449053 0.000000 343163.086320 0.000000 957.967591 -EDGE_SE2 358 359 0.006974 -0.000995 -0.614029 417035.012297 -816381.545249 0.000000 1598190.077659 0.000000 959.659886 -EDGE_SE2 359 360 0.004457 -0.000580 -0.176301 10913.841058 -232052.237339 0.000000 4938979.710352 0.000000 1806.770854 -EDGE_SE2 360 361 0.010594 0.005702 0.919376 117786.647020 259806.659966 0.000000 573130.987341 0.000000 678.609427 -EDGE_SE2 361 362 0.219803 0.130838 0.533743 11.126432 -4.821230 0.000000 1528.288566 0.000000 1062.759073 -EDGE_SE2 362 363 0.297072 -0.003532 -0.006476 11.143964 6.070793 0.000000 1132.926894 0.000000 2467.931845 -EDGE_SE2 363 364 0.289348 0.182399 0.614616 13.404127 43.923133 0.000000 852.466620 0.000000 958.962236 -EDGE_SE2 364 365 0.292841 -0.019896 -0.497082 210.238012 -435.053006 0.000000 961.616130 0.000000 1115.446721 -EDGE_SE2 365 366 0.158985 -0.083838 -0.554071 25.686079 -211.524041 0.000000 3080.923734 0.000000 1035.138105 -EDGE_SE2 366 367 0.639497 -0.004946 0.002087 11.133620 2.291973 0.000000 244.487349 0.000000 2489.597576 -EDGE_SE2 367 368 0.604131 -0.012617 -0.052944 11.381139 -8.419024 0.000000 273.602223 0.000000 2254.911286 -EDGE_SE2 368 369 0.002209 0.000313 -0.259186 3043052.869717 -7202482.111400 0.000000 17047344.890980 0.000000 1576.740546 -EDGE_SE2 369 370 0.005991 -0.002050 -0.599321 177021.973379 -640449.340665 0.000000 2317243.809168 0.000000 977.391885 -EDGE_SE2 370 371 0.006708 -0.002639 -0.557206 63344.214539 -343349.901593 0.000000 1861425.577547 0.000000 1030.974377 -EDGE_SE2 371 372 0.558575 -0.019962 -0.032348 11.114630 1.042696 0.000000 320.094134 0.000000 2345.782636 -EDGE_SE2 372 373 0.602488 0.010152 -0.002327 11.208284 -5.066873 0.000000 275.312725 0.000000 2488.405486 -EDGE_SE2 373 374 0.632591 -0.017325 -0.065949 11.465862 -9.193251 0.000000 249.351067 0.000000 2200.225307 -EDGE_SE2 374 375 0.469417 0.004030 -0.028771 11.728554 -16.521047 0.000000 453.167816 0.000000 2362.123399 -EDGE_SE2 375 376 0.002158 0.000483 0.698524 4333313.980583 8354996.990813 0.000000 16109195.449061 0.000000 866.555999 -EDGE_SE2 376 377 0.046225 0.038326 0.699019 12.380728 187.607270 0.000000 27733.237753 0.000000 866.051140 -EDGE_SE2 377 378 0.665310 0.013616 0.055080 11.368301 7.426696 0.000000 225.566926 0.000000 2245.790433 -EDGE_SE2 378 379 0.642743 0.038423 0.085729 11.266862 5.984327 0.000000 241.043246 0.000000 2120.787390 -EDGE_SE2 379 380 0.640645 0.038346 0.120298 11.958441 13.985039 0.000000 241.931786 0.000000 1991.924564 -EDGE_SE2 380 381 0.233459 0.003584 0.150906 44.408710 244.130938 0.000000 1801.027395 0.000000 1887.384136 -EDGE_SE2 381 382 0.006327 0.000255 1.053123 1795131.477569 1120171.329981 0.000000 699008.143264 0.000000 593.075621 -EDGE_SE2 382 383 -0.003556 -0.002021 0.980056 1193066.389244 2388759.272445 0.000000 4782832.968807 0.000000 637.653764 -EDGE_SE2 383 384 0.001991 0.002244 0.957091 138704.428768 1233640.966374 0.000000 10972926.458664 0.000000 652.706547 -EDGE_SE2 384 385 0.011270 0.001792 1.022982 445019.439592 379059.233315 0.000000 322894.736351 0.000000 610.880091 -EDGE_SE2 385 386 0.168577 0.055826 0.222392 40.994230 -305.838725 0.000000 3141.216954 0.000000 1673.088872 -EDGE_SE2 386 387 0.606264 0.162988 0.328749 12.170230 15.994975 0.000000 252.669738 0.000000 1415.970180 -EDGE_SE2 387 388 0.635832 0.013644 0.010758 11.138130 -2.525694 0.000000 247.211302 0.000000 2447.065724 -EDGE_SE2 388 389 0.627947 -0.024187 -0.080320 11.534345 -10.113978 0.000000 252.803888 0.000000 2142.077483 -EDGE_SE2 389 390 0.069191 0.001300 0.478702 4122.849667 8300.847325 0.000000 16769.002054 0.000000 1143.348663 -EDGE_SE2 390 391 -0.133725 -0.033239 0.382997 112.537800 723.030020 0.000000 5165.300931 0.000000 1307.066045 -EDGE_SE2 391 392 -0.002743 0.000191 1.047762 10690557.469801 5209631.734700 0.000000 2538727.272155 0.000000 596.185006 -EDGE_SE2 392 393 0.028217 0.013447 1.259453 54184.105027 51083.362632 0.000000 48181.052425 0.000000 489.703700 -EDGE_SE2 393 394 0.008510 0.003621 0.998557 368727.424224 543264.764209 0.000000 800454.686659 0.000000 625.902660 -EDGE_SE2 394 395 -0.234595 -0.069505 0.339597 15.518106 85.399492 0.000000 1665.996927 0.000000 1393.130101 -EDGE_SE2 395 396 0.000000 0.000000 0.001250 11.111719 0.486111 0.000000 399.999392 0.000000 2493.761699 -EDGE_SE2 396 397 0.001351 -0.000419 -0.009641 4125762.827775 13757530.022207 0.000000 45875198.303653 0.000000 2452.483262 -EDGE_SE2 397 398 0.004038 0.000754 0.199105 1257.017158 85923.998064 0.000000 5925765.672764 0.000000 1738.703710 -EDGE_SE2 398 399 0.122070 0.031296 0.300741 26.669772 312.344207 0.000000 6281.503348 0.000000 1477.604991 -EDGE_SE2 399 400 0.657179 0.003449 0.051415 11.580586 10.161892 0.000000 231.067499 0.000000 2261.474377 -EDGE_SE2 400 401 0.635899 -0.025106 -0.091900 11.758968 -12.342900 0.000000 246.266720 0.000000 2096.883376 -EDGE_SE2 401 402 0.629497 -0.118990 -0.266463 12.583010 -18.442000 0.000000 242.178169 0.000000 1558.672941 -EDGE_SE2 402 403 0.633089 -0.005208 -0.033694 11.265698 -6.068382 0.000000 249.328787 0.000000 2339.677603 -EDGE_SE2 403 404 0.592291 -0.016693 -0.078703 11.809289 -13.806384 0.000000 284.130876 0.000000 2148.504341 -EDGE_SE2 404 405 0.003136 0.000509 0.534523 1319098.360620 3365183.519520 0.000000 8585083.951642 0.000000 1061.678944 -EDGE_SE2 405 406 -0.004868 -0.003220 0.979360 434468.323030 1042340.456046 0.000000 2500772.052588 0.000000 638.102476 -EDGE_SE2 406 407 0.360175 0.041381 0.128315 11.256473 10.438221 0.000000 760.666018 0.000000 1963.718742 -EDGE_SE2 407 408 0.616703 -0.006846 -0.037807 11.290664 -6.721434 0.000000 262.722906 0.000000 2321.169297 -EDGE_SE2 408 409 0.297889 -0.008429 -0.099428 16.743853 -79.045672 0.000000 1120.378773 0.000000 2068.266140 -EDGE_SE2 409 410 0.004317 -0.001082 -0.627510 701653.157370 -1746561.647658 0.000000 4347637.661335 0.000000 943.827609 -EDGE_SE2 410 411 -0.000994 0.000097 -0.588953 22378715.073239 -41773418.211876 0.000000 77976755.075261 0.000000 990.188564 -EDGE_SE2 411 412 0.546073 -0.101761 -0.202851 11.219546 -5.824669 0.000000 323.987402 0.000000 1727.890996 -EDGE_SE2 412 413 0.631438 -0.020731 -0.079531 11.633150 -11.167657 0.000000 250.014003 0.000000 2145.209801 -EDGE_SE2 413 414 0.633214 0.018368 0.103733 12.438356 17.726525 0.000000 247.864474 0.000000 2052.163475 -EDGE_SE2 414 415 0.642786 -0.007808 0.006683 11.192966 4.346517 0.000000 241.911433 0.000000 2466.917009 -EDGE_SE2 415 416 0.306053 0.007945 -0.055552 18.109521 -85.672115 0.000000 1059.879484 0.000000 2243.782429 -EDGE_SE2 416 417 0.267976 -0.110772 -0.425050 12.399497 -38.940001 0.000000 1188.028820 0.000000 1231.061654 -EDGE_SE2 417 418 0.438509 -0.005278 -0.045468 11.679675 -16.999880 0.000000 519.402521 0.000000 2287.275721 -EDGE_SE2 418 419 -0.008030 -0.002982 0.430439 7637.313369 101662.498186 0.000000 1355241.825942 0.000000 1221.803385 -EDGE_SE2 419 420 -0.065315 -0.007710 0.215762 233.503005 2255.992653 0.000000 22896.400499 0.000000 1691.386574 -EDGE_SE2 420 421 -0.013305 -0.007156 0.966654 91010.544219 177739.023434 0.000000 347168.883115 0.000000 646.374313 -EDGE_SE2 421 422 -0.003762 0.002250 1.237922 4986989.532826 -1042930.375544 0.000000 218119.888862 0.000000 499.171886 -EDGE_SE2 422 423 0.004840 0.001590 0.345136 2976.169439 106843.491501 0.000000 3850030.373965 0.000000 1381.680463 -EDGE_SE2 423 424 0.000000 0.000000 0.000279 11.111141 0.108500 0.000000 399.999970 0.000000 2498.605584 -EDGE_SE2 424 425 -0.001184 -0.000773 0.011635 14427656.748816 -22661090.484365 0.000000 35593138.003454 0.000000 2442.824774 -EDGE_SE2 425 426 0.000000 0.000000 -0.009217 11.144147 -3.584186 0.000000 399.966964 0.000000 2454.544407 -EDGE_SE2 426 427 0.032570 0.000297 0.003027 14.602102 -573.593983 0.000000 94256.571445 0.000000 2484.933444 -EDGE_SE2 427 428 0.662478 0.017221 -0.005659 11.327981 -6.850147 0.000000 227.483295 0.000000 2471.943383 -EDGE_SE2 428 429 0.646109 -0.008556 -0.041027 11.287389 -6.342667 0.000000 239.327268 0.000000 2306.832289 -EDGE_SE2 429 430 0.343414 -0.005292 -0.046373 11.913023 -25.889333 0.000000 846.935174 0.000000 2283.320937 -EDGE_SE2 430 431 -0.023101 -0.000669 0.051637 107.302578 4242.658469 0.000000 187139.464787 0.000000 2260.519686 -EDGE_SE2 431 432 0.009902 -0.002457 -0.632717 138557.863737 -337512.999974 0.000000 822224.717657 0.000000 937.817169 -EDGE_SE2 432 433 0.001378 -0.000302 -0.678362 9999210.149914 -20053649.036543 0.000000 40218116.393371 0.000000 887.500758 -EDGE_SE2 433 434 0.513100 -0.123605 -0.228760 11.131390 2.656033 0.000000 358.981365 0.000000 1655.792374 -EDGE_SE2 434 435 0.631214 0.005719 -0.028704 11.453001 -9.049106 0.000000 250.621585 0.000000 2362.431102 -EDGE_SE2 435 436 0.631793 -0.021639 -0.009165 11.261393 5.992726 0.000000 250.081133 0.000000 2454.797368 -EDGE_SE2 436 437 0.639424 0.009664 0.007564 11.124412 -1.761949 0.000000 244.511544 0.000000 2462.604819 -EDGE_SE2 437 438 0.468926 -0.010782 -0.032675 11.152708 -4.294523 0.000000 454.487167 0.000000 2344.297271 -EDGE_SE2 438 439 0.003173 -0.000464 -0.440138 821397.131579 -2704073.669587 0.000000 8902055.008072 0.000000 1205.401670 -EDGE_SE2 439 440 -0.014852 0.005433 -0.648635 34474.470288 -112210.861458 0.000000 365363.698028 0.000000 919.794858 -EDGE_SE2 440 441 -0.024049 0.006849 -0.627372 18805.328328 -51499.965243 0.000000 141131.456239 0.000000 943.987688 -EDGE_SE2 441 442 -0.014780 0.003839 -0.634781 59210.969468 -147922.954315 0.000000 369626.867304 0.000000 935.450222 -EDGE_SE2 442 443 -0.000800 0.000601 -0.571864 520464.172419 7194009.672200 0.000000 99439862.677541 0.000000 1011.835875 -EDGE_SE2 443 444 0.017737 -0.005934 -0.596674 20918.075426 -74425.493858 0.000000 264954.124953 0.000000 980.635254 -EDGE_SE2 444 445 0.013295 -0.004859 -0.663772 47441.842994 -146367.463524 0.000000 451689.453992 0.000000 903.134403 -EDGE_SE2 445 446 0.005858 0.000522 0.198590 34655.344042 314604.971132 0.000000 2856945.136973 0.000000 1740.198174 -EDGE_SE2 446 447 0.027440 0.012672 0.626113 4057.920343 20653.549968 0.000000 105419.866954 0.000000 945.449998 -EDGE_SE2 447 448 0.642139 0.024176 0.015042 11.228997 -5.217769 0.000000 242.055366 0.000000 2426.453558 -EDGE_SE2 448 449 0.651924 0.033077 0.098456 11.620735 10.662081 0.000000 234.177343 0.000000 2071.928088 -EDGE_SE2 449 450 0.646061 0.005408 0.011249 11.113005 0.657701 0.000000 239.562562 0.000000 2444.690013 -EDGE_SE2 450 451 0.475342 0.014049 0.039952 11.157786 4.485326 0.000000 442.142124 0.000000 2311.603906 -EDGE_SE2 451 452 0.003128 0.000025 -0.382848 1482988.188246 -3599772.040321 0.000000 8738081.943140 0.000000 1307.347729 -EDGE_SE2 452 453 -0.001414 0.000044 -0.628930 15847694.024322 -23260624.874036 0.000000 34141069.617498 0.000000 942.182786 -EDGE_SE2 453 454 0.248647 -0.103850 -0.427200 12.471635 -43.090213 0.000000 1375.854801 0.000000 1227.355391 -EDGE_SE2 454 455 0.599080 -0.006587 -0.019767 11.131695 -2.346400 0.000000 278.577271 0.000000 2404.020135 -EDGE_SE2 455 456 0.632985 -0.015395 -0.063626 11.479192 -9.358780 0.000000 249.066485 0.000000 2209.846555 -EDGE_SE2 456 457 0.357560 -0.010532 -0.086657 13.629849 -43.977817 0.000000 778.975316 0.000000 2117.166652 -EDGE_SE2 457 458 0.008700 -0.003028 -0.643446 108644.728053 -340906.127357 0.000000 1069817.962261 0.000000 925.612330 -EDGE_SE2 458 459 -0.005152 0.003223 -0.605857 5968.917352 -126872.556203 0.000000 2701785.030428 0.000000 969.451909 -EDGE_SE2 459 460 0.416054 -0.104238 -0.255576 11.165314 -5.371986 0.000000 543.523585 0.000000 1585.820389 -EDGE_SE2 460 461 0.649228 -0.030811 -0.086883 11.462229 -8.893307 0.000000 236.365424 0.000000 2116.286282 -EDGE_SE2 461 462 0.546102 -0.010087 -0.039056 11.248462 -6.670465 0.000000 335.062438 0.000000 2315.592315 -EDGE_SE2 462 463 0.001368 -0.000392 0.843270 40115968.021050 19291406.563907 0.000000 9277076.794710 0.000000 735.803923 -EDGE_SE2 463 464 0.219986 0.165070 0.680690 12.900768 48.403186 0.000000 1320.227268 0.000000 885.043825 -EDGE_SE2 464 465 0.606621 0.010649 0.008756 11.131273 -2.291927 0.000000 271.643258 0.000000 2456.788366 -EDGE_SE2 465 466 0.582724 -0.019176 -0.054453 11.242627 -6.100006 0.000000 294.042673 0.000000 2248.462015 -EDGE_SE2 466 467 0.609463 -0.011568 -0.042219 11.250443 -5.994134 0.000000 268.982117 0.000000 2301.558596 -EDGE_SE2 467 468 0.645554 -0.004365 -0.036329 11.311111 -6.762186 0.000000 239.747111 0.000000 2327.794866 -EDGE_SE2 468 469 0.640915 -0.033457 -0.083791 11.342903 -7.324322 0.000000 242.550344 0.000000 2128.378819 -EDGE_SE2 469 470 0.496055 0.004661 0.018154 11.141432 3.461691 0.000000 406.321439 0.000000 2411.643257 -EDGE_SE2 470 471 0.003172 -0.000201 -0.200803 186163.578620 -1344733.466553 0.000000 9714134.809108 0.000000 1733.789946 -EDGE_SE2 471 472 0.023951 -0.007826 -0.634338 15457.850073 -46841.273730 0.000000 142054.355974 0.000000 935.957765 -EDGE_SE2 472 473 0.005795 -0.000612 -0.642279 770586.651464 -1294281.400636 0.000000 2173924.317941 0.000000 926.928274 -EDGE_SE2 473 474 0.006655 -0.002332 -0.633006 171083.550792 -560999.777087 0.000000 1839703.994891 0.000000 937.485259 -EDGE_SE2 474 475 0.004438 -0.002279 -0.575983 41353.024051 -405427.745271 0.000000 3975919.455633 0.000000 1006.553705 -EDGE_SE2 475 476 0.004920 -0.000853 -0.653637 861490.486597 -1647075.461555 0.000000 3149079.624245 0.000000 914.238796 -EDGE_SE2 476 477 -0.005593 0.002916 -0.561690 16496.747941 -202882.586381 0.000000 2496811.464145 0.000000 1025.062106 -EDGE_SE2 477 478 -0.008770 0.002879 -0.629412 110767.500374 -343119.326612 0.000000 1062982.494329 0.000000 941.625450 -EDGE_SE2 478 479 -0.005316 0.000950 -0.456182 260653.569609 -908664.641767 0.000000 3167842.768150 0.000000 1178.986119 -EDGE_SE2 479 480 -0.002143 -0.000584 0.559864 1701134.587605 5621159.573829 0.000000 18574462.284739 0.000000 1027.463821 -EDGE_SE2 480 481 0.016780 0.006185 0.998459 113112.243905 150237.129574 0.000000 199577.592411 0.000000 625.964239 -EDGE_SE2 481 482 0.002129 0.000684 0.334515 11133.789831 471436.097728 0.000000 19981887.764379 0.000000 1403.760085 -EDGE_SE2 482 483 0.000000 0.000000 -0.006313 11.126610 -2.454990 0.000000 399.984501 0.000000 2468.731408 -EDGE_SE2 483 484 0.461514 0.003388 0.034531 11.449878 12.456397 0.000000 469.130063 0.000000 2335.893245 -EDGE_SE2 484 485 0.630348 0.014297 -0.002799 11.267128 -6.122690 0.000000 251.389266 0.000000 2486.063539 -EDGE_SE2 485 486 0.640080 -0.006164 0.008991 11.191872 4.336635 0.000000 243.975902 0.000000 2455.644098 -EDGE_SE2 486 487 0.638592 0.022157 0.027672 11.122603 -1.639115 0.000000 244.911905 0.000000 2367.178245 -EDGE_SE2 487 488 0.641671 0.002950 0.035457 11.331743 7.147283 0.000000 242.644669 0.000000 2331.717176 -EDGE_SE2 488 489 0.486254 0.015193 0.029736 11.112036 -0.616989 0.000000 422.520775 0.000000 2357.698224 -EDGE_SE2 489 490 0.004012 0.001331 0.483683 148092.465768 898284.334868 0.000000 5449142.422335 0.000000 1135.684680 -EDGE_SE2 490 491 0.003142 0.002440 0.933695 460500.560493 1642380.154927 0.000000 5857718.767430 0.000000 668.596440 -EDGE_SE2 491 492 0.234443 0.072984 0.301949 11.111148 0.245247 0.000000 1658.639702 0.000000 1474.864302 -EDGE_SE2 492 493 0.622117 -0.019196 -0.055751 11.264294 -6.149470 0.000000 257.979045 0.000000 2242.936641 -EDGE_SE2 493 494 0.632902 -0.009810 -0.061610 11.617811 -10.980862 0.000000 249.080844 0.000000 2218.247533 -EDGE_SE2 494 495 0.635286 0.054465 0.110803 11.261154 5.934337 0.000000 245.819252 0.000000 2026.123532 -EDGE_SE2 495 496 0.641209 0.012116 0.121000 13.521763 23.526915 0.000000 240.723619 0.000000 1989.430553 -EDGE_SE2 496 497 0.639602 -0.025661 -0.083377 11.547127 -10.068553 0.000000 243.615831 0.000000 2130.005800 -EDGE_SE2 497 498 0.634982 -0.033636 -0.099467 11.622457 -10.978306 0.000000 246.809304 0.000000 2068.119413 -EDGE_SE2 498 499 0.638794 -0.029903 -0.015370 11.341287 7.326250 0.000000 244.297416 0.000000 2424.886152 -EDGE_SE2 499 500 0.581639 0.165523 0.331283 11.876261 14.147113 0.000000 272.681816 0.000000 1410.584918 -EDGE_SE2 500 501 0.608658 -0.019295 -0.065817 11.412115 -8.816677 0.000000 269.359185 0.000000 2200.770331 -EDGE_SE2 501 502 0.717458 -0.037860 -0.078159 11.229253 -4.643405 0.000000 193.613030 0.000000 2150.673003 -EDGE_SE2 502 503 0.468033 -0.006282 -0.011528 11.112706 0.842757 0.000000 456.422908 0.000000 2443.341609 -EDGE_SE2 503 504 0.001010 -0.000329 0.882265 76783402.732445 30131648.184749 0.000000 11824394.003794 0.000000 705.632337 -EDGE_SE2 504 505 0.222895 0.156032 0.638914 12.174041 37.721370 0.000000 1349.770700 0.000000 930.738499 -EDGE_SE2 505 506 0.625769 -0.021473 -0.145256 14.102191 -26.846905 0.000000 252.079673 0.000000 1906.052494 -EDGE_SE2 506 507 0.635814 -0.018204 -0.072178 11.558631 -10.268279 0.000000 246.715411 0.000000 2174.734405 -EDGE_SE2 507 508 0.638204 -0.013687 -0.007658 11.155629 3.229265 0.000000 245.359315 0.000000 2462.145389 -EDGE_SE2 508 509 0.642039 -0.008726 -0.051671 11.446573 -8.804866 0.000000 242.212186 0.000000 2260.373525 -EDGE_SE2 509 510 0.372102 -0.006043 -0.009950 11.139237 4.471542 0.000000 722.013535 0.000000 2450.982789 -EDGE_SE2 510 511 0.002084 -0.003553 0.891003 5160201.305808 -1945038.253710 0.000000 733157.306436 0.000000 699.126192 -EDGE_SE2 511 512 0.007335 0.005942 1.221397 297144.725264 495141.758266 0.000000 825112.510283 0.000000 506.626201 -EDGE_SE2 512 513 -0.002181 -0.000566 0.972737 8538321.615481 9762487.124922 0.000000 11162190.656524 0.000000 642.394025 -EDGE_SE2 513 514 0.090094 0.023767 0.232272 18.681434 -295.052416 0.000000 11510.742828 0.000000 1646.367741 -EDGE_SE2 514 515 0.658710 0.013018 0.004928 11.159348 -3.251856 0.000000 230.330528 0.000000 2475.540949 -EDGE_SE2 515 516 0.637378 -0.008635 0.019744 11.371458 7.817478 0.000000 245.848124 0.000000 2404.128580 -EDGE_SE2 516 517 0.633811 0.029113 0.032052 11.156620 -3.285893 0.000000 248.362090 0.000000 2347.128404 -EDGE_SE2 517 518 0.634675 0.007993 0.028090 11.168044 3.673667 0.000000 248.158131 0.000000 2365.253746 -EDGE_SE2 518 519 0.560153 0.002654 -0.012150 11.198833 -5.193662 0.000000 318.608437 0.000000 2440.339501 -EDGE_SE2 519 520 0.197101 -0.000544 0.405434 414.955475 933.778631 0.000000 2170.216473 0.000000 1265.665952 -EDGE_SE2 520 521 0.006002 0.005024 1.019492 163989.160959 490677.362406 0.000000 1468282.469392 0.000000 612.993310 -EDGE_SE2 521 522 0.414779 0.108184 0.270025 11.229248 7.935203 0.000000 544.113259 0.000000 1549.942078 -EDGE_SE2 522 523 0.644866 -0.002177 -0.013262 11.133525 -2.267210 0.000000 240.444921 0.000000 2434.986160 -EDGE_SE2 523 524 0.606582 -0.016880 -0.047652 11.213538 -5.164087 0.000000 271.469885 0.000000 2277.749269 -EDGE_SE2 524 525 0.576823 0.002529 0.018772 11.171018 4.163585 0.000000 300.483332 0.000000 2408.718278 -EDGE_SE2 525 526 0.607148 -0.019364 -0.063586 11.372241 -8.233863 0.000000 270.738644 0.000000 2210.012777 -EDGE_SE2 526 527 0.632532 -0.013902 -0.038859 11.179151 -4.029527 0.000000 249.750981 0.000000 2316.470615 -EDGE_SE2 527 528 0.639965 -0.031254 -0.079842 11.335080 -7.212289 0.000000 243.362638 0.000000 2143.974316 -EDGE_SE2 528 529 0.630138 0.005118 -0.025519 11.383434 -8.091842 0.000000 251.553301 0.000000 2377.128104 -EDGE_SE2 529 530 0.643013 -0.010371 -0.027118 11.138973 -2.535041 0.000000 241.767362 0.000000 2369.732519 -EDGE_SE2 530 531 0.632204 -0.021106 -0.076364 11.552220 -10.254093 0.000000 249.479589 0.000000 2157.852130 -EDGE_SE2 531 532 0.210203 0.000529 0.053978 17.069737 115.687987 0.000000 2257.218094 0.000000 2250.489117 -EDGE_SE2 532 533 0.010481 0.007945 1.006775 71059.615350 189815.528908 0.000000 507128.543079 0.000000 620.787044 -EDGE_SE2 533 534 0.004524 0.002070 0.671821 233294.669406 942460.043629 0.000000 3807527.338703 0.000000 894.459037 -EDGE_SE2 534 535 0.000000 0.000000 0.001338 11.111807 0.520333 0.000000 399.999304 0.000000 2493.323403 -EDGE_SE2 535 536 0.357295 0.039869 0.132965 11.474755 16.648687 0.000000 773.337063 0.000000 1947.632537 -EDGE_SE2 536 537 0.617574 -0.018145 -0.055950 11.288271 -6.664110 0.000000 261.790435 0.000000 2242.091332 -EDGE_SE2 537 538 0.579730 -0.014385 -0.025102 11.111136 -0.083914 0.000000 297.359512 0.000000 2379.062475 -EDGE_SE2 538 539 0.638569 -0.022758 -0.062503 11.280003 -6.281777 0.000000 244.755892 0.000000 2214.520366 -EDGE_SE2 539 540 0.636808 -0.030043 -0.090399 11.550435 -10.149862 0.000000 245.607132 0.000000 2102.660323 -EDGE_SE2 540 541 0.624998 0.051843 0.165279 12.763015 19.972919 0.000000 252.600591 0.000000 1841.111831 -EDGE_SE2 541 542 0.637879 0.044546 0.058251 11.141827 -2.677719 0.000000 244.543713 0.000000 2232.351783 -EDGE_SE2 542 543 0.119005 -0.001027 -0.027249 13.554992 -131.231977 0.000000 7058.031360 0.000000 2369.128157 -EDGE_SE2 543 544 0.004143 0.001613 0.643358 365523.202414 1309813.828807 0.000000 4693733.444673 0.000000 925.711464 -EDGE_SE2 544 545 0.011964 0.010101 1.076679 54873.490374 139170.850294 0.000000 353049.492454 0.000000 579.697285 -EDGE_SE2 545 546 -0.062408 -0.034041 0.835070 2157.534535 6151.646423 0.000000 17641.720853 0.000000 742.394487 -EDGE_SE2 546 547 0.004683 0.006620 1.031598 8884.576322 115819.915331 0.000000 1511737.642851 0.000000 605.709415 -EDGE_SE2 547 548 0.010894 0.005062 0.931876 157491.371124 290395.414023 0.000000 535503.600637 0.000000 669.856096 -EDGE_SE2 548 549 0.018521 0.007174 0.814504 46970.071545 98477.220300 0.000000 206526.818200 0.000000 759.318793 -EDGE_SE2 549 550 0.007039 -0.001728 -0.474679 102346.133104 -429334.075047 0.000000 1801229.739954 0.000000 1149.595399 -EDGE_SE2 550 551 0.012919 -0.003649 -0.620137 63414.169645 -176527.986637 0.000000 491503.647693 0.000000 952.437591 -EDGE_SE2 551 552 0.004870 -0.001510 -0.626201 393240.954950 -1165183.372361 0.000000 3452577.879626 0.000000 945.347676 -EDGE_SE2 552 553 0.131013 -0.071366 -0.571551 34.798953 -324.963500 0.000000 4469.148228 0.000000 1012.238963 -EDGE_SE2 553 554 0.656516 -0.001289 -0.033128 11.325592 -6.879883 0.000000 231.796023 0.000000 2342.241894 -EDGE_SE2 554 555 0.640736 0.024110 0.091367 11.781224 12.453936 0.000000 242.565349 0.000000 2098.932021 -EDGE_SE2 555 556 0.644807 0.029102 0.137696 13.068095 21.074909 0.000000 238.068451 0.000000 1931.468129 -EDGE_SE2 556 557 0.641564 -0.010754 -0.042948 11.270029 -6.066916 0.000000 242.724912 0.000000 2298.342233 -EDGE_SE2 557 558 0.615263 0.021826 0.078921 11.588181 10.969921 0.000000 263.357654 0.000000 2147.636202 -EDGE_SE2 558 559 0.005363 -0.000612 -0.410558 293857.680729 -960260.813810 0.000000 3138046.146667 0.000000 1256.487325 -EDGE_SE2 559 560 0.000000 0.000000 -0.505043 102.152456 -164.670704 0.000000 308.958656 0.000000 1103.677058 -EDGE_SE2 560 561 0.009398 0.003440 0.501104 22371.403618 147733.503165 0.000000 976080.093687 0.000000 1109.476905 -EDGE_SE2 561 562 0.177331 0.126693 0.638384 11.791812 37.750509 0.000000 2104.689083 0.000000 931.340765 -EDGE_SE2 562 563 0.597201 -0.094603 -0.194811 11.484023 -9.885231 0.000000 273.150922 0.000000 1751.223531 -EDGE_SE2 563 564 0.639558 0.013847 0.046248 11.252240 5.735744 0.000000 244.222662 0.000000 2283.866567 -EDGE_SE2 564 565 0.614599 -0.011365 -0.051966 11.395132 -8.481100 0.000000 264.363876 0.000000 2259.105962 -EDGE_SE2 565 566 0.005382 -0.000625 0.804171 2155482.804415 1641813.690713 0.000000 1250573.669361 0.000000 768.041369 -EDGE_SE2 566 567 -0.001231 0.000678 0.856086 48423129.554559 10395439.301461 0.000000 2231696.342135 0.000000 725.677769 -EDGE_SE2 567 568 0.583042 0.017568 0.046196 11.184172 4.544849 0.000000 293.831241 0.000000 2284.093607 -EDGE_SE2 568 569 0.628101 -0.010139 -0.052147 11.425095 -8.716657 0.000000 253.098625 0.000000 2258.328765 -EDGE_SE2 569 570 0.639821 -0.001979 -0.004808 11.111796 -0.399705 0.000000 244.274090 0.000000 2476.132272 -EDGE_SE2 570 571 0.348563 -0.003118 -0.043397 12.074353 -27.948587 0.000000 822.042846 0.000000 2296.364589 -EDGE_SE2 571 572 0.037203 -0.034953 -0.237353 9379.952208 16482.424026 0.000000 29008.326343 0.000000 1632.874389 -EDGE_SE2 572 573 0.082599 0.001138 0.282645 1044.389047 3750.080318 0.000000 13621.294688 0.000000 1519.592228 -EDGE_SE2 573 574 0.006029 0.005935 0.955816 43915.035779 243749.664142 0.000000 1353281.899972 0.000000 653.557826 -EDGE_SE2 574 575 0.017352 0.009648 1.009306 58713.654313 106982.648688 0.000000 194982.001569 0.000000 619.224094 -EDGE_SE2 575 576 -0.000693 0.000729 1.024139 92116121.130465 -24932672.587732 0.000000 6748430.711509 0.000000 610.181931 -EDGE_SE2 576 577 0.005808 0.005602 0.968758 61460.098525 300963.429168 0.000000 1474062.832839 0.000000 644.993297 -EDGE_SE2 577 578 0.020578 -0.005874 -0.365770 1686.236926 -19051.115635 0.000000 216678.422748 0.000000 1340.246481 -EDGE_SE2 578 579 0.649778 -0.007064 0.014101 11.251830 5.633981 0.000000 236.679123 0.000000 2430.958734 -EDGE_SE2 579 580 0.646533 0.021127 0.074055 11.501231 9.420338 0.000000 238.586536 0.000000 2167.139990 -EDGE_SE2 580 581 0.641277 0.007721 0.041304 11.309760 6.786133 0.000000 242.935131 0.000000 2305.605160 -EDGE_SE2 581 582 0.642222 0.008953 0.042003 11.293216 6.487457 0.000000 242.225126 0.000000 2302.512889 -EDGE_SE2 582 583 0.324533 0.007424 0.078126 13.971366 51.714099 0.000000 946.114403 0.000000 2150.804664 -EDGE_SE2 583 584 0.003585 0.000066 0.840201 4172109.666193 3878928.313892 0.000000 3606370.037121 0.000000 738.260001 -EDGE_SE2 584 585 0.609333 0.022255 0.117010 12.778611 20.669000 0.000000 267.307574 0.000000 2003.668573 -EDGE_SE2 585 586 0.610182 0.017652 0.096276 12.276387 17.274488 0.000000 267.194725 0.000000 2080.176547 -EDGE_SE2 586 587 0.632351 0.024309 0.024257 11.158994 -3.379740 0.000000 249.665755 0.000000 2382.989492 -EDGE_SE2 587 588 0.643770 -0.006740 0.003322 11.154886 3.173805 0.000000 241.219104 0.000000 2483.472403 -EDGE_SE2 588 589 0.633840 0.040950 0.080270 11.169866 3.729294 0.000000 247.815430 0.000000 2142.275779 -EDGE_SE2 589 590 0.644706 0.014870 0.073779 11.700561 11.612184 0.000000 239.871719 0.000000 2168.254200 -EDGE_SE2 590 591 0.632424 0.012303 0.002621 11.178752 -4.018640 0.000000 249.862554 0.000000 2486.946343 -EDGE_SE2 591 592 0.642169 0.006914 0.001997 11.128903 -2.028750 0.000000 242.448415 0.000000 2490.044831 -EDGE_SE2 592 593 0.271993 0.001273 0.212505 68.183858 270.652782 0.000000 1294.612122 0.000000 1700.485497 -EDGE_SE2 593 594 0.014453 0.008841 0.993097 64316.365031 135154.721370 0.000000 284074.972077 0.000000 629.336815 -EDGE_SE2 594 595 0.571176 0.080832 0.240324 13.980407 28.672593 0.000000 297.633480 0.000000 1625.061173 -EDGE_SE2 595 596 0.575835 0.004878 -0.017875 11.312665 -7.648565 0.000000 301.358130 0.000000 2412.965503 -EDGE_SE2 596 597 0.635351 -0.013108 -0.034135 11.154253 -3.193993 0.000000 247.578146 0.000000 2337.682549 -EDGE_SE2 597 598 0.604127 -0.006451 -0.039602 11.330957 -7.598611 0.000000 273.744081 0.000000 2313.160651 -EDGE_SE2 598 599 0.385673 -0.010715 -0.063459 11.952011 -23.555254 0.000000 670.939617 0.000000 2210.540655 -EDGE_SE2 599 600 0.008688 0.002526 0.814757 314076.796383 533827.575685 0.000000 907375.060518 0.000000 759.107091 -EDGE_SE2 600 601 0.021686 0.011399 1.028615 44738.476040 73830.093373 0.000000 121880.188267 0.000000 607.492253 -EDGE_SE2 601 602 0.018101 0.007667 1.024411 88285.368598 122674.329555 0.000000 170491.062574 0.000000 610.017974 -EDGE_SE2 602 603 0.186319 0.162792 0.780432 17.405089 100.856712 0.000000 1627.271409 0.000000 788.658931 -EDGE_SE2 603 604 0.621077 -0.063008 -0.240492 15.850028 -33.777361 0.000000 251.864479 0.000000 1624.621039 -EDGE_SE2 604 605 0.633484 0.018906 0.082620 11.773196 12.531662 0.000000 248.305264 0.000000 2132.985568 -EDGE_SE2 605 606 0.635159 0.014918 0.042086 11.193000 4.401211 0.000000 247.658010 0.000000 2302.146123 -EDGE_SE2 606 607 0.573078 -0.057501 -0.081034 11.215573 5.506259 0.000000 301.350193 0.000000 2139.248825 -EDGE_SE2 607 608 0.372357 0.012526 0.044458 11.194332 7.682633 0.000000 720.342546 0.000000 2291.701491 -EDGE_SE2 608 609 0.009981 0.001237 0.336295 44194.195786 204270.386082 0.000000 944408.518792 0.000000 1400.022844 -EDGE_SE2 609 610 0.008024 0.008992 0.932617 5618.242041 61880.517104 0.000000 682926.927594 0.000000 669.342525 -EDGE_SE2 610 611 0.619179 0.006175 0.002964 11.123375 -1.749910 0.000000 260.797662 0.000000 2485.245630 -EDGE_SE2 611 612 0.495865 -0.011645 0.002777 11.383631 10.376429 0.000000 406.201910 0.000000 2486.172625 -EDGE_SE2 612 613 -0.032580 0.001507 0.463705 22407.672171 40045.920128 0.000000 71614.770023 0.000000 1166.898001 -EDGE_SE2 613 614 0.006074 0.002811 1.037837 720992.332387 1043867.597380 0.000000 1511367.480327 0.000000 602.006419 -EDGE_SE2 614 615 0.468918 0.167392 0.304515 11.688040 -15.032631 0.000000 402.805589 0.000000 1469.067849 -EDGE_SE2 615 616 0.634148 0.014484 -0.051156 12.408628 -17.503753 0.000000 247.240026 0.000000 2262.588948 -EDGE_SE2 616 617 0.620915 -0.099361 -0.217030 11.933466 -14.077035 0.000000 252.081317 0.000000 1687.863964 -EDGE_SE2 617 618 0.637519 0.006833 0.010118 11.111196 -0.140914 0.000000 246.016254 0.000000 2450.167576 -EDGE_SE2 618 619 0.602001 -0.001093 -0.001468 11.111143 0.091989 0.000000 275.933653 0.000000 2492.676131 -EDGE_SE2 619 620 0.611116 -0.021079 -0.096329 12.090448 -15.813910 0.000000 266.467179 0.000000 2079.975428 -EDGE_SE2 620 621 -0.351742 0.001000 -0.051606 13.005075 -38.809484 0.000000 806.361703 0.000000 2260.652962 -EDGE_SE2 621 622 -0.288473 0.005647 -0.075042 14.769187 -65.879798 0.000000 1197.567688 0.000000 2163.162499 -EDGE_SE2 622 623 -0.003587 0.000519 -0.537149 1119299.663768 -2695881.271885 0.000000 6493221.297933 0.000000 1058.054588 -EDGE_SE2 623 624 -0.002684 0.001648 -0.642690 84963.761375 -921398.117942 0.000000 9993513.245668 0.000000 926.464498 -EDGE_SE2 624 625 0.007502 -0.002176 -0.658633 221323.684950 -560152.893180 0.000000 1417785.340096 0.000000 908.739498 -EDGE_SE2 625 626 0.007582 -0.000720 -0.618128 430777.189603 -746427.081854 0.000000 1293412.370737 0.000000 954.803710 -EDGE_SE2 626 627 0.339972 -0.139061 -0.410806 11.481721 -16.444927 0.000000 740.815480 0.000000 1256.045618 -EDGE_SE2 627 628 0.641810 -0.041240 -0.100917 11.422475 -8.468830 0.000000 241.456222 0.000000 2062.675227 -EDGE_SE2 628 629 0.640763 0.030371 0.071050 11.241212 5.491249 0.000000 242.883687 0.000000 2179.317557 -EDGE_SE2 629 630 0.608678 0.024217 0.085089 11.641507 11.694459 0.000000 268.956806 0.000000 2123.289866 -EDGE_SE2 630 631 0.520687 0.012252 0.020220 11.115021 -1.182335 0.000000 368.639644 0.000000 2401.885734 -EDGE_SE2 631 632 0.003617 -0.000095 0.676209 3189142.029325 3767706.315238 0.000000 4451258.564395 0.000000 889.781790 -EDGE_SE2 632 633 0.052581 0.076324 1.075605 146.388666 1246.996416 0.000000 11505.997044 0.000000 580.297357 -EDGE_SE2 633 634 0.651291 -0.000853 0.025227 11.269271 5.958489 0.000000 235.590568 0.000000 2378.482380 -EDGE_SE2 634 635 0.638650 -0.043082 -0.137291 12.248607 -16.238485 0.000000 242.925867 0.000000 1932.844002 -EDGE_SE2 635 636 0.635527 0.004328 -0.019084 11.269630 -6.120407 0.000000 247.419146 0.000000 2407.243610 -EDGE_SE2 636 637 0.632517 -0.030952 -0.100684 11.749524 -12.316243 0.000000 248.715635 0.000000 2063.548601 -EDGE_SE2 637 638 0.419086 -0.009115 -0.030747 11.156304 -5.021448 0.000000 569.055296 0.000000 2353.075434 -EDGE_SE2 638 639 0.640680 0.028288 0.078765 11.389438 8.031492 0.000000 242.870396 0.000000 2148.257386 -EDGE_SE2 639 640 0.645185 -0.028949 -0.069702 11.252424 -5.682397 0.000000 239.608542 0.000000 2184.813613 -EDGE_SE2 640 641 0.631149 0.010125 -0.010170 11.275857 -6.284012 0.000000 250.806675 0.000000 2449.915330 -EDGE_SE2 641 642 0.608456 -0.016443 -0.047551 11.220217 -5.312718 0.000000 269.804578 0.000000 2278.188510 -EDGE_SE2 642 643 0.501922 -0.014902 -0.065028 11.592531 -13.614212 0.000000 396.111379 0.000000 2204.032313 -EDGE_SE2 643 644 0.006192 0.003634 0.763605 103362.922493 435664.745636 0.000000 1836493.394795 0.000000 803.780315 -EDGE_SE2 644 645 0.147583 0.161030 0.956903 45.062660 263.875747 0.000000 2061.986889 0.000000 652.831965 -EDGE_SE2 645 646 0.624864 0.078137 0.297396 18.253618 40.874666 0.000000 245.025960 0.000000 1485.234058 -EDGE_SE2 646 647 0.626416 0.074352 0.291155 18.229469 40.732090 0.000000 244.184966 0.000000 1499.626982 -EDGE_SE2 647 648 0.630437 0.066336 0.168411 12.070691 15.073373 0.000000 247.888334 0.000000 1831.254626 -EDGE_SE2 648 649 0.592663 -0.072497 -0.334526 23.127883 -55.612924 0.000000 268.484510 0.000000 1403.737590 -EDGE_SE2 649 650 0.621511 -0.143009 -0.254374 11.297892 -6.619122 0.000000 245.678334 0.000000 1588.861060 -EDGE_SE2 650 651 0.144853 0.010732 0.805829 2122.876396 2350.861156 0.000000 2628.138776 0.000000 766.631681 -EDGE_SE2 651 652 0.002718 0.001656 1.033599 2156297.352241 4079208.548390 0.000000 7716956.136248 0.000000 604.518184 -EDGE_SE2 652 653 0.014957 0.011986 1.038848 34378.938116 90407.858801 0.000000 237837.637992 0.000000 601.409535 -EDGE_SE2 653 654 0.267736 0.151078 0.517447 11.125502 3.881714 0.000000 1058.107188 0.000000 1085.707770 -EDGE_SE2 654 655 0.563434 -0.005218 -0.003312 11.121867 1.807836 0.000000 314.964252 0.000000 2483.521908 -EDGE_SE2 655 656 0.610635 -0.014726 -0.011084 11.154711 3.346600 0.000000 267.986619 0.000000 2445.487982 -EDGE_SE2 656 657 0.631799 0.103933 0.278849 14.219359 26.720114 0.000000 240.811095 0.000000 1528.626076 -EDGE_SE2 657 658 0.640227 0.007103 -0.006984 11.187191 -4.208049 0.000000 243.861599 0.000000 2465.442445 -EDGE_SE2 658 659 0.497774 -0.026439 -0.071049 11.237681 -7.036752 0.000000 402.323496 0.000000 2179.321626 -EDGE_SE2 659 660 0.006409 -0.000096 0.600351 810833.659716 1147194.412851 0.000000 1623122.139710 0.000000 976.134174 -EDGE_SE2 660 661 0.169432 0.128488 0.653648 11.162367 10.620021 0.000000 2211.525163 0.000000 914.226633 -EDGE_SE2 661 662 0.629749 0.003998 0.069631 12.075080 15.212434 0.000000 251.179162 0.000000 2185.103670 -EDGE_SE2 662 663 0.621897 0.037784 0.101008 11.511766 9.929781 0.000000 257.209513 0.000000 2062.334274 -EDGE_SE2 663 664 0.007280 -0.002392 0.538314 970988.716591 843174.347297 0.000000 732204.084386 0.000000 1056.452618 -EDGE_SE2 664 665 0.026579 0.029587 0.878851 111.857316 2521.446990 0.000000 63117.159733 0.000000 708.199031 -EDGE_SE2 665 666 0.623804 0.018831 0.034452 11.115596 1.049644 0.000000 256.744183 0.000000 2336.250038 -EDGE_SE2 666 667 0.580478 0.005759 0.002114 11.128518 -2.229713 0.000000 296.729354 0.000000 2489.463423 -EDGE_SE2 667 668 0.608507 -0.000467 0.023179 11.259577 6.198692 0.000000 269.917040 0.000000 2388.013473 -EDGE_SE2 668 669 0.638606 0.001692 -0.027742 11.327262 -7.110068 0.000000 244.989751 0.000000 2366.855797 -EDGE_SE2 669 670 0.636315 -0.034673 -0.062258 11.125493 -1.838895 0.000000 246.230714 0.000000 2215.542002 -EDGE_SE2 670 671 0.308072 0.001150 0.052261 13.564460 50.513802 0.000000 1051.177078 0.000000 2257.839465 -EDGE_SE2 671 672 -0.576602 0.036531 -0.092061 11.350145 -8.300360 0.000000 299.337680 0.000000 2096.265145 -EDGE_SE2 672 673 -0.262392 0.011580 -0.133741 22.638322 -128.254010 0.000000 1438.090461 0.000000 1944.967298 -EDGE_SE2 673 674 -0.005814 0.002674 -0.655099 120542.083833 -528941.427411 0.000000 2321232.182459 0.000000 912.624358 -EDGE_SE2 674 675 -0.014637 0.005942 -0.615221 20765.078980 -88798.929746 0.000000 379950.502646 0.000000 958.243990 -EDGE_SE2 675 676 -0.006630 0.003741 -0.630060 23265.609241 -198963.775781 0.000000 1702330.544222 0.000000 940.876947 -EDGE_SE2 676 677 -0.012856 0.004015 -0.622499 54491.338930 -164518.778220 0.000000 496823.064188 0.000000 949.666532 -EDGE_SE2 677 678 0.191689 -0.090627 -0.451238 11.315076 -21.245540 0.000000 2224.105339 0.000000 1187.032313 -EDGE_SE2 678 679 0.570835 0.011396 0.059784 11.579724 11.761290 0.000000 306.296841 0.000000 2225.898166 -EDGE_SE2 679 680 0.434701 0.056384 0.340338 33.525701 104.470312 0.000000 498.028172 0.000000 1391.589518 -EDGE_SE2 680 681 0.616588 -0.017915 -0.025786 11.113788 0.820773 0.000000 262.808564 0.000000 2375.890788 -EDGE_SE2 681 682 0.634780 0.014947 0.049632 11.272332 6.178273 0.000000 247.873967 0.000000 2269.163993 -EDGE_SE2 682 683 0.300050 0.004977 0.161644 34.081549 157.239703 0.000000 1087.465169 0.000000 1852.652221 -EDGE_SE2 683 684 0.008066 0.002610 1.069340 655223.236656 694424.027218 0.000000 735993.720066 0.000000 583.816417 -EDGE_SE2 684 685 0.408559 0.177472 0.439476 11.545242 14.621378 0.000000 503.554260 0.000000 1206.510628 -EDGE_SE2 685 686 0.481253 -0.056338 -0.146676 11.487851 -12.495538 0.000000 425.557060 0.000000 1901.334651 -EDGE_SE2 686 687 0.633778 -0.013418 -0.036773 11.168993 -3.709053 0.000000 248.788111 0.000000 2325.801528 -EDGE_SE2 687 688 0.633546 -0.023306 -0.013253 11.242546 5.587827 0.000000 248.672293 0.000000 2435.029417 -EDGE_SE2 688 689 0.601923 0.011148 0.026350 11.127352 2.073709 0.000000 275.894586 0.000000 2373.280306 -EDGE_SE2 689 690 0.577060 0.031776 0.072952 11.203899 5.171117 0.000000 299.300801 0.000000 2171.597942 -EDGE_SE2 690 691 0.431081 -0.000968 -0.029004 11.488376 -14.095403 0.000000 537.744126 0.000000 2361.053797 -EDGE_SE2 691 692 -0.008991 -0.001137 0.532009 190083.391915 441924.473409 0.000000 1027500.439761 0.000000 1065.166196 -EDGE_SE2 692 693 -0.002490 -0.004336 1.036147 719.063258 -53205.166853 0.000000 3998572.017505 0.000000 603.006163 -EDGE_SE2 693 694 -0.004998 -0.000127 0.832038 2085335.472274 1998610.421428 0.000000 1915513.414270 0.000000 744.853827 -EDGE_SE2 694 695 0.001511 0.001312 0.796919 166502.359918 2031923.976353 0.000000 24798401.868961 0.000000 774.253197 -EDGE_SE2 695 696 0.013256 0.002978 1.021557 279104.028874 270742.205141 0.000000 262652.464508 0.000000 611.741616 -EDGE_SE2 696 697 0.014932 -0.001676 -0.254525 8971.193925 -62353.454280 0.000000 433930.455572 0.000000 1588.478599 -EDGE_SE2 697 698 0.334592 -0.212289 -0.657753 16.435194 -57.473689 0.000000 631.541865 0.000000 909.704543 -EDGE_SE2 698 699 0.645635 0.022320 0.112426 12.493854 17.721303 0.000000 238.228242 0.000000 2020.215724 -EDGE_SE2 699 700 0.637453 0.017479 0.051850 11.251293 5.735412 0.000000 245.770774 0.000000 2259.604267 -EDGE_SE2 700 701 0.580454 0.032375 0.057254 11.111784 0.437751 0.000000 295.879739 0.000000 2236.564024 -EDGE_SE2 701 702 0.576635 -0.068380 -0.117543 11.111180 0.139782 0.000000 296.573785 0.000000 2001.757773 -EDGE_SE2 702 703 0.620013 -0.001990 0.031996 11.419637 8.759800 0.000000 259.823358 0.000000 2347.383139 -EDGE_SE2 703 704 0.636789 0.025712 0.015764 11.253257 -5.779092 0.000000 246.065659 0.000000 2423.005361 -EDGE_SE2 704 705 0.643209 0.030107 0.064485 11.183283 4.074239 0.000000 241.110228 0.000000 2206.281466 -EDGE_SE2 705 706 0.514363 -0.039200 -0.277636 25.728836 -71.533878 0.000000 361.172137 0.000000 1531.530774 -EDGE_SE2 706 707 0.003881 -0.001622 -0.533536 106334.972814 -767757.982233 0.000000 5543943.675681 0.000000 1063.046000 -EDGE_SE2 707 708 -0.003691 0.001823 -0.654541 223240.622476 -1125745.646018 0.000000 5677142.471470 0.000000 913.240034 -EDGE_SE2 708 709 -0.067573 0.007248 -0.341732 1183.126036 -4897.853094 0.000000 20479.250565 0.000000 1388.700057 -EDGE_SE2 709 710 -0.014148 0.002189 -0.575257 81786.222098 -182245.919313 0.000000 406168.616806 0.000000 1007.481714 -EDGE_SE2 710 711 0.000644 0.000757 -0.611741 100340028.914478 -9326915.500645 0.000000 866976.800978 0.000000 962.386453 -EDGE_SE2 711 712 -0.004894 0.001455 -0.574644 304805.524713 -1037473.284048 0.000000 3531410.530133 0.000000 1008.266280 -EDGE_SE2 712 713 0.477793 -0.132293 -0.294292 11.342379 -9.563968 0.000000 406.624099 0.000000 1492.366443 -EDGE_SE2 713 714 0.567602 -0.077796 -0.205164 12.504613 -20.177539 0.000000 303.276539 0.000000 1721.264882 -EDGE_SE2 714 715 0.633486 0.028573 0.105231 11.969833 14.257268 0.000000 247.822982 0.000000 2046.604352 -EDGE_SE2 715 716 0.639776 -0.022007 -0.136206 13.517556 -23.552031 0.000000 241.616372 0.000000 1936.537236 -EDGE_SE2 716 717 0.632432 0.018685 0.086524 11.885459 13.573115 0.000000 249.026579 0.000000 2117.685003 -EDGE_SE2 717 718 0.776383 -0.078919 -0.173172 11.900522 -10.964942 0.000000 163.414455 0.000000 1816.421486 -EDGE_SE2 718 719 0.636837 0.028128 0.076417 11.355832 7.579234 0.000000 245.847108 0.000000 2157.639641 -EDGE_SE2 719 720 0.632529 -0.017060 -0.057232 11.329664 -7.218698 0.000000 249.541537 0.000000 2236.657107 -EDGE_SE2 720 721 0.638105 0.018029 0.070335 11.525887 9.849065 0.000000 244.982458 0.000000 2182.230164 -EDGE_SE2 721 722 0.025289 0.000135 0.068811 640.196727 9897.678857 0.000000 155735.617178 0.000000 2188.457812 -EDGE_SE2 722 723 0.006445 0.007867 1.098357 43580.586864 200575.333708 0.000000 923374.631033 0.000000 567.781519 -EDGE_SE2 723 724 0.010463 0.006590 1.077175 158723.565692 280363.009735 0.000000 495267.878673 0.000000 579.420471 -EDGE_SE2 724 725 0.010678 0.005468 0.639413 19006.567647 113303.006550 0.000000 675834.367551 0.000000 930.171994 -EDGE_SE2 725 726 0.290588 0.137540 0.458060 11.355500 15.286330 0.000000 967.259181 0.000000 1175.950976 -EDGE_SE2 726 727 0.635716 0.023142 0.032574 11.114541 -0.899732 0.000000 247.111115 0.000000 2344.755903 -EDGE_SE2 727 728 0.637440 -0.010740 0.003027 11.203891 4.667737 0.000000 245.942991 0.000000 2484.933444 -EDGE_SE2 728 729 0.638658 0.024646 0.059374 11.212224 4.859951 0.000000 244.701967 0.000000 2227.621438 -EDGE_SE2 729 730 0.629725 0.021040 0.039078 11.118875 1.367234 0.000000 251.883563 0.000000 2315.494262 -EDGE_SE2 730 731 0.635128 0.000033 0.013336 11.152896 3.145232 0.000000 247.858605 0.000000 2434.630538 -EDGE_SE2 731 732 0.123756 0.004174 0.448902 1070.432039 2403.087812 0.000000 5462.557297 0.000000 1190.863501 -EDGE_SE2 732 733 0.000083 0.001424 1.000990 11773189.473890 -20977730.743166 0.000000 37378633.397513 0.000000 624.381709 -EDGE_SE2 733 734 0.552572 0.038413 0.079326 11.142093 3.122929 0.000000 325.902343 0.000000 2146.024772 -EDGE_SE2 734 735 0.600091 -0.007820 -0.030980 11.196982 -4.783337 0.000000 277.560623 0.000000 2352.011970 -EDGE_SE2 735 736 0.005111 -0.000077 0.602090 1281626.650843 1806077.209424 0.000000 2545169.768506 0.000000 974.015847 -EDGE_SE2 736 737 0.008677 0.008287 0.804006 1211.768495 28855.042050 0.000000 693475.760746 0.000000 768.181871 -EDGE_SE2 737 738 0.598858 0.011099 0.020076 11.111750 0.413426 0.000000 278.741652 0.000000 2402.563911 -EDGE_SE2 738 739 0.625793 -0.054407 -0.056575 11.331296 7.301220 0.000000 253.215672 0.000000 2239.439570 -EDGE_SE2 739 740 0.522300 0.024647 0.064307 11.215445 6.082008 0.000000 365.653135 0.000000 2207.019506 -EDGE_SE2 740 741 -0.000981 -0.000074 -0.367127 18919361.341981 -39981559.645604 0.000000 84491555.069462 0.000000 1337.587765 -EDGE_SE2 741 742 0.011219 -0.001990 -0.650273 160936.661861 -313150.446277 0.000000 609381.105788 0.000000 917.969856 -EDGE_SE2 742 743 0.004918 -0.001325 -0.614408 456134.364729 -1245052.232453 0.000000 3398555.362562 0.000000 959.208992 -EDGE_SE2 743 744 0.009459 -0.002723 -0.537185 66620.250624 -253582.580605 0.000000 965406.035238 0.000000 1058.005031 -EDGE_SE2 744 745 0.002047 -0.000908 -0.591149 593780.161881 -3389363.396311 0.000000 19347237.473549 0.000000 987.457262 -EDGE_SE2 745 746 0.494445 -0.175533 -0.472825 17.183290 -45.841209 0.000000 357.183969 0.000000 1152.491455 -EDGE_SE2 746 747 0.638469 -0.005433 -0.034207 11.265722 -6.015279 0.000000 245.140841 0.000000 2337.357068 -EDGE_SE2 747 748 0.577733 -0.023010 -0.067174 11.326766 -7.878192 0.000000 298.912675 0.000000 2195.176966 -EDGE_SE2 748 749 0.069701 0.001911 0.431306 3186.109937 7428.826456 0.000000 17392.995499 0.000000 1220.323642 -EDGE_SE2 749 750 0.003545 0.004632 0.930924 530.021459 39050.161440 0.000000 2938698.139852 0.000000 670.516775 -EDGE_SE2 750 751 0.221430 0.155717 0.642978 12.335877 40.697289 0.000000 1363.426091 0.000000 926.139724 -EDGE_SE2 751 752 0.622533 -0.037077 -0.102200 11.559653 -10.494975 0.000000 256.672582 0.000000 2057.875967 -EDGE_SE2 752 753 0.636978 0.006588 0.064190 11.792803 12.647310 0.000000 245.754530 0.000000 2207.504825 -EDGE_SE2 753 754 0.633682 -0.063409 -0.161022 11.994454 -14.394628 0.000000 245.680720 0.000000 1854.637814 -EDGE_SE2 754 755 0.634065 0.020125 0.076284 11.582011 10.562020 0.000000 248.011279 0.000000 2158.172928 -EDGE_SE2 755 756 0.630931 -0.052093 -0.191444 13.935694 -25.795248 0.000000 246.683906 0.000000 1761.135371 -EDGE_SE2 756 757 0.634731 0.023338 0.098895 12.024248 14.675298 0.000000 246.962348 0.000000 2070.272980 -EDGE_SE2 757 758 0.593380 0.045763 0.126480 11.775379 13.406035 0.000000 281.667065 0.000000 1970.121623 -EDGE_SE2 758 759 0.549299 -0.065716 -0.139191 11.238871 -6.348958 0.000000 326.618479 0.000000 1926.401990 -EDGE_SE2 759 760 0.597230 0.041521 0.114150 11.646967 11.969500 0.000000 278.475751 0.000000 2013.968527 -EDGE_SE2 760 761 0.024133 -0.000801 0.183513 7939.377412 36011.249548 0.000000 163579.039427 0.000000 1784.818025 -EDGE_SE2 761 762 0.000826 0.001155 1.009551 175055.866129 2941983.942252 0.000000 49446048.586526 0.000000 619.072925 -EDGE_SE2 762 763 0.580729 0.162376 0.292110 11.211099 5.135900 0.000000 274.918098 0.000000 1497.411049 -EDGE_SE2 763 764 0.445705 -0.005172 -0.027993 11.243315 -8.065663 0.000000 503.190215 0.000000 2365.700131 -EDGE_SE2 764 765 0.008361 0.001719 0.638276 244331.555302 525026.230585 0.000000 1128252.931867 0.000000 931.463562 -EDGE_SE2 765 766 0.005344 0.005700 0.998613 53037.087612 289905.348468 0.000000 1584991.089178 0.000000 625.867778 -EDGE_SE2 766 767 0.002645 0.001415 0.929266 1998232.117440 4267585.657123 0.000000 9114261.878773 0.000000 671.669747 -EDGE_SE2 767 768 0.005861 0.004878 0.721092 1261.041135 46349.790224 0.000000 1718749.770688 0.000000 843.979381 -EDGE_SE2 768 769 0.614146 0.107525 0.156299 11.182444 -4.189539 0.000000 257.172044 0.000000 1869.819603 -EDGE_SE2 769 770 0.636280 -0.020581 -0.067664 11.405109 -8.318026 0.000000 246.451563 0.000000 2193.162494 -EDGE_SE2 770 771 0.070000 0.001891 0.559330 5261.360007 8913.246458 0.000000 15142.958000 0.000000 1028.167661 -EDGE_SE2 771 772 0.006744 0.005112 0.865896 64935.054362 294012.600872 0.000000 1331467.660768 0.000000 718.067286 -EDGE_SE2 772 773 0.581158 0.026914 0.085198 11.541618 11.055471 0.000000 295.017264 0.000000 2122.863350 -EDGE_SE2 773 774 0.551019 0.013319 0.021516 11.113346 -0.843063 0.000000 329.161850 0.000000 2395.795043 -EDGE_SE2 774 775 0.004023 0.000830 0.842994 2110367.484852 2837367.218934 0.000000 3814842.025573 0.000000 736.024077 -EDGE_SE2 775 776 0.192558 0.126940 0.615944 13.158809 61.827681 0.000000 1877.920658 0.000000 957.386713 -EDGE_SE2 776 777 0.634282 0.011394 0.058423 11.499517 9.594027 0.000000 248.093510 0.000000 2231.626302 -EDGE_SE2 777 778 0.632295 0.006026 -0.021506 11.341246 -7.412667 0.000000 249.873975 0.000000 2395.841950 -EDGE_SE2 778 779 0.635747 0.009144 0.017394 11.113255 0.711673 0.000000 247.364613 0.000000 2415.247629 -EDGE_SE2 779 780 0.227263 -0.012780 -0.131130 21.872372 -143.298917 0.000000 1919.305655 0.000000 1953.956841 -EDGE_SE2 780 781 0.009670 -0.001829 -0.575170 148004.400196 -361794.734360 0.000000 884479.796277 0.000000 1007.593008 -EDGE_SE2 781 782 0.270298 -0.114336 -0.431075 12.208626 -35.507762 0.000000 1159.889369 0.000000 1220.717636 -EDGE_SE2 782 783 0.611405 -0.011479 -0.001409 11.188373 4.449352 0.000000 267.339350 0.000000 2492.969862 -EDGE_SE2 783 784 0.240113 0.001039 -0.022196 12.323249 -45.688698 0.000000 1733.239839 0.000000 2392.608572 -EDGE_SE2 784 785 0.007959 -0.002926 -0.554001 55828.680061 -272963.055421 0.000000 1334874.506740 0.000000 1035.230954 -EDGE_SE2 785 786 0.004434 -0.000587 -0.590466 980656.281246 -1985068.949325 0.000000 4018282.809762 0.000000 988.305540 -EDGE_SE2 786 787 -0.002001 0.000044 -0.492560 5130982.209484 -10089392.987608 0.000000 19839501.318078 0.000000 1122.215884 -EDGE_SE2 787 788 0.007208 -0.003016 -0.631388 88847.369552 -370943.890957 0.000000 1548921.124356 0.000000 939.345765 -EDGE_SE2 788 789 0.007538 -0.002876 -0.554007 54527.813069 -284219.801981 0.000000 1481775.284964 0.000000 1035.223369 -EDGE_SE2 789 790 0.500376 -0.029812 -0.068057 11.139386 -3.307267 0.000000 397.957897 0.000000 2191.548808 -EDGE_SE2 790 791 0.628826 0.029153 0.097815 11.750061 12.398882 0.000000 251.712715 0.000000 2074.348338 -EDGE_SE2 791 792 0.315704 0.029420 0.702590 333.591596 461.723980 0.000000 672.202381 0.000000 862.422051 -EDGE_SE2 792 793 0.615391 0.019005 0.041490 11.139594 2.682653 0.000000 263.776793 0.000000 2304.781716 -EDGE_SE2 793 794 0.637735 0.016326 0.067824 11.529260 9.895724 0.000000 245.298956 0.000000 2192.505307 -EDGE_SE2 794 795 -0.033087 -0.024410 0.423241 2638.528642 -12185.315173 0.000000 56523.600693 0.000000 1234.193106 -EDGE_SE2 795 796 -0.015774 0.000648 0.650956 163376.535826 197117.295726 0.000000 237853.533044 0.000000 917.210485 -EDGE_SE2 796 797 0.294705 0.074097 0.255423 11.199896 9.754709 0.000000 1082.852214 0.000000 1586.206944 -EDGE_SE2 797 798 0.626641 0.006060 0.046881 11.448159 9.053531 0.000000 254.300351 0.000000 2281.105507 -EDGE_SE2 798 799 0.635666 0.023992 0.062363 11.254358 5.812774 0.000000 246.986103 0.000000 2215.104071 -EDGE_SE2 799 800 0.634468 0.014510 0.069489 11.626291 11.041861 0.000000 247.771504 0.000000 2185.683957 -EDGE_SE2 800 801 0.637847 0.031241 0.082897 11.380932 7.942930 0.000000 244.933506 0.000000 2131.894492 -EDGE_SE2 801 802 0.632634 0.019958 0.054891 11.241159 5.567715 0.000000 249.480486 0.000000 2246.595241 -EDGE_SE2 802 803 0.630127 -0.088612 -0.168062 11.300661 -6.683601 0.000000 246.777787 0.000000 1832.349094 -EDGE_SE2 803 804 0.635077 0.012853 0.053211 11.368430 7.800535 0.000000 247.581479 0.000000 2253.768144 -EDGE_SE2 804 805 0.636519 0.031282 0.073549 11.251547 5.744442 0.000000 246.083247 0.000000 2169.183365 -EDGE_SE2 805 806 0.606343 0.012170 0.038909 11.203672 4.912129 0.000000 271.793935 0.000000 2316.247649 -EDGE_SE2 806 807 0.189729 0.005119 0.292220 201.113736 699.449492 0.000000 2585.968127 0.000000 1497.155415 -EDGE_SE2 807 808 0.003371 0.007327 0.958098 50103.576561 -272947.437303 0.000000 1487266.786417 0.000000 652.035379 -EDGE_SE2 808 809 0.587431 0.097914 0.147917 11.191667 -4.670314 0.000000 281.877711 0.000000 1897.225851 -EDGE_SE2 809 810 0.627956 -0.080646 -0.136964 11.131446 -2.201546 0.000000 249.460063 0.000000 1933.955965 -EDGE_SE2 810 811 0.612576 0.015533 -0.000815 11.285797 -6.674619 0.000000 266.143624 0.000000 2495.929976 -EDGE_SE2 811 812 0.006046 0.000091 0.739463 1201081.516913 1357305.749329 0.000000 1533875.311129 0.000000 826.246472 -EDGE_SE2 812 813 0.018109 0.013231 0.985482 23964.289238 64716.524103 0.000000 174861.749709 0.000000 634.173515 -EDGE_SE2 813 814 0.002006 0.002998 1.002590 3587.558087 165756.923868 0.000000 7682316.480714 0.000000 623.384389 -EDGE_SE2 814 815 0.020425 0.007022 0.928829 67887.614244 99709.570495 0.000000 146482.982663 0.000000 671.974131 -EDGE_SE2 815 816 0.008370 0.002024 0.688367 256339.335199 529144.216869 0.000000 1092335.622944 0.000000 877.013540 -EDGE_SE2 816 817 0.011362 -0.004578 -0.661788 50470.023799 -176289.897436 0.000000 615920.695413 0.000000 905.292186 -EDGE_SE2 817 818 0.201346 -0.091729 -0.483386 17.453139 -113.332496 0.000000 2036.370991 0.000000 1136.139494 -EDGE_SE2 818 819 0.600994 -0.000021 0.016657 11.185144 4.434946 0.000000 276.785971 0.000000 2418.750645 -EDGE_SE2 819 820 0.604107 -0.012016 -0.038590 11.203011 -4.913481 0.000000 273.813863 0.000000 2317.670726 -EDGE_SE2 820 821 0.557707 0.002574 0.018330 11.169482 4.256080 0.000000 321.439373 0.000000 2410.809711 -EDGE_SE2 821 822 0.002019 0.000160 0.773476 9984938.759006 11985389.451377 0.000000 14386651.191718 0.000000 794.857685 -EDGE_SE2 822 823 0.012515 0.017002 0.933412 12.904840 -634.367588 0.000000 224360.656810 0.000000 668.792184 -EDGE_SE2 823 824 0.631778 0.009322 -0.078655 13.193602 -22.229505 0.000000 248.399474 0.000000 2148.695562 -EDGE_SE2 824 825 0.640608 -0.019581 -0.029178 11.111552 0.320195 0.000000 243.449875 0.000000 2360.255512 -EDGE_SE2 825 826 0.635727 0.086901 0.163404 11.286990 6.382404 0.000000 242.719295 0.000000 1847.051069 -EDGE_SE2 826 827 0.633058 -0.024805 -0.048406 11.131451 -2.200226 0.000000 249.121319 0.000000 2274.474191 -EDGE_SE2 827 828 0.640515 0.016631 0.035640 11.132900 2.250508 0.000000 243.562272 0.000000 2330.893209 -EDGE_SE2 828 829 0.604311 -0.016371 -0.038614 11.146008 -3.026498 0.000000 273.593060 0.000000 2317.563615 -EDGE_SE2 829 830 0.611353 -0.005562 -0.007256 11.111980 0.472139 0.000000 267.533817 0.000000 2464.111086 -EDGE_SE2 830 831 0.582292 -0.023325 -0.045648 11.120035 -1.590099 0.000000 294.448496 0.000000 2286.488316 -EDGE_SE2 831 832 0.567969 0.005415 -0.002512 11.154466 -3.599292 0.000000 309.920273 0.000000 2487.487168 -EDGE_SE2 832 833 0.256920 0.000340 0.139017 39.444586 204.467263 0.000000 1486.640047 0.000000 1926.990602 -EDGE_SE2 833 834 0.004768 0.001527 1.059143 1850884.389821 1989766.154557 0.000000 2139092.913935 0.000000 589.612746 -EDGE_SE2 834 835 0.011746 0.005469 0.781010 68236.467078 189689.298378 0.000000 527410.776663 0.000000 788.147120 -EDGE_SE2 835 836 0.015546 0.007972 0.947065 68058.939717 132897.198385 0.000000 259558.927707 0.000000 659.445803 -EDGE_SE2 836 837 0.017648 0.011594 1.040763 44125.577264 89148.384604 0.000000 180165.948528 0.000000 600.281370 -EDGE_SE2 837 838 0.013822 0.005479 0.865867 99632.035075 187458.017071 0.000000 352753.354065 0.000000 718.089607 -EDGE_SE2 838 839 0.001331 -0.000521 -0.216716 1187297.905481 7527754.381697 0.000000 47728231.706064 0.000000 1688.735256 -EDGE_SE2 839 840 0.014201 -0.005576 -0.560094 14691.670684 -78049.547320 0.000000 414963.402725 0.000000 1027.160891 -EDGE_SE2 840 841 0.018508 -0.004844 -0.614349 33618.056740 -89734.585298 0.000000 239613.242398 0.000000 959.279471 -EDGE_SE2 841 842 0.244308 -0.051175 -0.229250 11.937138 -36.275589 0.000000 1604.181092 0.000000 1654.472583 -EDGE_SE2 842 843 0.571578 -0.004308 -0.001490 11.121898 1.783710 0.000000 306.061619 0.000000 2492.566618 -EDGE_SE2 843 844 0.632633 0.008356 -0.006975 11.208334 -4.816449 0.000000 249.718690 0.000000 2465.486516 -EDGE_SE2 844 845 0.634632 -0.005065 -0.013668 11.118782 -1.348758 0.000000 248.265069 0.000000 2433.036002 -EDGE_SE2 845 846 0.637586 0.012768 0.002757 11.181094 -4.052879 0.000000 245.824022 0.000000 2486.271799 -EDGE_SE2 846 847 0.633522 -0.013243 -0.025675 11.116535 -1.135991 0.000000 249.044882 0.000000 2376.405061 -EDGE_SE2 847 848 0.158686 0.003991 0.484189 788.091041 1572.022678 0.000000 3191.702019 0.000000 1134.910441 -EDGE_SE2 848 849 0.002564 0.003351 0.928473 680.469413 61313.177701 0.000000 5616294.276467 0.000000 672.222250 -EDGE_SE2 849 850 0.350597 0.124336 0.356364 11.283371 11.069863 0.000000 722.488046 0.000000 1358.900004 -EDGE_SE2 850 851 0.642798 -0.027661 -0.090713 11.635228 -10.977882 0.000000 241.048137 0.000000 2101.449848 -EDGE_SE2 851 852 0.640836 -0.004293 0.015855 11.229301 5.239393 0.000000 243.375111 0.000000 2422.571276 -EDGE_SE2 852 853 0.610781 0.003140 -0.026553 11.369134 -8.138170 0.000000 267.793422 0.000000 2372.341770 -EDGE_SE2 853 854 0.603861 -0.006364 0.003213 11.160858 3.617435 0.000000 274.156807 0.000000 2484.012095 -EDGE_SE2 854 855 0.610860 0.010420 -0.000693 11.192009 -4.557204 0.000000 267.830171 0.000000 2496.538599 -EDGE_SE2 855 856 0.637641 -0.018640 -0.037084 11.125605 -1.844014 0.000000 245.725607 0.000000 2324.406817 -EDGE_SE2 856 857 0.637311 0.003614 -0.006131 11.143851 -2.774089 0.000000 246.164229 0.000000 2469.624632 -EDGE_SE2 857 858 0.636231 -0.022329 -0.050745 11.168921 -3.690286 0.000000 246.680327 0.000000 2264.359323 -EDGE_SE2 858 859 0.507785 0.004334 -0.003406 11.164814 -4.497374 0.000000 387.747642 0.000000 2483.056613 -EDGE_SE2 859 860 0.002497 0.001745 0.863794 679539.881456 2619251.092498 0.000000 10095943.152469 0.000000 719.687882 -EDGE_SE2 860 861 0.070325 0.071481 0.928711 191.497025 1326.435969 0.000000 9764.823807 0.000000 672.056358 -EDGE_SE2 861 862 0.652508 -0.024736 -0.055891 11.183497 -4.020862 0.000000 234.461196 0.000000 2242.341901 -EDGE_SE2 862 863 0.638309 -0.005836 -0.029014 11.203612 -4.654534 0.000000 245.322644 0.000000 2361.007908 -EDGE_SE2 863 864 0.606532 -0.017964 0.001104 11.356731 7.994878 0.000000 271.342913 0.000000 2494.489128 -EDGE_SE2 864 865 0.604147 0.028266 0.061183 11.165720 3.784052 0.000000 273.324162 0.000000 2220.033054 -EDGE_SE2 865 866 0.579332 -0.011435 0.001982 11.246334 6.225212 0.000000 297.699601 0.000000 2490.119385 -EDGE_SE2 866 867 0.156548 0.001719 0.185575 133.883281 696.034953 0.000000 3957.157327 0.000000 1778.614047 -EDGE_SE2 867 868 0.017881 0.010690 1.037717 52749.955417 96798.086518 0.000000 177676.543047 0.000000 602.077325 -EDGE_SE2 868 869 0.012235 0.005926 0.991656 143324.570378 238746.643887 0.000000 397740.398080 0.000000 630.247818 -EDGE_SE2 869 870 0.002231 -0.000124 1.013097 15395648.131442 8450866.436031 0.000000 4638802.180641 0.000000 616.894084 -EDGE_SE2 870 871 0.001088 0.001679 0.986895 2024.695825 -224276.785530 0.000000 24980373.832027 0.000000 633.271838 -EDGE_SE2 871 872 0.008015 -0.000084 0.694560 653702.249121 768245.705891 0.000000 902886.231030 0.000000 870.614924 -EDGE_SE2 872 873 0.008522 -0.003526 -0.564717 34612.893765 -198701.425503 0.000000 1141058.001437 0.000000 1021.100307 -EDGE_SE2 873 874 0.015858 -0.003778 -0.551904 36802.135033 -111762.498404 0.000000 339519.358447 0.000000 1038.030952 -EDGE_SE2 874 875 0.013521 -0.001487 -0.578534 110429.389804 -217905.190004 0.000000 430036.577843 0.000000 1003.303038 -EDGE_SE2 875 876 0.574406 -0.080596 -0.153354 11.166806 -3.991544 0.000000 297.175644 0.000000 1879.380673 -EDGE_SE2 876 877 0.574770 -0.024813 -0.080909 11.525988 -10.980326 0.000000 301.721640 0.000000 2139.743633 -EDGE_SE2 877 878 0.217632 0.014817 0.389316 219.643265 626.455159 0.000000 1893.056212 0.000000 1295.203276 -EDGE_SE2 878 879 0.647020 0.057742 0.054681 11.377140 -7.747124 0.000000 236.717936 0.000000 2247.489979 -EDGE_SE2 879 880 0.455025 -0.018050 -0.108582 13.346224 -32.372663 0.000000 479.986413 0.000000 2034.250181 -EDGE_SE2 880 881 -0.043409 -0.000133 0.008718 12.813581 300.540520 0.000000 53066.151297 0.000000 2456.973472 -EDGE_SE2 881 882 0.647486 -0.064933 -0.241227 15.572920 -31.371721 0.000000 231.690918 0.000000 1622.697550 -EDGE_SE2 882 883 0.642883 -0.072854 -0.185824 12.322147 -16.564386 0.000000 237.676574 0.000000 1777.868098 -EDGE_SE2 883 884 0.383517 -0.007350 -0.063028 12.396618 -29.287057 0.000000 678.343333 0.000000 2212.333526 -EDGE_SE2 884 885 0.004929 0.003007 0.590777 5527.682570 128525.333864 0.000000 2994400.210829 0.000000 987.919146 -EDGE_SE2 885 886 0.007741 0.006994 1.034072 79878.863906 258839.772996 0.000000 838873.176713 0.000000 604.237070 -EDGE_SE2 886 887 0.010004 0.001000 1.027322 633501.134354 474724.505488 0.000000 355759.975090 0.000000 608.267401 -EDGE_SE2 887 888 0.005011 0.003332 0.999568 444415.889744 1014710.716533 0.000000 2316903.025310 0.000000 625.269895 -EDGE_SE2 888 889 0.005294 0.002083 0.911927 808634.909964 1357982.238784 0.000000 2280571.939853 0.000000 683.907547 -EDGE_SE2 889 890 0.355203 0.095914 0.279682 11.296105 11.600417 0.000000 738.538555 0.000000 1526.637360 -EDGE_SE2 890 891 0.616728 -0.012401 -0.066427 11.650788 -11.642307 0.000000 262.267312 0.000000 2198.253354 -EDGE_SE2 891 892 0.637263 -0.017865 -0.031981 11.114783 -0.928852 0.000000 246.045454 0.000000 2347.451378 -EDGE_SE2 892 893 0.642710 0.035710 0.241471 18.981950 41.834853 0.000000 233.470505 0.000000 1622.059760 -EDGE_SE2 893 894 0.638528 0.083732 0.226070 13.210427 21.873674 0.000000 239.022296 0.000000 1663.065968 -EDGE_SE2 894 895 0.615722 0.105457 0.303953 15.507767 32.534377 0.000000 251.859007 0.000000 1470.334450 -EDGE_SE2 895 896 0.020155 0.000491 0.481484 47934.902135 97432.832108 0.000000 198099.713240 0.000000 1139.058627 -EDGE_SE2 896 897 0.528670 0.158235 0.274351 11.197167 -5.224460 0.000000 328.288174 0.000000 1539.436858 -EDGE_SE2 897 898 0.618615 0.002949 0.017900 11.154262 3.285463 0.000000 261.262932 0.000000 2412.846978 -EDGE_SE2 898 899 0.499807 -0.008373 -0.065092 12.019606 -18.779145 0.000000 399.287489 0.000000 2203.767446 -EDGE_SE2 899 900 0.683722 0.007464 -0.000025 11.135386 -2.218523 0.000000 213.865098 0.000000 2499.875005 -EDGE_SE2 900 901 0.579956 -0.019520 -0.061673 11.335628 -8.008145 0.000000 296.748861 0.000000 2217.984277 -EDGE_SE2 901 902 0.644996 0.015819 0.027799 11.113573 0.751022 0.000000 240.226171 0.000000 2366.593280 -EDGE_SE2 902 903 0.494510 0.003386 -0.042100 12.063405 -19.440085 0.000000 407.959954 0.000000 2302.084267 -EDGE_SE2 903 904 0.005815 0.000708 0.715026 912712.732392 1351574.977076 0.000000 2001492.072759 0.000000 849.960204 -EDGE_SE2 904 905 0.554057 0.035513 0.058326 11.121228 -1.780350 0.000000 324.412517 0.000000 2232.035396 -EDGE_SE2 905 906 0.630376 0.024475 0.068969 11.329543 7.239573 0.000000 251.055279 0.000000 2187.810926 -EDGE_SE2 906 907 0.631021 0.007133 -0.005018 11.175042 -3.916492 0.000000 251.041590 0.000000 2475.097597 -EDGE_SE2 907 908 0.637636 0.001891 0.024788 11.222935 5.123325 0.000000 245.840507 0.000000 2380.520611 -EDGE_SE2 908 909 0.628314 0.001550 -0.017825 11.210816 -4.913046 0.000000 253.205497 0.000000 2413.202580 -EDGE_SE2 909 910 0.147710 -0.043678 -0.369688 39.436166 -343.900479 0.000000 4186.479576 0.000000 1332.590482 -EDGE_SE2 910 911 0.616085 0.011620 0.061065 11.560202 10.634138 0.000000 262.919471 0.000000 2220.526856 -EDGE_SE2 911 912 0.605554 0.006436 0.010902 11.111131 0.071773 0.000000 272.675019 0.000000 2446.368619 -EDGE_SE2 912 913 0.580860 0.003431 0.006628 11.111259 0.205638 0.000000 296.374652 0.000000 2467.186590 -EDGE_SE2 913 914 0.630196 -0.006309 -0.024572 11.162137 -3.503890 0.000000 251.720029 0.000000 2381.524439 -EDGE_SE2 914 915 0.556525 -0.006690 -0.037260 11.309630 -7.863953 0.000000 322.627351 0.000000 2323.618084 -EDGE_SE2 915 916 0.178865 0.104304 0.546671 11.926058 43.487541 0.000000 2331.711773 0.000000 1045.066974 -EDGE_SE2 916 917 0.624756 -0.045966 -0.254265 18.993145 -43.113795 0.000000 246.938496 0.000000 1589.137227 -EDGE_SE2 917 918 0.518933 -0.035276 -0.074995 11.129291 -2.552939 0.000000 369.618783 0.000000 2163.351655 -EDGE_SE2 918 919 0.629099 -0.005797 -0.042335 11.375987 -7.994276 0.000000 252.388154 0.000000 2301.046350 -EDGE_SE2 919 920 0.629899 -0.010656 -0.016202 11.111234 0.171926 0.000000 251.961442 0.000000 2420.917100 -EDGE_SE2 920 921 0.634457 -0.008549 -0.051374 11.451756 -8.983787 0.000000 248.039729 0.000000 2261.650760 -EDGE_SE2 921 922 0.635258 -0.006313 -0.024873 11.163905 -3.534345 0.000000 247.721599 0.000000 2380.125761 -EDGE_SE2 922 923 0.608430 -0.014168 -0.060475 11.469047 -9.619410 0.000000 269.629762 0.000000 2222.998344 -EDGE_SE2 923 924 0.530888 -0.000682 0.019383 11.257903 7.101435 0.000000 354.661710 0.000000 2405.831657 -EDGE_SE2 924 925 0.009664 0.004698 0.510887 2959.553169 50448.307002 0.000000 863189.572650 0.000000 1095.156133 -EDGE_SE2 925 926 0.598427 0.009230 0.018979 11.114501 0.953220 0.000000 279.170216 0.000000 2407.739741 -EDGE_SE2 926 927 0.627907 -0.001812 -0.020475 11.186132 -4.264817 0.000000 253.558182 0.000000 2400.685500 -EDGE_SE2 927 928 0.640773 -0.000139 -0.006135 11.119254 -1.375744 0.000000 243.544155 0.000000 2469.604995 -EDGE_SE2 928 929 0.636560 -0.000176 0.001919 11.112246 0.517244 0.000000 246.784978 0.000000 2490.432549 -EDGE_SE2 929 930 0.640386 -0.014253 -0.078843 11.855260 -13.135681 0.000000 242.981611 0.000000 2147.946760 -EDGE_SE2 930 931 0.635431 -0.020570 -0.076099 11.562880 -10.322123 0.000000 246.953500 0.000000 2158.915046 -EDGE_SE2 931 932 0.639769 -0.010067 -0.055067 11.471607 -9.160668 0.000000 243.895776 0.000000 2245.845776 -EDGE_SE2 932 933 0.631644 -0.015304 -0.059766 11.413371 -8.500896 0.000000 250.193846 0.000000 2225.973780 -EDGE_SE2 933 934 0.504140 0.003029 -0.005219 11.159305 -4.292306 0.000000 393.395672 0.000000 2474.107872 -EDGE_SE2 934 935 0.005622 0.001555 0.696880 504282.663987 1107982.483573 0.000000 2434463.693063 0.000000 868.235915 -EDGE_SE2 935 936 0.001863 0.001286 0.945362 2184651.299423 6152115.682451 0.000000 17324844.542602 0.000000 660.600886 -EDGE_SE2 936 937 0.551720 0.024485 0.062876 11.219817 5.867053 0.000000 327.765518 0.000000 2212.966335 -EDGE_SE2 937 938 0.601673 -0.055314 -0.173492 12.866388 -21.406117 0.000000 272.165004 0.000000 1815.430979 -EDGE_SE2 938 939 0.633059 0.008315 0.027580 11.160855 3.443094 0.000000 249.431085 0.000000 2367.602135 -EDGE_SE2 939 940 0.636273 -0.027491 -0.082722 11.479060 -9.300191 0.000000 246.180789 0.000000 2132.583702 -EDGE_SE2 940 941 0.635944 -0.015109 -0.050698 11.282417 -6.356188 0.000000 246.953620 0.000000 2264.561906 -EDGE_SE2 941 942 0.551791 -0.000493 0.007207 11.131937 2.570602 0.000000 328.415327 0.000000 2464.350846 -EDGE_SE2 942 943 0.644097 -0.001397 0.091523 13.123577 21.416835 0.000000 239.030853 0.000000 2098.332107 -EDGE_SE2 943 944 0.639009 -0.005732 -0.053678 11.578073 -10.437551 0.000000 244.411524 0.000000 2251.770804 -EDGE_SE2 944 945 0.635846 -0.012134 -0.052894 11.381001 -7.978646 0.000000 246.980906 0.000000 2255.125454 -EDGE_SE2 945 946 0.179699 -0.002044 0.066572 29.818272 239.512661 0.000000 3077.654110 0.000000 2197.655692 -EDGE_SE2 946 947 0.406915 0.138311 0.331627 11.119495 2.108463 0.000000 541.380410 0.000000 1409.856218 -EDGE_SE2 947 948 0.579846 -0.001426 -0.021963 11.220001 -5.582502 0.000000 297.312605 0.000000 2393.699690 -EDGE_SE2 948 949 0.604761 -0.049058 -0.159219 12.704160 -20.309811 0.000000 270.041237 0.000000 1860.411550 -EDGE_SE2 949 950 0.636468 -0.025408 -0.243519 20.734999 -46.608998 0.000000 236.840951 0.000000 1616.721292 -EDGE_SE2 950 951 0.637412 -0.014316 -0.075046 11.760145 -12.330103 0.000000 245.353687 0.000000 2163.146402 -EDGE_SE2 951 952 0.637647 0.028692 0.246962 20.543292 46.058094 0.000000 236.016458 0.000000 1607.805725 -EDGE_SE2 952 953 0.635715 0.017895 0.077527 11.686555 11.642681 0.000000 246.671765 0.000000 2153.196604 -EDGE_SE2 953 954 0.632201 -0.012369 -0.013007 11.121379 1.566520 0.000000 250.095671 0.000000 2436.212212 -EDGE_SE2 954 955 0.636040 -0.066402 -0.167279 12.043873 -14.725801 0.000000 243.592003 0.000000 1834.808164 -EDGE_SE2 955 956 0.633169 -0.005303 -0.005934 11.112531 0.581642 0.000000 249.417984 0.000000 2470.592019 -EDGE_SE2 956 957 0.635780 -0.029022 -0.090789 11.591896 -10.635874 0.000000 246.396892 0.000000 2101.157024 -EDGE_SE2 957 958 0.610078 -0.010922 -0.028564 11.140389 -2.745454 0.000000 268.560860 0.000000 2363.074257 -EDGE_SE2 958 959 0.604449 -0.016600 -0.068060 11.543478 -10.642379 0.000000 273.065197 0.000000 2191.536497 -EDGE_SE2 959 960 0.605875 0.054020 0.088370 11.111191 -0.143784 0.000000 270.268197 0.000000 2110.507423 -EDGE_SE2 960 961 0.602054 -0.007561 -0.020089 11.126126 -1.993676 0.000000 275.827437 0.000000 2402.502675 -EDGE_SE2 961 962 0.630743 0.063738 0.073005 11.293528 -6.582439 0.000000 248.635890 0.000000 2171.383420 -EDGE_SE2 962 963 0.629692 0.069822 0.195655 12.835701 20.187165 0.000000 247.411650 0.000000 1748.752064 -EDGE_SE2 963 964 0.639791 -0.008273 -0.057447 11.572861 -10.365475 0.000000 243.797913 0.000000 2235.747685 -EDGE_SE2 964 965 0.633282 -0.006211 -0.001478 11.127639 1.984141 0.000000 249.307749 0.000000 2492.626351 -EDGE_SE2 965 966 0.413197 -0.005006 -0.057147 12.275386 -25.836778 0.000000 584.463061 0.000000 2237.016798 -EDGE_SE2 966 967 0.007510 0.002997 0.859092 325478.163261 626027.365559 0.000000 1204158.381440 0.000000 723.332943 -EDGE_SE2 967 968 0.006374 0.000527 0.902649 1307259.212480 1219284.216016 0.000000 1137250.475138 0.000000 690.593544 -EDGE_SE2 968 969 0.000997 0.000085 0.061289 54733.539945 -2337658.966155 0.000000 99861248.240381 0.000000 2219.589608 -EDGE_SE2 969 970 0.000000 0.000000 -0.000113 11.111116 -0.043944 0.000000 399.999995 0.000000 2499.435096 -EDGE_SE2 970 971 0.361971 0.001286 0.001712 11.113656 -1.383525 0.000000 763.211229 0.000000 2491.461932 -EDGE_SE2 971 972 0.616031 0.005441 0.007131 11.111841 -0.429263 0.000000 263.487396 0.000000 2464.722790 -EDGE_SE2 972 973 0.597393 -0.119850 -0.282715 12.960368 -21.775201 0.000000 267.516438 0.000000 1519.426379 -EDGE_SE2 973 974 0.636084 -0.002771 0.033166 11.443293 8.848623 0.000000 246.819403 0.000000 2342.069601 -EDGE_SE2 974 975 0.636019 0.005980 0.005775 11.114215 -0.856039 0.000000 247.181135 0.000000 2471.373217 -EDGE_SE2 975 976 0.637784 0.010421 0.013983 11.112413 -0.552665 0.000000 245.773095 0.000000 2431.524562 -EDGE_SE2 976 977 0.637920 -0.011711 -0.026304 11.125929 -1.864213 0.000000 245.637352 0.000000 2373.493056 -EDGE_SE2 977 978 0.636711 0.018679 0.044825 11.167625 3.646515 0.000000 246.400847 0.000000 2290.091830 -EDGE_SE2 978 979 0.635616 -0.004880 -0.014420 11.121860 -1.593981 0.000000 247.494628 0.000000 2429.430070 -EDGE_SE2 979 980 0.640402 0.020748 0.043464 11.139636 2.574932 0.000000 243.549790 0.000000 2296.069703 -EDGE_SE2 980 981 0.634599 -0.001102 0.002602 11.115574 1.028911 0.000000 248.309152 0.000000 2487.040602 -EDGE_SE2 981 982 0.582350 0.019314 0.039136 11.121253 1.695467 0.000000 294.536681 0.000000 2315.235788 -EDGE_SE2 982 983 0.606004 -0.005123 -0.005706 11.113084 0.717739 0.000000 272.279670 0.000000 2471.712344 -EDGE_SE2 983 984 0.606209 0.019467 0.054452 11.241333 5.825396 0.000000 271.705807 0.000000 2248.466280 -EDGE_SE2 984 985 0.610191 0.001384 0.017236 11.168792 3.853233 0.000000 268.517439 0.000000 2415.997974 -EDGE_SE2 985 986 0.638807 0.015817 0.032823 11.126330 1.886242 0.000000 244.888150 0.000000 2343.625460 -EDGE_SE2 986 987 0.637113 -0.009662 -0.040633 11.263641 -5.987507 0.000000 246.149046 0.000000 2308.579426 -EDGE_SE2 987 988 0.633843 -0.002158 0.009656 11.151672 3.105391 0.000000 248.863041 0.000000 2452.410392 -EDGE_SE2 988 989 0.637783 0.006714 0.004244 11.120373 -1.474376 0.000000 245.804243 0.000000 2478.914326 -EDGE_SE2 989 990 0.633086 -0.006222 -0.014570 11.116473 -1.130568 0.000000 249.472636 0.000000 2428.711760 -EDGE_SE2 990 991 0.638433 0.009419 0.020655 11.119271 1.382293 0.000000 245.278829 0.000000 2399.838818 -EDGE_SE2 991 992 0.608534 -0.016202 -0.037230 11.140243 -2.745311 0.000000 269.821049 0.000000 2323.752499 -EDGE_SE2 992 993 0.639975 0.012788 0.027838 11.125497 1.830546 0.000000 244.047684 0.000000 2366.413689 -EDGE_SE2 993 994 0.610172 -0.004541 -0.015063 11.126066 -1.962186 0.000000 268.563253 0.000000 2426.353160 -EDGE_SE2 994 995 0.582700 0.014385 0.047816 11.262674 6.550087 0.000000 294.185844 0.000000 2277.036316 -EDGE_SE2 995 996 0.603243 0.013060 0.027432 11.119936 1.525036 0.000000 274.662056 0.000000 2368.284283 -EDGE_SE2 996 997 0.638105 -0.021586 -0.064337 11.329213 -7.143676 0.000000 245.093679 0.000000 2206.895091 -EDGE_SE2 997 998 0.393437 0.003178 0.021791 11.230485 8.704766 0.000000 645.865055 0.000000 2394.505630 -EDGE_SE2 998 999 0.009526 0.010297 0.717647 5765.419520 -53770.217970 0.000000 502458.344534 0.000000 847.368232 -EDGE_SE2 999 1000 0.631982 0.082779 0.148351 11.188179 4.255373 0.000000 246.074510 0.000000 1895.792072 -EDGE_SE2 1000 1001 0.637485 -0.061202 -0.321633 22.788129 -50.803890 0.000000 232.146590 0.000000 1431.259099 -EDGE_SE2 1001 1002 0.630663 0.034533 0.091989 11.444015 8.924103 0.000000 250.338290 0.000000 2096.541587 -EDGE_SE2 1002 1003 0.637376 -0.055899 -0.180665 13.130018 -21.602372 0.000000 242.257264 0.000000 1793.439086 -EDGE_SE2 1003 1004 0.634119 0.020824 0.062770 11.323812 7.101476 0.000000 248.209237 0.000000 2213.407797 -EDGE_SE2 1004 1005 0.602568 0.019724 0.067274 11.426188 9.115028 0.000000 274.805024 0.000000 2194.765624 -EDGE_SE2 1005 1006 0.609599 0.024909 0.082661 11.561301 10.758208 0.000000 268.200258 0.000000 2132.824020 -EDGE_SE2 1006 1007 0.575806 0.024121 0.097394 12.004289 16.068550 0.000000 300.189527 0.000000 2075.940233 -EDGE_SE2 1007 1008 0.296743 0.004461 0.166242 36.620925 167.418708 0.000000 1109.865632 0.000000 1838.072567 -EDGE_SE2 1008 1009 0.169516 0.079531 0.456955 12.060231 51.919506 0.000000 2851.251647 0.000000 1177.735408 -EDGE_SE2 1009 1010 0.635352 0.000090 0.046389 11.616830 10.927244 0.000000 247.219830 0.000000 2283.251111 -EDGE_SE2 1010 1011 0.629760 -0.000818 -0.047667 11.628961 -11.160233 0.000000 251.626406 0.000000 2277.684046 -EDGE_SE2 1011 1012 0.630488 0.001328 0.037830 11.417845 8.582558 0.000000 251.254815 0.000000 2321.066416 -EDGE_SE2 1012 1013 0.638821 0.043192 0.110754 11.546239 10.055629 0.000000 243.492562 0.000000 2026.302298 -EDGE_SE2 1013 1014 0.624005 -0.081360 -0.179557 11.711863 -12.027816 0.000000 251.923335 0.000000 1796.809951 -EDGE_SE2 1014 1015 0.637514 0.038828 0.081332 11.209468 4.796725 0.000000 245.041047 0.000000 2138.069893 -EDGE_SE2 1015 1016 0.781423 -0.009024 0.004462 11.150231 2.443265 0.000000 163.706442 0.000000 2477.838437 -EDGE_SE2 1016 1017 0.603503 0.011706 -0.057436 12.662554 -20.153468 0.000000 272.907609 0.000000 2235.794200 -EDGE_SE2 1017 1018 0.577934 -0.006330 0.007000 11.203996 5.173502 0.000000 299.265589 0.000000 2465.364100 -EDGE_SE2 1018 1019 0.612439 0.031252 0.059177 11.128209 2.087199 0.000000 265.898770 0.000000 2228.450161 -EDGE_SE2 1019 1020 0.633556 -0.016343 -0.014332 11.142340 2.725270 0.000000 248.935660 0.000000 2429.851626 -EDGE_SE2 1020 1021 0.639472 -0.000168 -0.041050 11.499240 -9.510600 0.000000 244.155855 0.000000 2306.730360 -EDGE_SE2 1021 1022 0.070832 0.000271 -0.007651 13.732233 -228.486723 0.000000 19928.608606 0.000000 2462.179597 -EDGE_SE2 1022 1023 0.011155 0.000820 0.086100 139.654009 10135.503712 0.000000 799187.396127 0.000000 2119.338762 -EDGE_SE2 1023 1024 0.645203 -0.002963 0.025849 11.323339 6.969723 0.000000 240.001559 0.000000 2375.598978 -EDGE_SE2 1024 1025 0.648616 0.019366 0.017058 11.148143 -2.895107 0.000000 237.448652 0.000000 2416.843718 -EDGE_SE2 1025 1026 0.180366 -0.000503 0.006661 11.384697 28.945754 0.000000 3073.606781 0.000000 2467.024836 -EDGE_SE2 1026 1027 0.004554 0.001979 0.914665 948112.591095 1716536.548028 0.000000 3107798.392242 0.000000 681.952949 -EDGE_SE2 1027 1028 0.157112 0.092209 0.562259 14.094418 94.590688 0.000000 3010.265300 0.000000 1024.315957 -EDGE_SE2 1028 1029 0.581697 -0.003158 -0.022026 11.189449 -4.719569 0.000000 295.446261 0.000000 2393.404593 -EDGE_SE2 1029 1030 0.602428 -0.021526 -0.077659 11.575380 -11.062953 0.000000 274.727843 0.000000 2152.669156 -EDGE_SE2 1030 1031 0.635032 -0.016817 -0.051435 11.258521 -5.904986 0.000000 247.654252 0.000000 2261.388344 -EDGE_SE2 1031 1032 0.639363 0.000529 0.006615 11.118933 1.351453 0.000000 244.619567 0.000000 2467.250316 -EDGE_SE2 1032 1033 0.641217 -0.014716 -0.042294 11.197935 -4.487022 0.000000 242.999818 0.000000 2301.227383 -EDGE_SE2 1033 1034 0.158807 -0.001999 -0.209870 162.997809 -759.872169 0.000000 3812.666625 0.000000 1707.900606 -EDGE_SE2 1034 1035 0.008238 -0.003621 -0.612225 47839.602807 -238285.590348 0.000000 1187170.073357 0.000000 961.808711 -EDGE_SE2 1035 1036 -0.001359 0.001797 -0.564077 2434530.032209 6484761.501388 0.000000 17273293.058299 0.000000 1021.936120 -EDGE_SE2 1036 1037 0.377894 -0.111886 -0.234247 12.927670 33.853486 0.000000 642.006396 0.000000 1641.103033 -EDGE_SE2 1037 1038 0.646416 0.075596 0.094513 11.219032 -4.926286 0.000000 235.981250 0.000000 2086.883283 -EDGE_SE2 1038 1039 0.688482 -0.025514 -0.028564 11.125452 1.691699 0.000000 210.663547 0.000000 2363.074257 -EDGE_SE2 1039 1040 -0.001379 -0.000331 0.637825 7632266.679736 17921038.113983 0.000000 42079787.422993 0.000000 931.976618 -EDGE_SE2 1040 1041 -0.001323 0.000722 1.029242 43947061.658526 1853405.592364 0.000000 78175.908225 0.000000 607.116902 -EDGE_SE2 1041 1042 0.008412 0.001092 0.999819 812869.525001 684736.258072 0.000000 576819.734984 0.000000 625.113140 -EDGE_SE2 1042 1043 0.010022 0.000228 0.981327 666387.833541 467987.047537 0.000000 328671.866042 0.000000 636.836128 -EDGE_SE2 1043 1044 -0.013611 -0.010605 1.005709 38168.445431 106580.996550 0.000000 297712.956067 0.000000 621.446905 -EDGE_SE2 1044 1045 -0.002205 0.000124 -0.113838 68490.084727 -1183057.512679 0.000000 20438767.774721 0.000000 2015.095849 -EDGE_SE2 1045 1046 -0.013495 0.002112 -0.385242 27867.951272 -118972.940165 0.000000 508129.060237 0.000000 1302.832870 -EDGE_SE2 1046 1047 -0.019733 0.005898 -0.555401 16179.611132 -59582.091055 0.000000 219575.422578 0.000000 1033.368600 -EDGE_SE2 1047 1048 0.344577 -0.119470 -0.312747 11.437653 15.549100 0.000000 751.519224 0.000000 1450.701098 -EDGE_SE2 1048 1049 0.648714 0.010953 0.017287 11.111148 0.091735 0.000000 237.558159 0.000000 2415.755736 -EDGE_SE2 1049 1050 0.646643 0.027376 0.075802 11.366332 7.617483 0.000000 238.466970 0.000000 2160.107247 -EDGE_SE2 1050 1051 0.640459 -0.024485 -0.073431 11.399152 -8.175325 0.000000 243.147148 0.000000 2169.660298 -EDGE_SE2 1051 1052 0.610691 0.014345 0.058571 11.427186 9.005158 0.000000 267.673485 0.000000 2231.002333 -EDGE_SE2 1052 1053 0.402418 -0.011887 -0.047093 11.297991 -10.639018 0.000000 616.787282 0.000000 2280.181912 -EDGE_SE2 1053 1054 0.003611 0.000205 0.781659 3360883.673888 3794299.768961 0.000000 4283634.029810 0.000000 787.573032 -EDGE_SE2 1054 1055 -0.004781 -0.004680 1.092196 217607.587361 662404.612456 0.000000 2016495.376692 0.000000 571.130228 -EDGE_SE2 1055 1056 -0.018363 -0.010043 1.033833 59014.075212 99936.588877 0.000000 169279.247866 0.000000 604.379088 -EDGE_SE2 1056 1057 -0.018154 -0.006925 1.003259 94181.986455 126785.732418 0.000000 170707.431880 0.000000 622.968093 -EDGE_SE2 1057 1058 0.000938 0.000345 0.933655 30225249.079874 45961533.817447 0.000000 69890696.274135 0.000000 668.624102 -EDGE_SE2 1058 1059 0.584768 -0.021106 -0.077373 11.589927 -11.588453 0.000000 291.578229 0.000000 2153.812205 -EDGE_SE2 1059 1060 0.639269 -0.001644 0.007968 11.137057 2.461692 0.000000 244.671355 0.000000 2460.631159 -EDGE_SE2 1060 1061 0.453408 -0.013544 -0.118843 14.861134 -42.033048 0.000000 482.248782 0.000000 1997.108732 -EDGE_SE2 1061 1062 0.018407 -0.004707 -0.560522 25818.783584 -80519.505627 0.000000 251230.619305 0.000000 1026.597536 -EDGE_SE2 1062 1063 -0.002214 0.000350 -0.593849 3568021.016682 -7633088.462779 0.000000 16329573.254319 0.000000 984.114566 -EDGE_SE2 1063 1064 0.301240 -0.082154 -0.262828 11.122946 3.465125 0.000000 1025.683739 0.000000 1567.659011 -EDGE_SE2 1064 1065 0.616850 0.043972 0.123884 11.806351 13.175109 0.000000 260.785525 0.000000 1979.233494 -EDGE_SE2 1065 1066 0.592225 -0.017770 -0.054423 11.274422 -6.684300 0.000000 284.699249 0.000000 2248.589962 -EDGE_SE2 1066 1067 0.012072 0.002120 0.435817 44661.394679 166520.359722 0.000000 621038.078606 0.000000 1212.667746 -EDGE_SE2 1067 1068 0.005144 0.001599 0.677089 463864.684926 1176104.562755 0.000000 2982033.931716 0.000000 888.848590 -EDGE_SE2 1068 1069 0.004939 0.001172 0.172450 14196.268745 -234184.920951 0.000000 3866205.524763 0.000000 1818.659296 -EDGE_SE2 1069 1070 0.007958 -0.000773 -0.535379 282034.324000 -601390.067166 0.000000 1282423.325271 0.000000 1060.495466 -EDGE_SE2 1070 1071 0.555140 -0.067868 -0.125252 11.115112 -1.111199 0.000000 319.703282 0.000000 1974.424001 -EDGE_SE2 1071 1072 0.529254 -0.007703 -0.066861 12.056408 -18.055610 0.000000 355.981837 0.000000 2196.465215 -EDGE_SE2 1072 1073 -0.002029 0.000845 -0.507518 262958.959876 -2318685.557147 0.000000 20446281.115523 0.000000 1100.056511 -EDGE_SE2 1073 1074 0.561059 -0.025535 -0.032676 11.161259 3.916370 0.000000 316.967702 0.000000 2344.292731 -EDGE_SE2 1074 1075 0.609431 -0.004618 0.018200 11.282578 6.650541 0.000000 269.060256 0.000000 2411.425356 -EDGE_SE2 1075 1076 0.065495 0.001481 0.413124 3385.912605 8198.067984 0.000000 19925.858335 0.000000 1251.928321 -EDGE_SE2 1076 1077 0.014156 0.014258 0.821400 271.376246 8025.080869 0.000000 247458.480551 0.000000 753.579966 -EDGE_SE2 1077 1078 0.639455 0.059020 0.049931 11.521069 -9.730785 0.000000 242.081388 0.000000 2267.871749 -EDGE_SE2 1078 1079 0.600624 0.018085 0.067237 11.477555 9.863097 0.000000 276.583452 0.000000 2194.917807 -EDGE_SE2 1079 1080 -0.000999 -0.000009 -0.477168 21861087.771847 -41382475.748765 0.000000 78336011.010556 0.000000 1145.724571 -EDGE_SE2 1080 1081 0.531360 -0.067180 -0.117428 11.134556 2.812802 0.000000 348.582744 0.000000 2002.169815 -EDGE_SE2 1081 1082 0.224996 0.001967 0.288640 161.010437 521.488265 0.000000 1825.328808 0.000000 1505.486248 -EDGE_SE2 1082 1083 -0.010460 -0.011699 1.057628 18726.686416 85143.154920 0.000000 387354.631781 0.000000 590.481488 -EDGE_SE2 1083 1084 0.006499 0.006279 0.991072 59854.087388 264007.010053 0.000000 1164720.918244 0.000000 630.617587 -EDGE_SE2 1084 1085 0.020056 0.011249 0.977075 38176.449616 75898.227624 0.000000 150947.567649 0.000000 639.578299 -EDGE_SE2 1085 1086 0.570700 0.124357 0.228493 11.165939 3.931749 0.000000 293.059740 0.000000 1656.511362 -EDGE_SE2 1086 1087 0.610443 -0.014761 -0.013224 11.141951 2.815599 0.000000 268.167634 0.000000 2435.168807 -EDGE_SE2 1087 1088 0.601056 0.009570 -0.017713 11.411487 -8.927269 0.000000 276.432474 0.000000 2413.733758 -EDGE_SE2 1088 1089 0.071683 -0.000572 -0.098521 170.122570 -1751.356223 0.000000 19300.592801 0.000000 2071.681742 -EDGE_SE2 1089 1090 0.001412 -0.000123 -0.621871 12937756.646628 -21836745.706848 0.000000 36856777.365825 0.000000 950.402110 -EDGE_SE2 1090 1091 0.549397 -0.084430 -0.180259 11.352156 -8.676436 0.000000 323.419744 0.000000 1794.673157 -EDGE_SE2 1091 1092 0.637703 -0.006996 -0.017526 11.121203 -1.539154 0.000000 245.862562 0.000000 2414.621027 -EDGE_SE2 1092 1093 0.588555 -0.015042 -0.067587 11.600931 -11.646012 0.000000 288.008196 0.000000 2193.478870 -EDGE_SE2 1093 1094 0.004150 -0.003600 -0.406987 303638.185583 955915.435697 0.000000 3009539.565685 0.000000 1262.873468 -EDGE_SE2 1094 1095 0.025922 -0.007302 -0.593448 13559.452360 -41040.960104 0.000000 124333.371329 0.000000 984.609944 -EDGE_SE2 1095 1096 0.013621 -0.004396 -0.574854 32930.237834 -122414.831277 0.000000 455229.472232 0.000000 1007.997402 -EDGE_SE2 1096 1097 0.359533 -0.076883 -0.217554 11.145668 -5.017901 0.000000 739.746128 0.000000 1686.411461 -EDGE_SE2 1097 1098 0.655276 -0.022381 -0.058348 11.240870 -5.359644 0.000000 232.489049 0.000000 2231.942602 -EDGE_SE2 1098 1099 0.601562 -0.024926 -0.098508 11.973250 -15.083436 0.000000 275.001216 0.000000 2071.731935 -EDGE_SE2 1099 1100 0.019767 0.000659 0.359334 26235.559183 77565.286776 0.000000 229429.617699 0.000000 1352.968387 -EDGE_SE2 1100 1101 0.259176 0.070164 0.262689 11.115050 -2.328152 0.000000 1387.055621 0.000000 1568.004174 -EDGE_SE2 1101 1102 0.009767 -0.003058 -0.397627 8459.012576 -89410.542255 0.000000 946310.626975 0.000000 1279.845202 -EDGE_SE2 1102 1103 0.624811 -0.021021 -0.035899 11.112369 -0.554908 0.000000 255.864408 0.000000 2329.727795 -EDGE_SE2 1103 1104 0.653192 -0.005160 -0.029757 11.217748 -4.878085 0.000000 234.257657 0.000000 2357.602064 -EDGE_SE2 1104 1105 0.643770 -0.007193 -0.019886 11.128584 -2.005277 0.000000 241.242039 0.000000 2403.459167 -EDGE_SE2 1105 1106 0.496058 -0.003395 -0.027229 11.275339 -8.055101 0.000000 406.199361 0.000000 2369.220411 -EDGE_SE2 1106 1107 0.007526 0.002867 0.253134 18856.773417 -169404.055090 0.000000 1522787.727562 0.000000 1592.007032 -EDGE_SE2 1107 1108 0.625868 0.043204 0.005957 12.073074 -15.257830 0.000000 253.117791 0.000000 2470.479046 -EDGE_SE2 1108 1109 0.646003 -0.046291 -0.106175 11.383719 -7.866802 0.000000 238.127651 0.000000 2043.112734 -EDGE_SE2 1109 1110 0.614215 -0.005255 -0.051510 11.579361 -10.894385 0.000000 264.581538 0.000000 2261.065764 -EDGE_SE2 1110 1111 0.553199 -0.082221 -0.103442 11.711068 13.593477 0.000000 319.104424 0.000000 2053.246012 -EDGE_SE2 1111 1112 0.569389 0.118635 0.343250 16.481962 38.719218 0.000000 290.243375 0.000000 1385.563104 -EDGE_SE2 1112 1113 0.640195 -0.016890 -0.097447 12.284559 -16.483251 0.000000 242.648861 0.000000 2075.739728 -EDGE_SE2 1113 1114 0.617738 0.000372 0.015293 11.165263 3.685922 0.000000 262.000150 0.000000 2425.253974 -EDGE_SE2 1114 1115 0.584111 0.002651 -0.017913 11.253226 -6.328745 0.000000 292.947350 0.000000 2412.785348 -EDGE_SE2 1115 1116 0.648872 0.022176 0.034505 11.111138 0.077459 0.000000 237.232680 0.000000 2336.010662 -EDGE_SE2 1116 1117 0.098251 0.000884 -0.129855 209.328151 -1418.352583 0.000000 10160.208546 0.000000 1958.369267 -EDGE_SE2 1117 1118 0.002069 -0.000845 -0.611494 987546.746516 -4336045.136437 0.000000 19038602.480542 0.000000 962.681493 -EDGE_SE2 1118 1119 -0.002904 0.000748 -0.435035 368477.951626 -1990623.945676 0.000000 10754258.868008 0.000000 1213.989755 -EDGE_SE2 1119 1120 0.606241 -0.045059 -0.065468 11.130843 2.262698 0.000000 270.573321 0.000000 2202.212316 -EDGE_SE2 1120 1121 0.209582 -0.004500 -0.120622 33.301618 -223.063126 0.000000 2253.383418 0.000000 1990.772900 -EDGE_SE2 1121 1122 0.003037 -0.001969 -0.554282 3351.615045 159642.761466 0.000000 7629342.430661 0.000000 1034.857076 -EDGE_SE2 1122 1123 0.587650 -0.008795 0.015249 11.365183 8.406488 0.000000 289.256608 0.000000 2425.464195 -EDGE_SE2 1123 1124 0.579143 0.038116 0.096775 11.386604 8.868243 0.000000 296.584474 0.000000 2078.284141 -EDGE_SE2 1124 1125 0.646108 -0.002624 0.011822 11.168733 3.627591 0.000000 239.485218 0.000000 2441.921916 -EDGE_SE2 1125 1126 0.089655 0.001862 0.336774 1211.106301 3670.039910 0.000000 11235.483551 0.000000 1399.020340 -EDGE_SE2 1126 1127 -0.000544 0.000812 1.050094 84031228.496092 -41585087.528682 0.000000 20579500.003262 0.000000 594.829446 -EDGE_SE2 1127 1128 -0.002103 -0.000314 0.980680 12093669.618717 11006276.376659 0.000000 10016675.597149 0.000000 637.252248 -EDGE_SE2 1128 1129 0.011252 0.005103 1.008728 198520.561371 301067.442842 0.000000 456622.144090 0.000000 619.580502 -EDGE_SE2 1129 1130 0.583423 0.017793 0.012045 11.207158 -5.207167 0.000000 293.417741 0.000000 2440.845899 -EDGE_SE2 1130 1131 0.750481 -0.194337 -0.435123 16.183680 -27.603353 0.000000 161.320023 0.000000 1213.840879 -EDGE_SE2 1131 1132 0.640243 -0.002464 -0.021306 11.182068 -4.064051 0.000000 243.880425 0.000000 2396.780386 -EDGE_SE2 1132 1133 0.584277 -0.021867 -0.040363 11.113568 -0.831424 0.000000 292.516553 0.000000 2309.777849 -EDGE_SE2 1133 1134 0.266576 -0.000300 -0.098860 24.404478 -135.580323 0.000000 1393.907817 0.000000 2070.404863 -EDGE_SE2 1134 1135 0.009102 -0.002652 -0.559816 82791.558254 -291964.753439 0.000000 1029764.152962 0.000000 1027.527058 -EDGE_SE2 1135 1136 0.002258 -0.003305 -0.565768 971762.123150 2263140.796874 0.000000 5270709.266319 0.000000 1019.729968 -EDGE_SE2 1136 1137 0.306973 -0.042413 -0.186154 13.568352 -50.253896 0.000000 1038.871344 0.000000 1776.878994 -EDGE_SE2 1137 1138 0.654763 -0.011031 -0.017798 11.111312 -0.211469 0.000000 233.189098 0.000000 2413.330616 -EDGE_SE2 1138 1139 0.527941 0.000918 -0.077053 13.265063 -27.280422 0.000000 356.625570 0.000000 2155.092221 -EDGE_SE2 1139 1140 -0.002013 0.000175 -0.486547 3709493.048971 -8779833.609002 0.000000 20780669.837336 0.000000 1131.312847 -EDGE_SE2 1140 1141 0.002239 -0.000031 -0.555810 5310315.218372 -8816646.759236 0.000000 14638204.801536 0.000000 1032.825356 -EDGE_SE2 1141 1142 0.372375 -0.136555 -0.370794 11.343901 -12.055726 0.000000 635.453819 0.000000 1330.440997 -EDGE_SE2 1142 1143 0.629856 0.033762 0.085383 11.354434 7.641679 0.000000 251.102091 0.000000 2122.139741 -EDGE_SE2 1143 1144 0.612945 -0.010504 -0.034290 11.186135 -4.373095 0.000000 266.015507 0.000000 2336.981945 -EDGE_SE2 1144 1145 0.611105 -0.003802 -0.029142 11.245917 -5.880472 0.000000 267.628650 0.000000 2360.420642 -EDGE_SE2 1145 1146 0.609486 -0.000759 0.016125 11.188979 4.482263 0.000000 269.120272 0.000000 2421.284019 -EDGE_SE2 1146 1147 0.610275 0.036873 0.068649 11.128787 2.128847 0.000000 267.508800 0.000000 2189.121374 -EDGE_SE2 1147 1148 0.611337 -0.001965 0.012226 11.172242 3.959007 0.000000 267.506715 0.000000 2439.973063 -EDGE_SE2 1148 1149 0.074700 0.001638 0.503169 3846.534249 7344.890200 0.000000 14076.680961 0.000000 1106.431132 -EDGE_SE2 1149 1150 -0.006116 -0.002785 0.988331 626799.870889 997506.113526 0.000000 1587497.215478 0.000000 632.357453 -EDGE_SE2 1150 1151 0.014648 0.006739 0.987862 107400.301813 172549.549763 0.000000 277258.261675 0.000000 632.655875 -EDGE_SE2 1151 1152 0.009432 0.009802 1.036217 28480.057962 120720.157191 0.000000 511914.710091 0.000000 602.964704 -EDGE_SE2 1152 1153 0.010437 0.002034 0.996659 458897.357799 441929.724577 0.000000 425610.882022 0.000000 627.093176 -EDGE_SE2 1153 1154 0.277143 0.156011 0.537087 11.691675 23.815732 0.000000 988.073623 0.000000 1058.139946 -EDGE_SE2 1154 1155 0.634179 -0.005082 -0.053384 11.599710 -10.761566 0.000000 248.138569 0.000000 2253.027920 -EDGE_SE2 1155 1156 0.609122 -0.005838 0.006338 11.176606 4.113232 0.000000 269.430259 0.000000 2468.608751 -EDGE_SE2 1156 1157 0.580367 0.023367 0.106616 12.366206 18.881213 0.000000 295.153466 0.000000 2041.484648 -EDGE_SE2 1157 1158 0.608842 -0.050707 -0.133293 11.757739 -12.869932 0.000000 267.263428 0.000000 1946.505325 -EDGE_SE2 1158 1159 0.600995 -0.037895 -0.099499 11.464087 -9.658719 0.000000 275.409224 0.000000 2067.999033 -EDGE_SE2 1159 1160 0.637109 -0.021604 -0.142588 13.876030 -25.338115 0.000000 243.313239 0.000000 1914.964342 -EDGE_SE2 1160 1161 0.638306 -0.057065 -0.094949 11.118890 -1.344463 0.000000 243.484624 0.000000 2085.221653 -EDGE_SE2 1161 1162 0.639583 0.008532 0.053343 11.484272 9.323139 0.000000 244.042318 0.000000 2253.203316 -EDGE_SE2 1162 1163 0.646089 0.001306 -0.009750 11.142767 -2.689017 0.000000 239.527759 0.000000 2451.953812 -EDGE_SE2 1163 1164 0.642172 0.018842 0.080564 11.717339 11.822667 0.000000 241.677018 0.000000 2141.110196 -EDGE_SE2 1164 1165 0.530061 0.001899 -0.020695 11.314294 -8.367588 0.000000 355.709513 0.000000 2399.650727 -EDGE_SE2 1165 1166 0.004850 0.001220 0.493564 239307.954224 948357.988051 0.000000 3758451.305300 0.000000 1120.707645 -EDGE_SE2 1166 1167 0.000199 0.000971 0.944839 17209450.782475 -38137417.374455 0.000000 84515406.833935 0.000000 660.956228 -EDGE_SE2 1167 1168 0.004314 0.002779 0.911345 419892.583640 1190803.974162 0.000000 3377188.237646 0.000000 684.324107 -EDGE_SE2 1168 1169 0.046124 0.045485 0.829043 72.075115 1203.501414 0.000000 23769.649842 0.000000 747.295174 -EDGE_SE2 1169 1170 0.595970 0.008443 0.008952 11.118461 -1.409691 0.000000 281.483079 0.000000 2455.833943 -EDGE_SE2 1170 1171 0.643652 0.016020 0.034262 11.131351 2.158066 0.000000 241.208530 0.000000 2337.108483 -EDGE_SE2 1171 1172 0.608254 -0.008594 -0.059650 11.647713 -11.779607 0.000000 269.699888 0.000000 2226.461162 -EDGE_SE2 1172 1173 0.635753 0.004699 0.011813 11.115733 1.045017 0.000000 247.395251 0.000000 2441.965358 -EDGE_SE2 1173 1174 0.642740 -0.015092 -0.065577 11.519977 -9.706025 0.000000 241.521390 0.000000 2201.761802 -EDGE_SE2 1174 1175 0.635529 -0.015560 -0.030331 11.119207 -1.383209 0.000000 247.431148 0.000000 2354.975943 -EDGE_SE2 1175 1176 0.639892 -0.002591 -0.040430 11.419513 -8.473235 0.000000 243.910274 0.000000 2309.480375 -EDGE_SE2 1176 1177 0.631909 0.000862 0.005069 11.114395 0.886514 0.000000 250.429122 0.000000 2474.846416 -EDGE_SE2 1177 1178 0.638012 -0.011209 -0.043943 11.274194 -6.181646 0.000000 245.425622 0.000000 2293.963142 -EDGE_SE2 1178 1179 0.631478 0.010553 0.032543 11.171171 3.792944 0.000000 250.644522 0.000000 2344.896698 -EDGE_SE2 1179 1180 0.271033 0.000088 -0.003969 11.136010 -5.798071 0.000000 1361.277228 0.000000 2480.272525 -EDGE_SE2 1180 1181 0.006369 0.000562 0.838184 1136739.900794 1219876.425763 0.000000 1309117.124470 0.000000 739.881291 -EDGE_SE2 1181 1182 0.043362 0.037473 0.721003 13.218472 253.246510 0.000000 30444.337192 0.000000 844.066675 -EDGE_SE2 1182 1183 0.631233 0.006864 0.028504 11.185650 4.227407 0.000000 250.864920 0.000000 2363.349975 -EDGE_SE2 1183 1184 0.641740 0.028391 0.073043 11.303273 6.663120 0.000000 242.151864 0.000000 2171.229631 -EDGE_SE2 1184 1185 0.636568 -0.012623 -0.029018 11.131008 -2.164911 0.000000 246.663116 0.000000 2360.989552 -EDGE_SE2 1185 1186 0.637011 0.023759 0.069721 11.358319 7.617657 0.000000 245.847739 0.000000 2184.736002 -EDGE_SE2 1186 1187 0.658018 -0.006443 -0.033774 11.237521 -5.269863 0.000000 230.804698 0.000000 2339.315499 -EDGE_SE2 1187 1188 0.632380 0.009510 0.021371 11.120695 1.513079 0.000000 249.993325 0.000000 2396.475334 -EDGE_SE2 1188 1189 0.635906 -0.012539 0.021673 11.515314 9.760309 0.000000 246.794097 0.000000 2395.058778 -EDGE_SE2 1189 1190 0.298422 -0.000493 0.007995 11.214542 10.722954 0.000000 1122.783078 0.000000 2460.499340 -EDGE_SE2 1190 1191 -0.001378 -0.000184 0.920177 25978781.998107 25880314.470994 0.000000 25782242.304073 0.000000 678.043383 -EDGE_SE2 1191 1192 0.072942 0.060647 0.797518 130.506556 1145.096181 0.000000 10993.483679 0.000000 773.736998 -EDGE_SE2 1192 1193 0.612651 -0.026766 -0.132178 13.102394 -22.437131 0.000000 263.925490 0.000000 1950.341157 -EDGE_SE2 1193 1194 0.586582 0.016272 0.041820 11.166533 3.933966 0.000000 290.352323 0.000000 2303.321852 -EDGE_SE2 1194 1195 0.636169 0.007659 0.052000 11.487698 9.418662 0.000000 246.677289 0.000000 2258.959939 -EDGE_SE2 1195 1196 0.637063 -0.002513 -0.173199 17.787125 -39.066310 0.000000 239.717067 0.000000 1816.337880 -EDGE_SE2 1196 1197 0.631038 0.007284 0.010757 11.111259 -0.188614 0.000000 251.090831 0.000000 2447.070566 -EDGE_SE2 1197 1198 0.639335 0.035252 0.257911 20.557528 45.933130 0.000000 234.460604 0.000000 1579.938488 -EDGE_SE2 1198 1199 0.625974 -0.039991 -0.215702 16.676523 -36.355580 0.000000 248.600846 0.000000 1691.553532 -EDGE_SE2 1199 1200 0.642662 0.020896 0.068112 11.403588 8.210051 0.000000 241.573905 0.000000 2191.323116 -EDGE_SE2 1200 1201 0.632369 0.008692 0.054836 11.514278 9.806016 0.000000 249.617869 0.000000 2246.829525 -EDGE_SE2 1201 1202 0.636732 0.009332 0.021910 11.123506 1.708394 0.000000 246.587725 0.000000 2393.947989 -EDGE_SE2 1202 1203 0.605583 0.016194 -0.091218 14.730772 -30.544741 0.000000 268.864904 0.000000 2099.505256 -EDGE_SE2 1203 1204 0.604704 -0.076543 -0.148349 11.241024 -5.788533 0.000000 269.030303 0.000000 1895.798676 -EDGE_SE2 1204 1205 0.579744 0.012270 0.028212 11.125345 2.018565 0.000000 297.379849 0.000000 2364.692492 -EDGE_SE2 1205 1206 0.604024 0.022981 0.059672 11.234092 5.681316 0.000000 273.569799 0.000000 2226.368715 -EDGE_SE2 1206 1207 0.628833 -0.063349 -0.087865 11.148708 2.998873 0.000000 250.310531 0.000000 2112.467324 -EDGE_SE2 1207 1208 0.641879 0.018435 0.062871 11.381019 7.898378 0.000000 242.243300 0.000000 2212.987156 -EDGE_SE2 1208 1209 0.636239 0.001912 -0.003661 11.121595 -1.572688 0.000000 247.023047 0.000000 2481.795033 -EDGE_SE2 1209 1210 0.630195 0.024749 0.018688 11.212716 -4.940152 0.000000 251.306983 0.000000 2409.115535 -EDGE_SE2 1210 1211 0.632941 -0.047678 -0.070430 11.116473 1.127530 0.000000 248.202543 0.000000 2181.842838 -EDGE_SE2 1211 1212 0.633914 0.009606 0.052050 11.434554 8.761975 0.000000 248.470147 0.000000 2258.745224 -EDGE_SE2 1212 1213 0.635719 0.026579 0.075094 11.372749 7.851816 0.000000 246.746209 0.000000 2162.953249 -EDGE_SE2 1213 1214 0.748508 -0.025001 -0.220810 16.915079 -30.604005 0.000000 172.484342 0.000000 1677.427863 -EDGE_SE2 1214 1215 0.609440 0.015872 0.060134 11.410886 8.788375 0.000000 268.756514 0.000000 2224.428662 -EDGE_SE2 1215 1216 0.569604 0.086391 0.218432 12.447317 19.645507 0.000000 299.948223 0.000000 1683.981886 -EDGE_SE2 1216 1217 0.604098 -0.020910 -0.018798 11.176666 4.148410 0.000000 273.628656 0.000000 2408.595337 -EDGE_SE2 1217 1218 0.590246 0.008252 -0.050669 12.262477 -17.784784 0.000000 285.827155 0.000000 2264.686919 -EDGE_SE2 1218 1219 0.650992 0.006557 0.030598 11.205817 4.613431 0.000000 235.846718 0.000000 2353.755880 -EDGE_SE2 1219 1220 0.586496 0.026678 0.090738 11.682801 12.616550 0.000000 289.544235 0.000000 2101.353517 -EDGE_SE2 1220 1221 -0.001846 -0.000482 -0.452753 11628909.160981 -13576008.003722 0.000000 15849147.678198 0.000000 1184.558318 -EDGE_SE2 1221 1222 0.012937 -0.005157 -0.597307 24124.307745 -108858.692621 0.000000 491452.172995 0.000000 979.857795 -EDGE_SE2 1222 1223 0.011537 -0.003845 -0.559627 37585.948232 -154900.759550 0.000000 638583.281939 0.000000 1027.776110 -EDGE_SE2 1223 1224 0.574470 -0.089064 -0.182908 11.352146 -8.281702 0.000000 295.661931 0.000000 1786.644187 -EDGE_SE2 1224 1225 0.625700 0.003903 0.013764 11.124947 1.838454 0.000000 255.403355 0.000000 2432.575223 -EDGE_SE2 1225 1226 0.106504 -0.000109 -0.016731 13.283390 -138.281257 0.000000 8813.714781 0.000000 2418.398573 -EDGE_SE2 1226 1227 0.000000 0.000000 -0.000662 11.111282 -0.257444 0.000000 399.999830 0.000000 2496.693284 -EDGE_SE2 19 166 -2.459689 0.241111 0.252800 11.731352 1.696454 0.000000 15.751178 0.000000 1592.856013 -EDGE_SE2 19 172 1.067903 0.915786 0.149470 22.211594 -17.729393 0.000000 39.428022 0.000000 1892.102792 -EDGE_SE2 25 172 -2.539634 0.852630 -0.029860 11.348228 0.783018 0.000000 13.696833 0.000000 2357.130503 -EDGE_SE2 25 178 1.225223 0.672079 -0.102480 24.051651 -18.745770 0.000000 38.266385 0.000000 2056.830811 -EDGE_SE2 32 178 -2.020007 -0.239771 0.715180 15.237484 6.070047 0.000000 20.040376 0.000000 849.807581 -EDGE_SE2 32 183 -0.092559 0.424181 0.041600 515.079071 88.191252 0.000000 26.544031 0.000000 2304.294940 -EDGE_SE2 44 189 -2.265706 -0.570857 0.197720 11.128469 -0.353246 0.000000 18.300012 0.000000 1742.727183 -EDGE_SE2 44 195 1.302289 -0.133004 -0.005280 11.549680 4.530716 0.000000 57.916501 0.000000 2473.807626 -EDGE_SE2 50 195 -2.374379 -0.253682 0.216120 11.188114 0.699234 0.000000 17.460612 0.000000 1690.390904 -EDGE_SE2 50 201 1.291572 0.086248 -0.050700 11.777215 -5.648747 0.000000 59.014090 0.000000 2264.553285 -EDGE_SE2 50 203 2.550695 0.035722 -0.084650 11.152401 -0.417172 0.000000 15.326014 0.000000 2125.008969 -EDGE_SE2 61 203 -2.852494 0.122053 0.077870 11.127858 0.138148 0.000000 12.250754 0.000000 2151.826441 -EDGE_SE2 61 209 0.730780 0.072970 0.008890 12.538871 -15.710166 0.000000 183.975779 0.000000 2456.135792 -EDGE_SE2 67 209 -2.822142 0.138051 -0.004460 11.113900 0.062754 0.000000 12.522981 0.000000 2477.848305 -EDGE_SE2 67 220 0.783704 -1.176736 -1.412900 17.863537 -14.737201 0.000000 43.275118 0.000000 429.399329 -EDGE_SE2 81 220 -0.868391 -0.009825 -0.046880 11.522030 -7.053327 0.000000 132.179889 0.000000 2281.109865 -EDGE_SE2 81 226 2.836453 0.058719 0.049150 11.112174 0.037334 0.000000 12.422974 0.000000 2271.249469 -EDGE_SE2 87 226 -0.459640 0.184679 0.343830 185.785423 196.810927 0.000000 232.864045 0.000000 1384.367338 -EDGE_SE2 87 231 1.060624 0.863823 0.294010 34.429085 -29.740516 0.000000 94.681322 0.000000 4479.050905 -EDGE_SE2 94 231 -2.231597 0.316386 0.180250 23.930088 5.134954 0.000000 37.661225 0.000000 5384.101583 -EDGE_SE2 94 241 2.816677 0.483252 0.041400 22.259441 -0.288013 0.000000 24.450978 0.000000 6915.540303 -EDGE_SE2 101 241 -1.436886 0.314097 -0.098870 23.168499 8.096966 0.000000 91.505166 0.000000 6211.101543 -EDGE_SE2 101 247 2.168416 0.280309 -0.003870 22.564171 -2.567078 0.000000 41.493789 0.000000 7442.285250 -EDGE_SE2 107 247 -1.536762 0.375104 0.051050 26.954959 15.833327 0.000000 75.192472 0.000000 6789.136027 -EDGE_SE2 107 254 0.592787 -1.020976 -0.778000 30.649088 30.837091 0.000000 135.066803 0.000000 2372.453724 -EDGE_SE2 107 256 0.707309 -1.207974 -1.163740 23.417473 -9.695697 0.000000 100.872264 0.000000 1601.957964 -EDGE_SE2 107 258 0.976528 -2.202656 -1.227730 22.289493 -0.904508 0.000000 34.383996 0.000000 1511.249453 -EDGE_SE2 114 254 -0.931502 -0.024068 0.567580 77.556805 91.947180 0.000000 175.007037 0.000000 3052.121623 -EDGE_SE2 114 256 -0.723652 0.045801 0.181840 43.302820 84.297616 0.000000 359.313621 0.000000 5369.624220 -EDGE_SE2 114 258 0.306031 0.086093 0.117850 69.684517 -301.023297 0.000000 1931.422497 0.000000 6001.975262 -EDGE_SE2 121 266 0.737190 -0.017200 -0.118240 25.326151 -32.604829 0.000000 364.715589 0.000000 5997.789466 -EDGE_SE2 126 266 -1.480397 0.199141 0.062860 24.793867 12.913265 0.000000 87.064919 0.000000 6639.098888 -EDGE_SE2 126 272 2.350796 0.151313 0.025060 22.243466 -0.541415 0.000000 36.020406 0.000000 7137.772305 -EDGE_SE2 132 272 -1.375990 0.193564 -0.069560 22.622461 5.692420 0.000000 103.182984 0.000000 6556.181353 -EDGE_SE2 132 278 2.220707 0.319882 -0.000500 22.580601 -2.479176 0.000000 39.372548 0.000000 7492.505621 -EDGE_SE2 138 278 -1.378019 0.626427 0.016990 34.209796 25.223836 0.000000 75.297341 0.000000 7251.500784 -EDGE_SE2 138 284 2.391863 0.324204 -0.075755 22.750660 -2.473459 0.000000 33.799752 0.000000 6480.884307 -EDGE_SE2 151 284 -2.650853 -0.975732 1.454550 24.484817 1.146301 0.000000 22.802974 0.000000 1244.851357 -EDGE_SE2 151 291 -1.498500 0.364159 0.021790 26.317431 15.382872 0.000000 80.005044 0.000000 7183.530951 -EDGE_SE2 151 297 2.217429 -0.068852 -0.223460 22.895631 -3.456384 0.000000 39.962710 0.000000 5010.507463 -EDGE_SE2 19 305 -1.044094 0.006772 0.289080 17.951036 22.463977 0.000000 84.888274 0.000000 1504.458692 -EDGE_SE2 19 315 1.796874 2.231292 1.917670 11.894814 0.476141 0.000000 11.400392 0.000000 293.675468 -EDGE_SE2 19 337 1.676359 1.138847 2.369960 23.812750 -2.606854 0.000000 11.646135 0.000000 220.135719 -EDGE_SE2 19 342 2.451034 1.014379 -0.106190 11.820054 -1.302086 0.000000 13.502599 0.000000 2043.057325 -EDGE_SE2 19 1016 0.039741 0.374380 -2.852145 602.592071 -246.735273 0.000000 114.036309 0.000000 168.474698 -EDGE_SE2 25 315 -1.587705 2.017012 1.738340 12.043194 -1.708941 0.000000 14.244392 0.000000 333.399749 -EDGE_SE2 25 337 -1.901147 0.963583 2.190630 13.452340 -4.476777 0.000000 19.671370 0.000000 245.576678 -EDGE_SE2 25 342 -1.161097 0.702932 -0.285520 13.939899 10.682557 0.000000 51.452426 0.000000 1512.802847 -EDGE_SE2 25 367 0.878430 -0.159970 0.910020 100.995987 46.868336 0.000000 35.549493 0.000000 685.273881 -EDGE_SE2 25 374 2.840920 -0.557124 -0.601610 11.240233 -0.298749 0.000000 11.802326 0.000000 974.600130 -EDGE_SE2 25 1008 0.839778 0.868849 2.614245 65.217790 -13.300389 0.000000 14.380585 0.000000 191.383617 -EDGE_SE2 25 1010 0.208016 0.817490 -3.078070 128.288560 -37.883138 0.000000 23.358623 0.000000 150.324807 -EDGE_SE2 32 367 -1.650170 -1.061836 1.727680 23.555966 5.481395 0.000000 13.525417 0.000000 336.010748 -EDGE_SE2 32 374 -0.018221 0.098268 0.216050 10000.716890 -326.857444 0.000000 21.805806 0.000000 1690.585519 -EDGE_SE2 32 382 0.235335 2.451229 2.821510 16.224704 1.166981 0.000000 11.377430 0.000000 171.186695 -EDGE_SE2 32 387 0.993162 2.566664 0.274705 12.448992 -1.004261 0.000000 11.864945 0.000000 1538.582680 -EDGE_SE2 32 1003 0.362306 -0.196467 3.066965 108.121054 215.919400 0.000000 491.692649 0.000000 151.146840 -EDGE_SE2 32 1008 -2.427177 -0.386394 -2.851280 11.206045 0.712595 0.000000 16.460019 0.000000 168.550413 -EDGE_SE2 32 1010 -2.821790 -0.882420 -2.260410 11.209350 0.150567 0.000000 11.341881 0.000000 235.177395 -EDGE_SE2 44 437 0.696650 -2.771050 -0.205460 12.032017 0.446847 0.000000 11.327933 0.000000 1720.419675 -EDGE_SE2 44 454 -1.152851 -1.893499 1.516715 13.178717 3.850191 0.000000 18.280745 0.000000 394.704273 -EDGE_SE2 44 467 1.218289 1.232598 1.113140 13.331453 6.657638 0.000000 31.073862 0.000000 559.865191 -EDGE_SE2 44 494 2.154809 -0.649767 -0.306530 11.112721 -0.117867 0.000000 19.740180 0.000000 1464.539994 -EDGE_SE2 44 988 2.712903 -0.996652 3.047100 11.166944 0.211947 0.000000 11.915683 0.000000 152.634299 -EDGE_SE2 44 994 -0.892482 -0.642947 3.093485 38.865579 -34.860363 0.000000 54.896672 0.000000 149.194749 -EDGE_SE2 50 467 -2.756210 1.060141 1.334540 11.461044 -0.046083 0.000000 11.117180 0.000000 458.709115 -EDGE_SE2 50 494 -1.429190 -0.570621 -0.085130 17.367850 -12.471280 0.000000 35.969560 0.000000 2123.129418 -EDGE_SE2 50 500 2.268340 -0.490379 0.263640 12.680013 3.039151 0.000000 16.998309 0.000000 1565.644941 -EDGE_SE2 50 982 2.823043 -0.459737 -3.096925 11.157700 0.222838 0.000000 12.176961 0.000000 148.944310 -EDGE_SE2 50 988 -0.808543 -0.786485 -3.014685 35.481589 -32.415484 0.000000 54.227361 0.000000 155.108998 -EDGE_SE2 61 508 -1.449200 2.112314 1.477670 12.801726 -2.029955 0.000000 13.548517 0.000000 407.242490 -EDGE_SE2 61 522 -1.053664 -0.045704 0.099000 11.354883 4.375854 0.000000 89.660307 0.000000 2069.877405 -EDGE_SE2 61 528 2.466833 0.101003 -0.088240 11.198948 -0.676270 0.000000 16.317799 0.000000 2111.011691 -EDGE_SE2 61 976 1.165811 -0.076649 -3.054440 12.551016 9.349655 0.000000 71.820715 0.000000 152.082153 -EDGE_SE2 61 982 -2.503567 -0.322810 -2.934405 11.139618 0.360303 0.000000 15.665017 0.000000 161.503454 -EDGE_SE2 67 528 -1.085869 0.142906 -0.101590 11.172968 2.113193 0.000000 83.303853 0.000000 2060.155675 -EDGE_SE2 67 532 1.038480 -0.184540 -0.187270 11.121355 -0.898278 0.000000 89.877742 0.000000 1773.540133 -EDGE_SE2 67 556 0.875960 2.072499 -1.608750 12.194562 2.861649 0.000000 18.669404 0.000000 367.345805 -EDGE_SE2 67 564 0.934530 -0.521388 -1.425090 59.070878 -36.808963 0.000000 39.361868 0.000000 425.093327 -EDGE_SE2 67 567 1.078640 -1.127738 0.068640 28.794636 14.729412 0.000000 23.379907 0.000000 2189.158247 -EDGE_SE2 67 570 2.695842 -0.809094 0.129960 11.364179 0.564364 0.000000 12.369693 0.000000 1958.005326 -EDGE_SE2 67 965 0.918287 -0.079133 1.692110 113.199731 -21.468798 0.000000 15.625907 0.000000 344.948615 -EDGE_SE2 67 976 -2.389147 -0.017363 -3.067790 11.139434 0.425047 0.000000 17.489953 0.000000 151.085560 -EDGE_SE2 81 528 -2.540636 -1.571989 1.264430 11.150293 0.045554 0.000000 11.164073 0.000000 487.553422 -EDGE_SE2 81 532 -1.788049 0.441390 1.178750 29.071227 2.714944 0.000000 11.521516 0.000000 526.653783 -EDGE_SE2 81 564 -1.479377 0.271115 -0.059070 11.602730 4.003639 0.000000 43.715888 0.000000 2228.900474 -EDGE_SE2 81 567 -0.856391 0.288913 1.434660 118.478978 -20.563824 0.000000 15.049634 0.000000 421.758037 -EDGE_SE2 81 570 -0.839522 1.937122 1.495980 13.560960 -4.662733 0.000000 19.985567 0.000000 401.289509 -EDGE_SE2 81 575 -0.874481 2.499055 -2.998155 14.149112 0.594261 0.000000 11.227354 0.000000 156.394216 -EDGE_SE2 81 588 2.585410 -0.398590 0.032990 11.230814 0.636285 0.000000 14.493300 0.000000 2342.867749 -EDGE_SE2 81 959 1.715988 -0.147087 2.880085 11.804020 -3.896236 0.000000 33.019679 0.000000 166.057104 -EDGE_SE2 81 965 -1.915695 0.345142 3.058130 11.248005 1.439837 0.000000 26.255228 0.000000 151.805707 -EDGE_SE2 87 588 -0.567044 -0.325829 0.327670 19.376959 -42.100418 0.000000 225.541056 0.000000 1418.272639 -EDGE_SE2 87 597 1.033167 2.497995 1.747590 11.858143 1.168127 0.000000 12.937699 0.000000 331.158689 -EDGE_SE2 87 604 1.169979 2.733059 -1.279630 11.194520 0.099906 0.000000 11.230777 0.000000 481.073330 -EDGE_SE2 87 953 1.577352 0.448961 -2.823050 11.155436 1.074029 0.000000 37.135844 0.000000 171.048808 -EDGE_SE2 87 959 -1.472035 -0.337676 -3.108420 12.306884 -6.140766 0.000000 42.646361 0.000000 148.112032 -EDGE_SE2 94 597 -2.073375 1.943112 1.633830 11.708877 -0.635533 0.000000 11.786797 0.000000 360.383193 -EDGE_SE2 94 604 -1.910764 2.161126 -1.393390 11.355872 -0.402293 0.000000 11.772326 0.000000 436.428461 -EDGE_SE2 94 616 -0.567925 0.985809 1.728270 19.547380 -22.065015 0.000000 68.822033 0.000000 335.865437 -EDGE_SE2 94 632 -0.258230 -0.057031 -0.961010 1222.393668 -501.336231 0.000000 218.608540 0.000000 650.100338 -EDGE_SE2 94 637 2.180403 0.487495 0.048180 11.371804 -1.502621 0.000000 19.772123 0.000000 2275.455104 -EDGE_SE2 94 947 1.819562 0.355845 -2.812985 11.439117 2.406258 0.000000 28.763471 0.000000 171.952995 -EDGE_SE2 94 953 -1.765303 -0.154451 -2.936810 11.396122 2.414195 0.000000 31.560619 0.000000 161.306214 -EDGE_SE2 101 637 -2.066317 0.407256 -0.092090 11.230841 1.163903 0.000000 22.425529 0.000000 2096.153815 -EDGE_SE2 101 642 0.702540 0.058990 -0.158060 22.012272 -44.195550 0.000000 190.288971 0.000000 1864.137257 -EDGE_SE2 101 645 1.268565 0.117224 1.302975 55.348130 16.649223 0.000000 17.377280 0.000000 471.369465 -EDGE_SE2 101 657 0.970735 1.275733 -1.200065 31.327289 12.383774 0.000000 18.697009 0.000000 516.498549 -EDGE_SE2 101 663 2.721944 0.234701 0.154665 11.121870 0.156475 0.000000 13.386764 0.000000 1875.114422 -EDGE_SE2 101 665 2.743644 0.343689 1.484400 12.992900 0.403085 0.000000 11.197453 0.000000 405.039117 -EDGE_SE2 101 939 1.736598 0.040097 -3.139770 11.121070 -0.468281 0.000000 33.131371 0.000000 145.877255 -EDGE_SE2 101 944 -1.229619 0.290529 3.034680 11.913468 6.379852 0.000000 61.839764 0.000000 153.575457 -EDGE_SE2 101 947 -2.442020 0.327348 -2.953255 11.646767 1.607820 0.000000 15.937136 0.000000 159.966957 -EDGE_SE2 101 1138 -0.044980 -2.128090 1.406340 11.334711 -1.549412 0.000000 21.847605 0.000000 431.743719 -EDGE_SE2 101 1188 1.043367 -1.716987 3.078145 20.296004 6.412523 0.000000 15.588077 0.000000 150.319256 -EDGE_SE2 107 642 -2.988279 0.073654 -0.103140 11.111606 -0.006296 0.000000 11.191151 0.000000 2054.370375 -EDGE_SE2 107 645 -2.426303 0.162871 1.357895 16.787995 0.834037 0.000000 11.233646 0.000000 449.666954 -EDGE_SE2 107 663 -0.981564 0.359950 0.209585 33.867694 36.211234 0.000000 68.731953 0.000000 1708.704657 -EDGE_SE2 107 665 -0.965880 0.469964 1.539320 74.030616 -28.201297 0.000000 23.751280 0.000000 387.708339 -EDGE_SE2 107 680 -1.101202 1.597727 -1.082720 11.315853 -1.766538 0.000000 26.353031 0.000000 576.339299 -EDGE_SE2 107 683 -0.373238 0.237253 -0.978670 91.473664 -183.671027 0.000000 430.896755 0.000000 638.547590 -EDGE_SE2 107 704 -0.174419 -0.744617 -2.364960 56.805507 -72.228521 0.000000 125.281768 0.000000 220.790405 -EDGE_SE2 107 714 -0.001585 -1.518738 0.040750 43.298168 -1.345998 0.000000 11.167398 0.000000 2308.060399 -EDGE_SE2 107 730 1.719547 -1.998733 -2.849795 13.843772 1.215931 0.000000 11.652154 0.000000 168.680442 -EDGE_SE2 107 929 0.482300 -2.592003 1.960350 11.247596 0.654505 0.000000 14.249748 0.000000 285.268546 -EDGE_SE2 107 935 -0.294578 0.372182 2.191385 12.146819 -21.145445 0.000000 442.825321 0.000000 245.460450 -EDGE_SE2 107 939 -1.954743 0.111552 -3.084850 11.304032 1.688716 0.000000 25.893149 0.000000 149.826206 -EDGE_SE2 107 1147 -0.324254 -1.481983 0.140150 39.532534 -10.553850 0.000000 15.030118 0.000000 1923.162692 -EDGE_SE2 107 1150 0.364219 -1.279939 1.435915 18.388124 -16.646666 0.000000 49.191505 0.000000 421.323458 -EDGE_SE2 114 663 -2.629135 -1.250276 1.555165 11.663410 0.273309 0.000000 11.246360 0.000000 382.914669 -EDGE_SE2 114 665 -2.732868 -1.210420 2.884900 11.143210 -0.040214 0.000000 11.161493 0.000000 165.645759 -EDGE_SE2 114 683 -2.373686 -0.684712 0.366910 11.150086 0.451690 0.000000 16.345825 0.000000 1338.012489 -EDGE_SE2 114 704 -1.372213 -0.710183 -1.019380 41.720375 -2.264013 0.000000 11.278569 0.000000 613.061308 -EDGE_SE2 114 714 -0.579045 -0.714588 1.386330 35.416827 44.860054 0.000000 93.907456 0.000000 439.014644 -EDGE_SE2 114 730 0.273187 0.855886 -1.504215 26.282898 38.481991 0.000000 108.717525 0.000000 398.654506 -EDGE_SE2 114 929 0.575175 -0.482602 -2.977255 107.010292 82.154133 0.000000 81.490248 0.000000 158.042201 -EDGE_SE2 114 935 -2.487642 -0.577907 -2.746220 11.227888 0.692283 0.000000 15.215150 0.000000 178.136720 -EDGE_SE2 114 1147 -0.686929 -1.020901 1.485730 24.072771 23.324553 0.000000 53.083724 0.000000 404.605797 -EDGE_SE2 114 1150 -0.730123 -0.304695 2.781495 80.988104 -74.193686 0.000000 89.888157 0.000000 174.828760 -EDGE_SE2 114 1158 1.965070 -0.055808 0.083960 11.296704 1.644922 0.000000 25.690196 0.000000 2127.715200 -EDGE_SE2 114 1175 2.570804 -0.255481 2.913020 11.175699 -0.495876 0.000000 14.918247 0.000000 163.273564 -EDGE_SE2 121 754 -0.176774 -0.090674 -0.104160 764.277342 -1154.350823 0.000000 1780.343146 0.000000 2050.576559 -EDGE_SE2 121 759 2.795658 -0.372882 -0.047990 11.121537 0.122936 0.000000 12.560684 0.000000 2276.280256 -EDGE_SE2 121 923 -0.359940 -0.158411 2.816780 299.677751 -316.410365 0.000000 358.051835 0.000000 171.611249 -EDGE_SE2 121 1158 -1.765259 0.015433 0.094390 11.333442 2.148138 0.000000 31.866199 0.000000 2087.352405 -EDGE_SE2 121 1164 1.939145 -0.416391 -0.071910 11.388217 1.971985 0.000000 25.144479 0.000000 2175.822000 -EDGE_SE2 121 1175 -1.157476 -0.177912 2.923450 19.220682 -20.867733 0.000000 64.808438 0.000000 162.406633 -EDGE_SE2 126 754 -2.366181 -0.037747 0.076940 11.136170 0.410365 0.000000 17.831325 0.000000 2155.544499 -EDGE_SE2 126 759 0.608470 0.220030 0.133110 21.370901 -47.238010 0.000000 228.603826 0.000000 1947.134106 -EDGE_SE2 126 762 1.256487 0.430359 1.248090 39.878585 21.991658 0.000000 27.922912 0.000000 494.666638 -EDGE_SE2 126 923 -2.534151 -0.137367 2.997880 11.281716 -0.850942 0.000000 15.355440 0.000000 156.415757 -EDGE_SE2 126 1164 -0.226200 0.022965 0.109190 94.980319 392.781106 0.000000 1850.606209 0.000000 2032.020653 -EDGE_SE2 132 762 -2.439040 0.574751 1.153470 15.760986 0.874518 0.000000 11.275585 0.000000 539.091342 -EDGE_SE2 132 778 -1.013501 1.621706 1.561210 15.810999 -7.362314 0.000000 22.644082 0.000000 381.109374 -EDGE_SE2 132 792 -0.882133 1.768911 -1.797920 16.975203 -7.109142 0.000000 19.729650 0.000000 319.351840 -EDGE_SE2 132 800 1.214689 0.372313 0.005900 15.310892 -13.996183 0.000000 57.754774 0.000000 2470.759036 -EDGE_SE2 132 907 2.246094 -0.166035 -3.063890 11.307039 1.283425 0.000000 19.518199 0.000000 151.375685 -EDGE_SE2 132 911 0.287430 -0.091199 2.898905 15.641604 70.080974 0.000000 1095.174761 0.000000 164.457860 -EDGE_SE2 132 1084 -0.744736 -2.350749 -1.849821 11.115227 0.148119 0.000000 16.441489 0.000000 307.825761 -EDGE_SE2 138 800 -2.384799 0.661256 0.023390 11.548813 1.446295 0.000000 15.890090 0.000000 2387.028865 -EDGE_SE2 138 806 1.314158 0.976356 0.186155 16.125723 -10.306715 0.000000 32.294877 0.000000 1776.876918 -EDGE_SE2 138 821 1.339602 0.836157 -1.728640 27.615689 14.355051 0.000000 23.596586 0.000000 335.774357 -EDGE_SE2 138 823 1.391787 0.830084 -0.201680 23.357993 -13.427129 0.000000 25.832230 0.000000 1731.260189 -EDGE_SE2 138 903 0.257877 0.385262 2.468460 462.130945 37.644489 0.000000 14.253118 0.000000 207.810089 -EDGE_SE2 138 904 0.211460 0.361905 2.918540 518.619452 -160.188745 0.000000 61.672712 0.000000 162.813885 -EDGE_SE2 138 907 -1.344137 0.141028 -3.046400 12.828819 8.485440 0.000000 53.029004 0.000000 152.687113 -EDGE_SE2 151 828 -2.783661 1.064375 1.529770 11.244135 -0.044701 0.000000 11.126133 0.000000 390.641099 -EDGE_SE2 151 847 -2.892269 0.357584 -1.720550 11.773808 0.017727 0.000000 11.111585 0.000000 337.774286 -EDGE_SE2 151 855 0.421564 -0.036975 -0.072980 11.226261 7.937694 0.000000 558.283669 0.000000 2171.484605 -EDGE_SE2 151 895 -0.308314 0.072387 -2.940720 183.554391 374.539119 0.000000 824.593387 0.000000 160.986275 -EDGE_SE2 151 897 -0.653297 -0.340780 -2.145820 53.093226 74.186112 0.000000 142.204535 0.000000 252.622641 -EDGE_SE2 151 1029 2.658346 -1.322835 -1.612160 11.303644 -0.086066 0.000000 11.149585 0.000000 366.387340 -EDGE_SE2 157 297 -1.431635 -0.047784 -0.192100 12.991577 -8.198575 0.000000 46.855781 0.000000 1759.197636 -EDGE_SE2 157 305 2.323872 -0.569035 0.026110 11.551318 1.614103 0.000000 17.029536 0.000000 2374.390623 -EDGE_SE2 157 1029 -0.951615 -1.287326 -1.580800 20.706819 13.256279 0.000000 29.424395 0.000000 375.345582 -EDGE_SE2 157 1217 -0.999593 -2.295582 1.588120 11.945029 1.827929 0.000000 15.117890 0.000000 373.225399 -EDGE_SE2 163 297 -0.152489 -1.643361 1.294160 11.969142 -4.607638 0.000000 35.854192 0.000000 474.998898 -EDGE_SE2 163 305 0.683999 2.054722 1.512370 11.800954 2.562970 0.000000 20.633308 0.000000 396.070787 -EDGE_SE2 163 1029 1.123157 -1.269718 -0.094540 22.165118 11.817460 0.000000 23.744753 0.000000 2086.780326 -EDGE_SE2 163 1217 2.123761 -1.402656 3.074380 12.166102 1.857716 0.000000 14.382333 0.000000 150.597217 -EDGE_SE2 166 297 -2.448623 -0.097923 -0.181930 11.379485 -1.189522 0.000000 16.383459 0.000000 1789.602165 -EDGE_SE2 166 305 1.311990 -0.580954 0.036280 18.290583 14.744491 0.000000 41.391889 0.000000 2328.015008 -EDGE_SE2 166 1016 2.453320 -0.496114 -3.104945 11.376696 1.103524 0.000000 15.696336 0.000000 148.362881 -EDGE_SE2 166 1029 -1.956023 -1.332520 -1.570630 15.716267 3.136091 0.000000 13.246776 0.000000 378.321363 -EDGE_SE2 172 305 -2.223813 -0.584373 0.139610 11.218103 -0.907468 0.000000 18.807912 0.000000 1924.985691 -EDGE_SE2 172 315 0.916741 1.192284 1.768200 29.887371 16.398688 0.000000 25.433291 0.000000 326.245913 -EDGE_SE2 172 328 0.389353 2.701274 -1.418880 11.306838 0.643966 0.000000 13.229838 0.000000 427.278816 -EDGE_SE2 172 337 0.634889 0.129966 2.220490 195.557030 -88.592401 0.000000 53.663494 0.000000 241.043874 -EDGE_SE2 172 342 1.382392 -0.108475 -0.255660 12.384043 -7.102031 0.000000 50.735268 0.000000 1585.608223 -EDGE_SE2 172 1010 2.747473 0.046909 -3.048210 11.123505 0.162102 0.000000 13.231218 0.000000 152.550608 -EDGE_SE2 172 1016 -1.097321 -0.382262 -3.001615 13.480215 -11.980076 0.000000 71.691915 0.000000 156.123881 -EDGE_SE2 178 342 -2.376956 -0.213431 -0.183040 11.578392 -1.671546 0.000000 17.090523 0.000000 1786.245513 -EDGE_SE2 178 367 -0.259854 -0.863160 1.012500 18.840526 -28.383170 0.000000 115.336891 0.000000 617.260137 -EDGE_SE2 178 374 1.732968 -1.057467 -0.499130 11.142356 0.640290 0.000000 24.232266 0.000000 1112.401122 -EDGE_SE2 178 1003 1.826983 -1.529516 2.351785 11.166956 -0.600028 0.000000 17.558198 0.000000 222.529516 -EDGE_SE2 178 1008 -0.403552 0.156306 2.716725 12.710306 -28.871300 0.000000 532.343545 0.000000 180.975206 -EDGE_SE2 178 1010 -1.026746 0.040588 -2.975590 14.592518 16.700937 0.000000 91.228510 0.000000 158.174631 -EDGE_SE2 183 367 -1.618063 -1.419954 1.686080 18.191945 4.896376 0.000000 14.496941 0.000000 346.499109 -EDGE_SE2 183 374 0.060720 -0.328723 0.174450 894.828841 7.251305 0.000000 11.170611 0.000000 1812.470490 -EDGE_SE2 183 382 0.411911 2.011657 2.779910 23.397894 1.979206 0.000000 11.429930 0.000000 174.975438 -EDGE_SE2 183 387 1.173883 2.095476 0.233105 14.481439 -3.100632 0.000000 13.963629 0.000000 1644.144968 -EDGE_SE2 183 1003 0.428660 -0.639029 3.025365 102.305125 77.922908 0.000000 77.694205 0.000000 154.287027 -EDGE_SE2 183 1008 -2.366308 -0.712782 -2.892880 11.121230 -0.230529 0.000000 16.363269 0.000000 164.967341 -EDGE_SE2 189 400 -1.948699 2.200613 -1.116370 11.144116 -0.119102 0.000000 11.540899 0.000000 558.157565 -EDGE_SE2 189 414 -0.254807 -1.783532 -1.508360 11.922188 3.913799 0.000000 29.996898 0.000000 397.338159 -EDGE_SE2 189 454 0.831360 -1.515476 1.318995 21.578630 -11.156350 0.000000 23.001623 0.000000 464.879365 -EDGE_SE2 189 994 1.332308 -0.340434 2.895765 11.111898 0.181350 0.000000 52.882967 0.000000 164.723074 -EDGE_SE2 195 437 -0.591701 -2.641206 -0.200180 13.648824 -0.051289 0.000000 11.112148 0.000000 1735.590395 -EDGE_SE2 195 467 -0.091210 1.365140 1.118420 21.523802 -18.224556 0.000000 43.008191 0.000000 557.077827 -EDGE_SE2 195 484 0.655807 2.573505 -2.009240 11.219291 -0.565783 0.000000 14.070164 0.000000 276.074540 -EDGE_SE2 195 494 0.855236 -0.512254 -0.301250 16.102937 20.540136 0.000000 95.628725 0.000000 1476.449250 -EDGE_SE2 195 988 1.415155 -0.856188 3.052380 16.022409 10.041613 0.000000 31.642140 0.000000 152.236812 -EDGE_SE2 195 994 -2.192048 -0.521524 3.098765 11.750475 -2.253964 0.000000 19.057069 0.000000 148.810613 -EDGE_SE2 201 494 -2.683977 -0.793909 -0.034430 11.276756 -0.496479 0.000000 12.599187 0.000000 2336.349413 -EDGE_SE2 201 500 1.004735 -0.526385 0.314340 45.186413 33.298498 0.000000 43.650514 0.000000 1447.186686 -EDGE_SE2 201 982 1.557172 -0.467671 -3.046225 14.919173 9.340266 0.000000 34.020554 0.000000 152.700298 -EDGE_SE2 201 988 -2.053188 -0.978041 -2.963985 11.683303 -2.092334 0.000000 18.762142 0.000000 159.102109 -EDGE_SE2 203 500 -0.236863 -0.548090 0.348290 153.663942 -134.464803 0.000000 137.946777 0.000000 1375.223806 -EDGE_SE2 203 508 1.553867 1.875066 1.399800 12.536000 2.482853 0.000000 15.437455 0.000000 434.100125 -EDGE_SE2 203 522 1.780329 -0.307182 0.021130 11.822071 3.657498 0.000000 29.926926 0.000000 2397.606666 -EDGE_SE2 203 982 0.313263 -0.470658 -3.012275 253.866414 119.649456 0.000000 70.084044 0.000000 155.295388 -EDGE_SE2 209 508 -2.161765 2.058644 1.468780 11.180293 -0.053576 0.000000 11.152602 0.000000 410.180706 -EDGE_SE2 209 522 -1.785428 -0.102806 0.090110 11.132515 0.656456 0.000000 31.244975 0.000000 2103.775347 -EDGE_SE2 209 528 1.736233 0.012599 -0.097130 11.350617 -2.286079 0.000000 32.931671 0.000000 2076.939411 -EDGE_SE2 209 976 0.433683 -0.153481 -3.063330 87.280240 171.295909 0.000000 396.336584 0.000000 151.417412 -EDGE_SE2 220 528 -1.597199 -1.638814 1.311310 13.034804 3.414597 0.000000 17.172093 0.000000 467.976033 -EDGE_SE2 220 532 -0.939792 0.407621 1.225630 94.950637 -5.379789 0.000000 11.456320 0.000000 504.700895 -EDGE_SE2 220 564 -0.623480 0.251998 -0.012190 38.845567 71.101745 0.000000 193.391889 0.000000 2440.146629 -EDGE_SE2 220 567 -0.002013 0.298972 1.481540 21.284937 -105.664807 0.000000 1108.540123 0.000000 405.973278 -EDGE_SE2 220 570 -0.062402 1.946160 1.542860 11.165977 -0.913492 0.000000 26.320380 0.000000 386.629607 -EDGE_SE2 220 575 -0.123656 2.505837 -2.951275 15.792523 -0.664538 0.000000 11.205444 0.000000 160.127317 -EDGE_SE2 220 959 2.587973 -0.016000 2.926965 11.274656 -0.773198 0.000000 14.766597 0.000000 162.116000 -EDGE_SE2 220 965 -1.062787 0.305497 3.105010 15.212918 16.523642 0.000000 77.674651 0.000000 148.358205 -EDGE_SE2 226 231 1.660221 0.126920 -0.049820 11.505996 -3.114430 0.000000 35.674411 0.000000 2268.351350 -EDGE_SE2 226 588 -0.273207 -0.444423 -0.016160 274.806053 -156.291042 0.000000 103.744249 0.000000 2421.117227 -EDGE_SE2 226 597 2.185242 1.674700 1.403760 12.078039 1.038206 0.000000 12.225850 0.000000 432.671013 -EDGE_SE2 226 953 2.006857 -0.437847 3.116305 11.557944 2.329386 0.000000 23.254435 0.000000 147.545120 -EDGE_SE2 226 959 -1.129223 -0.150509 2.830935 23.235807 -25.544568 0.000000 64.928953 0.000000 170.345385 -EDGE_SE2 923 1175 0.749610 0.273000 0.106670 19.536607 -34.047517 0.000000 148.697481 0.000000 2041.285424 -EDGE_SE2 929 1175 -2.005899 0.102421 -0.392910 12.648561 -4.320259 0.000000 23.251108 0.000000 1288.528106 -EDGE_SE2 939 1188 0.696432 1.755817 -0.065270 26.430265 -4.946684 0.000000 12.708437 0.000000 2203.031036 -EDGE_SE2 1029 1217 1.008685 -0.037889 -3.114265 11.476877 5.630362 0.000000 97.781127 0.000000 147.691473 -EDGE_SE2 1044 1217 2.098504 -0.838430 0.479890 15.976239 4.188481 0.000000 14.717054 0.000000 1141.513725 -EDGE_SE2 1050 1210 -0.300235 -2.565672 1.536560 11.137270 0.317303 0.000000 14.960021 0.000000 388.552520 -EDGE_SE2 1050 1217 0.231465 1.668290 1.559140 11.493589 3.014437 0.000000 34.868922 0.000000 381.726156 -EDGE_SE2 1056 1217 2.128743 -0.472375 -0.931160 15.353750 -4.908186 0.000000 16.789250 0.000000 670.352902 -EDGE_SE2 1061 1210 1.269232 -1.441319 -2.909610 23.568115 6.644624 0.000000 14.655385 0.000000 163.558506 -EDGE_SE2 1075 1210 -1.662329 0.436026 -1.599840 32.701727 -4.997844 0.000000 12.268023 0.000000 369.868006 -EDGE_SE2 1084 1210 2.070374 1.896269 1.836205 12.355692 0.641825 0.000000 11.442098 0.000000 310.788312 -EDGE_SE2 1088 1210 0.609503 -1.485166 0.536770 38.204841 -4.021013 0.000000 11.707874 0.000000 1058.576530 -EDGE_SE2 1093 1210 -0.233452 -0.529583 1.494360 42.853783 90.090527 0.000000 266.801722 0.000000 401.810926 -EDGE_SE2 1098 1210 -0.378280 -0.172442 -2.937025 38.903093 -122.470929 0.000000 550.803768 0.000000 161.288572 -EDGE_SE2 1138 1188 0.583736 -1.006358 1.671805 21.764394 -23.563240 0.000000 63.228969 0.000000 350.211491 -EDGE_SE2 1147 1188 -2.232188 0.113982 2.992915 11.195782 -0.864255 0.000000 19.932721 0.000000 156.804966 -EDGE_SE2 1150 1188 -0.789302 2.834301 1.697150 11.120354 -0.063189 0.000000 11.543094 0.000000 343.660651 -EDGE_SE2 1158 1175 0.586855 -0.249767 2.829060 13.000858 20.975969 0.000000 243.941877 0.000000 170.512281 diff --git a/SLAM/GraphBasedSLAM/graph_based_slam.py b/SLAM/GraphBasedSLAM/graph_based_slam.py deleted file mode 100644 index edd20a959c..0000000000 --- a/SLAM/GraphBasedSLAM/graph_based_slam.py +++ /dev/null @@ -1,322 +0,0 @@ -""" - -Graph based SLAM example - -author: Atsushi Sakai (@Atsushi_twi) - -Ref - -[A Tutorial on Graph-Based SLAM] -(http://www2.informatik.uni-freiburg.de/~stachnis/pdf/grisetti10titsmag.pdf) - -""" -import sys -import pathlib -sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) - -import copy -import itertools -import math - -import matplotlib.pyplot as plt -import numpy as np -from scipy.spatial.transform import Rotation as Rot -from utils.angle import angle_mod - -# Simulation parameter -Q_sim = np.diag([0.2, np.deg2rad(1.0)]) ** 2 -R_sim = np.diag([0.1, np.deg2rad(10.0)]) ** 2 - -DT = 2.0 # time tick [s] -SIM_TIME = 100.0 # simulation time [s] -MAX_RANGE = 30.0 # maximum observation range -STATE_SIZE = 3 # State size [x,y,yaw] - -# Covariance parameter of Graph Based SLAM -C_SIGMA1 = 0.1 -C_SIGMA2 = 0.1 -C_SIGMA3 = np.deg2rad(1.0) - -MAX_ITR = 20 # Maximum iteration - -show_graph_d_time = 20.0 # [s] -show_animation = True - - -class Edge: - - def __init__(self): - self.e = np.zeros((3, 1)) - self.omega = np.zeros((3, 3)) # information matrix - self.d1 = 0.0 - self.d2 = 0.0 - self.yaw1 = 0.0 - self.yaw2 = 0.0 - self.angle1 = 0.0 - self.angle2 = 0.0 - self.id1 = 0 - self.id2 = 0 - - -def cal_observation_sigma(): - sigma = np.zeros((3, 3)) - sigma[0, 0] = C_SIGMA1 ** 2 - sigma[1, 1] = C_SIGMA2 ** 2 - sigma[2, 2] = C_SIGMA3 ** 2 - - return sigma - - -def calc_3d_rotational_matrix(angle): - return Rot.from_euler('z', angle).as_matrix() - - -def calc_edge(x1, y1, yaw1, x2, y2, yaw2, d1, - angle1, d2, angle2, t1, t2): - edge = Edge() - - tangle1 = pi_2_pi(yaw1 + angle1) - tangle2 = pi_2_pi(yaw2 + angle2) - tmp1 = d1 * math.cos(tangle1) - tmp2 = d2 * math.cos(tangle2) - tmp3 = d1 * math.sin(tangle1) - tmp4 = d2 * math.sin(tangle2) - - edge.e[0, 0] = x2 - x1 - tmp1 + tmp2 - edge.e[1, 0] = y2 - y1 - tmp3 + tmp4 - edge.e[2, 0] = 0 - - Rt1 = calc_3d_rotational_matrix(tangle1) - Rt2 = calc_3d_rotational_matrix(tangle2) - - sig1 = cal_observation_sigma() - sig2 = cal_observation_sigma() - - edge.omega = np.linalg.inv(Rt1 @ sig1 @ Rt1.T + Rt2 @ sig2 @ Rt2.T) - - edge.d1, edge.d2 = d1, d2 - edge.yaw1, edge.yaw2 = yaw1, yaw2 - edge.angle1, edge.angle2 = angle1, angle2 - edge.id1, edge.id2 = t1, t2 - - return edge - - -def calc_edges(x_list, z_list): - edges = [] - cost = 0.0 - z_ids = list(itertools.combinations(range(len(z_list)), 2)) - - for (t1, t2) in z_ids: - x1, y1, yaw1 = x_list[0, t1], x_list[1, t1], x_list[2, t1] - x2, y2, yaw2 = x_list[0, t2], x_list[1, t2], x_list[2, t2] - - if z_list[t1] is None or z_list[t2] is None: - continue # No observation - - for iz1 in range(len(z_list[t1][:, 0])): - for iz2 in range(len(z_list[t2][:, 0])): - if z_list[t1][iz1, 3] == z_list[t2][iz2, 3]: - d1 = z_list[t1][iz1, 0] - angle1, _ = z_list[t1][iz1, 1], z_list[t1][iz1, 2] - d2 = z_list[t2][iz2, 0] - angle2, _ = z_list[t2][iz2, 1], z_list[t2][iz2, 2] - - edge = calc_edge(x1, y1, yaw1, x2, y2, yaw2, d1, - angle1, d2, angle2, t1, t2) - - edges.append(edge) - cost += (edge.e.T @ edge.omega @ edge.e)[0, 0] - - print("cost:", cost, ",n_edge:", len(edges)) - return edges - - -def calc_jacobian(edge): - t1 = edge.yaw1 + edge.angle1 - A = np.array([[-1.0, 0, edge.d1 * math.sin(t1)], - [0, -1.0, -edge.d1 * math.cos(t1)], - [0, 0, 0]]) - - t2 = edge.yaw2 + edge.angle2 - B = np.array([[1.0, 0, -edge.d2 * math.sin(t2)], - [0, 1.0, edge.d2 * math.cos(t2)], - [0, 0, 0]]) - - return A, B - - -def fill_H_and_b(H, b, edge): - A, B = calc_jacobian(edge) - - id1 = edge.id1 * STATE_SIZE - id2 = edge.id2 * STATE_SIZE - - H[id1:id1 + STATE_SIZE, id1:id1 + STATE_SIZE] += A.T @ edge.omega @ A - H[id1:id1 + STATE_SIZE, id2:id2 + STATE_SIZE] += A.T @ edge.omega @ B - H[id2:id2 + STATE_SIZE, id1:id1 + STATE_SIZE] += B.T @ edge.omega @ A - H[id2:id2 + STATE_SIZE, id2:id2 + STATE_SIZE] += B.T @ edge.omega @ B - - b[id1:id1 + STATE_SIZE] += (A.T @ edge.omega @ edge.e) - b[id2:id2 + STATE_SIZE] += (B.T @ edge.omega @ edge.e) - - return H, b - - -def graph_based_slam(x_init, hz): - print("start graph based slam") - - z_list = copy.deepcopy(hz) - - x_opt = copy.deepcopy(x_init) - nt = x_opt.shape[1] - n = nt * STATE_SIZE - - for itr in range(MAX_ITR): - edges = calc_edges(x_opt, z_list) - - H = np.zeros((n, n)) - b = np.zeros((n, 1)) - - for edge in edges: - H, b = fill_H_and_b(H, b, edge) - - # to fix origin - H[0:STATE_SIZE, 0:STATE_SIZE] += np.identity(STATE_SIZE) - - dx = - np.linalg.inv(H) @ b - - for i in range(nt): - x_opt[0:3, i] += dx[i * 3:i * 3 + 3, 0] - - diff = (dx.T @ dx)[0, 0] - print("iteration: %d, diff: %f" % (itr + 1, diff)) - if diff < 1.0e-5: - break - - return x_opt - - -def calc_input(): - v = 1.0 # [m/s] - yaw_rate = 0.1 # [rad/s] - u = np.array([[v, yaw_rate]]).T - return u - - -def observation(xTrue, xd, u, RFID): - xTrue = motion_model(xTrue, u) - - # add noise to gps x-y - z = np.zeros((0, 4)) - - for i in range(len(RFID[:, 0])): - - dx = RFID[i, 0] - xTrue[0, 0] - dy = RFID[i, 1] - xTrue[1, 0] - d = math.hypot(dx, dy) - angle = pi_2_pi(math.atan2(dy, dx)) - xTrue[2, 0] - phi = pi_2_pi(math.atan2(dy, dx)) - if d <= MAX_RANGE: - dn = d + np.random.randn() * Q_sim[0, 0] # add noise - angle_noise = np.random.randn() * Q_sim[1, 1] - angle += angle_noise - phi += angle_noise - zi = np.array([dn, angle, phi, i]) - z = np.vstack((z, zi)) - - # add noise to input - ud1 = u[0, 0] + np.random.randn() * R_sim[0, 0] - ud2 = u[1, 0] + np.random.randn() * R_sim[1, 1] - ud = np.array([[ud1, ud2]]).T - - xd = motion_model(xd, ud) - - return xTrue, z, xd, ud - - -def motion_model(x, u): - F = np.array([[1.0, 0, 0], - [0, 1.0, 0], - [0, 0, 1.0]]) - - B = np.array([[DT * math.cos(x[2, 0]), 0], - [DT * math.sin(x[2, 0]), 0], - [0.0, DT]]) - - x = F @ x + B @ u - - return x - - -def pi_2_pi(angle): - return angle_mod(angle) - - -def main(): - print(__file__ + " start!!") - - time = 0.0 - - # RFID positions [x, y, yaw] - RFID = np.array([[10.0, -2.0, 0.0], - [15.0, 10.0, 0.0], - [3.0, 15.0, 0.0], - [-5.0, 20.0, 0.0], - [-5.0, 5.0, 0.0] - ]) - - # State Vector [x y yaw v]' - xTrue = np.zeros((STATE_SIZE, 1)) - xDR = np.zeros((STATE_SIZE, 1)) # Dead reckoning - - # history - hxTrue = [] - hxDR = [] - hz = [] - d_time = 0.0 - init = False - while SIM_TIME >= time: - - if not init: - hxTrue = xTrue - hxDR = xTrue - init = True - else: - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - - time += DT - d_time += DT - u = calc_input() - - xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) - - hz.append(z) - - if d_time >= show_graph_d_time: - x_opt = graph_based_slam(hxDR, hz) - d_time = 0.0 - - if show_animation: # pragma: no cover - 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]) - plt.plot(RFID[:, 0], RFID[:, 1], "*k") - - plt.plot(hxTrue[0, :].flatten(), - hxTrue[1, :].flatten(), "-b") - plt.plot(hxDR[0, :].flatten(), - hxDR[1, :].flatten(), "-k") - plt.plot(x_opt[0, :].flatten(), - x_opt[1, :].flatten(), "-r") - plt.axis("equal") - plt.grid(True) - plt.title("Time" + str(time)[0:5]) - plt.pause(1.0) - - -if __name__ == '__main__': - main() diff --git a/SLAM/GraphBasedSLAM/graphslam/__init__.py b/SLAM/GraphBasedSLAM/graphslam/__init__.py deleted file mode 100644 index 6aa2872e77..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -"""Graph SLAM solver in Python. - -""" diff --git a/SLAM/GraphBasedSLAM/graphslam/edge/__init__.py b/SLAM/GraphBasedSLAM/graphslam/edge/__init__.py deleted file mode 100644 index afb8549087..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/edge/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam diff --git a/SLAM/GraphBasedSLAM/graphslam/edge/edge_odometry.py b/SLAM/GraphBasedSLAM/graphslam/edge/edge_odometry.py deleted file mode 100644 index b247b97b43..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/edge/edge_odometry.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -r"""A class for odometry edges. - -""" - - -import numpy as np -import matplotlib.pyplot as plt - - -#: The difference that will be used for numerical differentiation -EPSILON = 1e-6 - - -class EdgeOdometry: - r"""A class for representing odometry edges in Graph SLAM. - - Parameters - ---------- - vertices : list[graphslam.vertex.Vertex] - A list of the vertices constrained by the edge - information : np.ndarray - The information matrix :math:`\Omega_j` associated with the edge - estimate : graphslam.pose.se2.PoseSE2 - The expected measurement :math:`\mathbf{z}_j` - - Attributes - ---------- - vertices : list[graphslam.vertex.Vertex] - A list of the vertices constrained by the edge - information : np.ndarray - The information matrix :math:`\Omega_j` associated with the edge - estimate : PoseSE2 - The expected measurement :math:`\mathbf{z}_j` - - """ - def __init__(self, vertex_ids, information, estimate, vertices=None): - self.vertex_ids = vertex_ids - self.information = information - self.estimate = estimate - self.vertices = vertices - - def calc_error(self): - r"""Calculate the error for the edge: :math:`\mathbf{e}_j \in \mathbb{R}^\bullet`. - - .. math:: - - \mathbf{e}_j = \mathbf{z}_j - (p_2 \ominus p_1) - - - Returns - ------- - np.ndarray - The error for the edge - - """ - return (self.estimate - (self.vertices[1].pose - self.vertices[0].pose)).to_compact() - - def calc_chi2(self): - r"""Calculate the :math:`\chi^2` error for the edge. - - .. math:: - - \mathbf{e}_j^T \Omega_j \mathbf{e}_j - - - Returns - ------- - float - The :math:`\chi^2` error for the edge - - """ - err = self.calc_error() - - return np.dot(np.dot(np.transpose(err), self.information), err) - - def calc_chi2_gradient_hessian(self): - r"""Calculate the edge's contributions to the graph's :math:`\chi^2` error, gradient (:math:`\mathbf{b}`), and Hessian (:math:`H`). - - Returns - ------- - float - The :math:`\chi^2` error for the edge - dict - The edge's contribution(s) to the gradient - dict - The edge's contribution(s) to the Hessian - - """ - chi2 = self.calc_chi2() - - err = self.calc_error() - - jacobians = self.calc_jacobians() - - return chi2, {v.index: np.dot(np.dot(np.transpose(err), self.information), jacobian) for v, jacobian in zip(self.vertices, jacobians)}, {(self.vertices[i].index, self.vertices[j].index): np.dot(np.dot(np.transpose(jacobians[i]), self.information), jacobians[j]) for i in range(len(jacobians)) for j in range(i, len(jacobians))} - - def calc_jacobians(self): - r"""Calculate the Jacobian of the edge's error with respect to each constrained pose. - - .. math:: - - \frac{\partial}{\partial \Delta \mathbf{x}^k} \left[ \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \right] - - - Returns - ------- - list[np.ndarray] - The Jacobian matrices for the edge with respect to each constrained pose - - """ - err = self.calc_error() - - # The dimensionality of the compact pose representation - dim = len(self.vertices[0].pose.to_compact()) - - return [self._calc_jacobian(err, dim, i) for i in range(len(self.vertices))] - - def _calc_jacobian(self, err, dim, vertex_index): - r"""Calculate the Jacobian of the edge with respect to the specified vertex's pose. - - Parameters - ---------- - err : np.ndarray - The current error for the edge (see :meth:`EdgeOdometry.calc_error`) - dim : int - The dimensionality of the compact pose representation - vertex_index : int - The index of the vertex (pose) for which we are computing the Jacobian - - Returns - ------- - np.ndarray - The Jacobian of the edge with respect to the specified vertex's pose - - """ - jacobian = np.zeros(err.shape + (dim,)) - p0 = self.vertices[vertex_index].pose.copy() - - for d in range(dim): - # update the pose - delta_pose = np.zeros(dim) - delta_pose[d] = EPSILON - self.vertices[vertex_index].pose += delta_pose - - # compute the numerical derivative - jacobian[:, d] = (self.calc_error() - err) / EPSILON - - # restore the pose - self.vertices[vertex_index].pose = p0.copy() - - return jacobian - - def to_g2o(self): - """Export the edge to the .g2o format. - - Returns - ------- - str - The edge in .g2o format - - """ - return "EDGE_SE2 {} {} {} {} {} ".format(self.vertex_ids[0], self.vertex_ids[1], self.estimate[0], self.estimate[1], self.estimate[2]) + " ".join([str(x) for x in self.information[np.triu_indices(3, 0)]]) + "\n" - - def plot(self, color='b'): - """Plot the edge. - - Parameters - ---------- - color : str - The color that will be used to plot the edge - - """ - xy = np.array([v.pose.position for v in self.vertices]) - plt.plot(xy[:, 0], xy[:, 1], color=color) diff --git a/SLAM/GraphBasedSLAM/graphslam/graph.py b/SLAM/GraphBasedSLAM/graphslam/graph.py deleted file mode 100644 index 672f63d60e..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/graph.py +++ /dev/null @@ -1,282 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -r"""A ``Graph`` class that stores the edges and vertices required for Graph SLAM. - -""" - -import warnings -from collections import defaultdict -from functools import reduce - -import matplotlib.pyplot as plt -import numpy as np -from scipy.sparse import SparseEfficiencyWarning, lil_matrix -from scipy.sparse.linalg import spsolve - -warnings.simplefilter("ignore", SparseEfficiencyWarning) -warnings.filterwarnings("ignore", category=SparseEfficiencyWarning) - - -# pylint: disable=too-few-public-methods -class _Chi2GradientHessian: - r"""A class that is used to aggregate the :math:`\chi^2` error, gradient, and Hessian. - - Parameters - ---------- - dim : int - The compact dimensionality of the poses - - Attributes - ---------- - chi2 : float - The :math:`\chi^2` error - dim : int - The compact dimensionality of the poses - gradient : defaultdict - The contributions to the gradient vector - hessian : defaultdict - The contributions to the Hessian matrix - - """ - - def __init__(self, dim): - self.chi2 = 0. - self.dim = dim - self.gradient = defaultdict(lambda: np.zeros(dim)) - self.hessian = defaultdict(lambda: np.zeros((dim, dim))) - - @staticmethod - def update(chi2_grad_hess, incoming): - r"""Update the :math:`\chi^2` error and the gradient and Hessian dictionaries. - - Parameters - ---------- - chi2_grad_hess : _Chi2GradientHessian - The ``_Chi2GradientHessian`` that will be updated - incoming : tuple - - """ - chi2_grad_hess.chi2 += incoming[0] - - for idx, contrib in incoming[1].items(): - chi2_grad_hess.gradient[idx] += contrib - - for (idx1, idx2), contrib in incoming[2].items(): - if idx1 <= idx2: - chi2_grad_hess.hessian[idx1, idx2] += contrib - else: - chi2_grad_hess.hessian[idx2, idx1] += np.transpose(contrib) - - return chi2_grad_hess - - -class Graph(object): - r"""A graph that will be optimized via Graph SLAM. - - Parameters - ---------- - edges : list[graphslam.edge.edge_odometry.EdgeOdometry] - A list of the vertices in the graph - vertices : list[graphslam.vertex.Vertex] - A list of the vertices in the graph - - Attributes - ---------- - _chi2 : float, None - The current :math:`\chi^2` error, or ``None`` if it has not yet been computed - _edges : list[graphslam.edge.edge_odometry.EdgeOdometry] - A list of the edges (i.e., constraints) in the graph - _gradient : numpy.ndarray, None - The gradient :math:`\mathbf{b}` of the :math:`\chi^2` error, or ``None`` if it has not yet been computed - _hessian : scipy.sparse.lil_matrix, None - The Hessian matrix :math:`H`, or ``None`` if it has not yet been computed - _vertices : list[graphslam.vertex.Vertex] - A list of the vertices in the graph - - """ - - def __init__(self, edges, vertices): - # The vertices and edges lists - self._edges = edges - self._vertices = vertices - - # The chi^2 error, gradient, and Hessian - self._chi2 = None - self._gradient = None - self._hessian = None - - self._link_edges() - - def _link_edges(self): - """Fill in the ``vertices`` attributes for the graph's edges. - - """ - index_id_dict = {i: v.id for i, v in enumerate(self._vertices)} - id_index_dict = {v_id: v_index for v_index, v_id in - index_id_dict.items()} - - # Fill in the vertices' `index` attribute - for v in self._vertices: - v.index = id_index_dict[v.id] - - for e in self._edges: - e.vertices = [self._vertices[id_index_dict[v_id]] for v_id in - e.vertex_ids] - - def calc_chi2(self): - r"""Calculate the :math:`\chi^2` error for the ``Graph``. - - Returns - ------- - float - The :math:`\chi^2` error - - """ - self._chi2 = sum((e.calc_chi2() for e in self._edges)) - return self._chi2 - - def _calc_chi2_gradient_hessian(self): - r"""Calculate the :math:`\chi^2` error, the gradient :math:`\mathbf{b}`, and the Hessian :math:`H`. - - """ - n = len(self._vertices) - dim = len(self._vertices[0].pose.to_compact()) - chi2_gradient_hessian = reduce(_Chi2GradientHessian.update, - (e.calc_chi2_gradient_hessian() - for e in self._edges), - _Chi2GradientHessian(dim)) - - self._chi2 = chi2_gradient_hessian.chi2 - - # Fill in the gradient vector - self._gradient = np.zeros(n * dim, dtype=float) - for idx, cont in chi2_gradient_hessian.gradient.items(): - self._gradient[idx * dim: (idx + 1) * dim] += cont - - # Fill in the Hessian matrix - self._hessian = lil_matrix((n * dim, n * dim), dtype=float) - for (row_idx, col_idx), cont in chi2_gradient_hessian.hessian.items(): - x_start = row_idx * dim - x_end = (row_idx + 1) * dim - y_start = col_idx * dim - y_end = (col_idx + 1) * dim - self._hessian[x_start:x_end, y_start:y_end] = cont - - if row_idx != col_idx: - x_start = col_idx * dim - x_end = (col_idx + 1) * dim - y_start = row_idx * dim - y_end = (row_idx + 1) * dim - self._hessian[x_start:x_end, y_start:y_end] = \ - np.transpose(cont) - - def optimize(self, tol=1e-4, max_iter=20, fix_first_pose=True): - r"""Optimize the :math:`\chi^2` error for the ``Graph``. - - Parameters - ---------- - tol : float - If the relative decrease in the :math:`\chi^2` error between iterations is less than ``tol``, we will stop - max_iter : int - The maximum number of iterations - fix_first_pose : bool - If ``True``, we will fix the first pose - - """ - n = len(self._vertices) - dim = len(self._vertices[0].pose.to_compact()) - - # Previous iteration's chi^2 error - chi2_prev = -1. - - # For displaying the optimization progress - print("\nIteration chi^2 rel. change") - print("--------- ----- -----------") - - for i in range(max_iter): - self._calc_chi2_gradient_hessian() - - # Check for convergence (from the previous iteration); this avoids having to calculate chi^2 twice - if i > 0: - rel_diff = (chi2_prev - self._chi2) / ( - chi2_prev + np.finfo(float).eps) - print( - "{:9d} {:20.4f} {:18.6f}".format(i, self._chi2, -rel_diff)) - if self._chi2 < chi2_prev and rel_diff < tol: - return - else: - print("{:9d} {:20.4f}".format(i, self._chi2)) - - # Update the previous iteration's chi^2 error - chi2_prev = self._chi2 - - # Hold the first pose fixed - if fix_first_pose: - self._hessian[:dim, :] = 0. - self._hessian[:, :dim] = 0. - self._hessian[:dim, :dim] += np.eye(dim) - self._gradient[:dim] = 0. - - # Solve for the updates - dx = spsolve(self._hessian, -self._gradient) - - # Apply the updates - for v, dx_i in zip(self._vertices, np.split(dx, n)): - v.pose += dx_i - - # If we reached the maximum number of iterations, print out the final iteration's results - self.calc_chi2() - rel_diff = (chi2_prev - self._chi2) / (chi2_prev + np.finfo(float).eps) - print("{:9d} {:20.4f} {:18.6f}".format( - max_iter, self._chi2, -rel_diff)) - - def to_g2o(self, outfile): - """Save the graph in .g2o format. - - Parameters - ---------- - outfile : str - The path where the graph will be saved - - """ - with open(outfile, 'w') as f: - for v in self._vertices: - f.write(v.to_g2o()) - - for e in self._edges: - f.write(e.to_g2o()) - - def plot(self, vertex_color='r', vertex_marker='o', vertex_markersize=3, - edge_color='b', title=None): - """Plot the graph. - - Parameters - ---------- - vertex_color : str - The color that will be used to plot the vertices - vertex_marker : str - The marker that will be used to plot the vertices - vertex_markersize : int - The size of the plotted vertices - edge_color : str - The color that will be used to plot the edges - title : str, None - The title that will be used for the plot - - """ - plt.figure() - - for e in self._edges: - e.plot(edge_color) - - for v in self._vertices: - v.plot(vertex_color, vertex_marker, vertex_markersize) - - if title: - plt.title(title) - - plt.show() diff --git a/SLAM/GraphBasedSLAM/graphslam/load.py b/SLAM/GraphBasedSLAM/graphslam/load.py deleted file mode 100644 index ebf4291456..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/load.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -"""Functions for loading graphs. - -""" - -import logging - -import numpy as np - -from .edge.edge_odometry import EdgeOdometry -from .graph import Graph -from .pose.se2 import PoseSE2 -from .util import upper_triangular_matrix_to_full_matrix -from .vertex import Vertex - -_LOGGER = logging.getLogger(__name__) - - -def load_g2o_se2(infile): - """Load an :math:`SE(2)` graph from a .g2o file. - - Parameters - ---------- - infile : str - The path to the .g2o file - - Returns - ------- - Graph - The loaded graph - - """ - edges = [] - vertices = [] - - with open(infile) as f: - for line in f.readlines(): - if line.startswith("VERTEX_SE2"): - numbers = line[10:].split() - arr = np.array([float(number) for number in numbers[1:]], - dtype=float) - p = PoseSE2(arr[:2], arr[2]) - v = Vertex(int(numbers[0]), p) - vertices.append(v) - continue - - if line.startswith("EDGE_SE2"): - numbers = line[9:].split() - arr = np.array([float(number) for number in numbers[2:]], - dtype=float) - vertex_ids = [int(numbers[0]), int(numbers[1])] - estimate = PoseSE2(arr[:2], arr[2]) - information = upper_triangular_matrix_to_full_matrix(arr[3:], 3) - e = EdgeOdometry(vertex_ids, information, estimate) - edges.append(e) - continue - - if line.strip(): - _LOGGER.warning("Line not supported -- '%s'", line.rstrip()) - - return Graph(edges, vertices) diff --git a/SLAM/GraphBasedSLAM/graphslam/pose/__init__.py b/SLAM/GraphBasedSLAM/graphslam/pose/__init__.py deleted file mode 100644 index afb8549087..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/pose/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam diff --git a/SLAM/GraphBasedSLAM/graphslam/pose/se2.py b/SLAM/GraphBasedSLAM/graphslam/pose/se2.py deleted file mode 100644 index 2a32e765f7..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/pose/se2.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -r"""Representation of a pose in :math:`SE(2)`. - -""" - -import math -import numpy as np - -from ..util import neg_pi_to_pi - - -class PoseSE2(np.ndarray): - r"""A representation of a pose in :math:`SE(2)`. - - Parameters - ---------- - position : np.ndarray, list - The position in :math:`\mathbb{R}^2` - orientation : float - The angle of the pose (in radians) - - """ - - def __new__(cls, position, orientation): - obj = np.array([position[0], position[1], neg_pi_to_pi(orientation)], - dtype=float).view(cls) - return obj - - # pylint: disable=arguments-differ - def copy(self): - """Return a copy of the pose. - - Returns - ------- - PoseSE2 - A copy of the pose - - """ - return PoseSE2(self[:2], self[2]) - - def to_array(self): - """Return the pose as a numpy array. - - Returns - ------- - np.ndarray - The pose as a numpy array - - """ - return np.array(self) - - def to_compact(self): - """Return the pose as a compact numpy array. - - Returns - ------- - np.ndarray - The pose as a compact numpy array - - """ - return np.array(self) - - def to_matrix(self): - """Return the pose as an :math:`SE(2)` matrix. - - Returns - ------- - np.ndarray - The pose as an :math:`SE(2)` matrix - - """ - return np.array([[np.cos(self[2]), -np.sin(self[2]), self[0]], - [np.sin(self[2]), np.cos(self[2]), self[1]], - [0., 0., 1.]], dtype=float) - - @classmethod - def from_matrix(cls, matrix): - """Return the pose as an :math:`SE(2)` matrix. - - Parameters - ---------- - matrix : np.ndarray - The :math:`SE(2)` matrix that will be converted to a `PoseSE2` instance - - Returns - ------- - PoseSE2 - The matrix as a `PoseSE2` object - - """ - return cls([matrix[0, 2], matrix[1, 2]], - math.atan2(matrix[1, 0], matrix[0, 0])) - - # ======================================================================= # - # # - # Properties # - # # - # ======================================================================= # - @property - def position(self): - """Return the pose's position. - - Returns - ------- - np.ndarray - The position portion of the pose - - """ - return np.array(self[:2]) - - @property - def orientation(self): - """Return the pose's orientation. - - Returns - ------- - float - The angle of the pose - - """ - return self[2] - - @property - def inverse(self): - """Return the pose's inverse. - - Returns - ------- - PoseSE2 - The pose's inverse - - """ - return PoseSE2([-self[0] * np.cos(self[2]) - self[1] * np.sin(self[2]), - self[0] * np.sin(self[2]) - self[1] * np.cos(self[2])], - -self[2]) - - # ======================================================================= # - # # - # Magic Methods # - # # - # ======================================================================= # - def __add__(self, other): - r"""Add poses (i.e., pose composition): :math:`p_1 \oplus p_2`. - - Parameters - ---------- - other : PoseSE2 - The other pose - - Returns - ------- - PoseSE2 - The result of pose composition - - """ - return PoseSE2( - [self[0] + other[0] * np.cos(self[2]) - other[1] * np.sin(self[2]), - self[1] + other[0] * np.sin(self[2]) + other[1] * np.cos(self[2]) - ], neg_pi_to_pi(self[2] + other[2])) - - def __sub__(self, other): - r"""Subtract poses (i.e., inverse pose composition): :math:`p_1 \ominus p_2`. - - Parameters - ---------- - other : PoseSE2 - The other pose - - Returns - ------- - PoseSE2 - The result of inverse pose composition - - """ - return PoseSE2([(self[0] - other[0]) * np.cos(other[2]) + ( - self[1] - other[1]) * np.sin(other[2]), - (other[0] - self[0]) * np.sin(other[2]) + ( - self[1] - other[1]) * np.cos(other[2])], - neg_pi_to_pi(self[2] - other[2])) diff --git a/SLAM/GraphBasedSLAM/graphslam/util.py b/SLAM/GraphBasedSLAM/graphslam/util.py deleted file mode 100644 index 619aff20af..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/util.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -"""Utility functions used throughout the package. - -""" - -import numpy as np - - -TWO_PI = 2 * np.pi - - -def neg_pi_to_pi(angle): - r"""Normalize ``angle`` to be in :math:`[-\pi, \pi)`. - - Parameters - ---------- - angle : float - An angle (in radians) - - Returns - ------- - float - The angle normalized to :math:`[-\pi, \pi)` - - """ - return (angle + np.pi) % (TWO_PI) - np.pi - - -def solve_for_edge_dimensionality(n): - r"""Solve for the dimensionality of an edge. - - In a .g2o file, an edge is specified as `` ``, where only the upper triangular portion of the matrix is provided. - - This solves the problem: - - .. math:: - - d + \frac{d (d + 1)}{2} = n - - Returns - ------- - int - The dimensionality of the edge - - """ - return int(round(np.sqrt(2 * n + 2.25) - 1.5)) - - -def upper_triangular_matrix_to_full_matrix(arr, n): - """Given an upper triangular matrix, return the full matrix. - - Parameters - ---------- - arr : np.ndarray - The upper triangular portion of the matrix - n : int - The size of the matrix - - Returns - ------- - mat : np.ndarray - The full matrix - - """ - triu0 = np.triu_indices(n, 0) - tril1 = np.tril_indices(n, -1) - - mat = np.zeros((n, n), dtype=float) - mat[triu0] = arr - mat[tril1] = mat.T[tril1] - - return mat diff --git a/SLAM/GraphBasedSLAM/graphslam/vertex.py b/SLAM/GraphBasedSLAM/graphslam/vertex.py deleted file mode 100644 index 6fb1f0d098..0000000000 --- a/SLAM/GraphBasedSLAM/graphslam/vertex.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2020 Jeff Irion and contributors -# -# This file originated from the `graphslam` package: -# -# https://github.com/JeffLIrion/python-graphslam - -"""A ``Vertex`` class. - -""" - -import matplotlib.pyplot as plt - - -# pylint: disable=too-few-public-methods -class Vertex: - """A class for representing a vertex in Graph SLAM. - - Parameters - ---------- - vertex_id : int - The vertex's unique ID - pose : graphslam.pose.se2.PoseSE2 - The pose associated with the vertex - vertex_index : int, None - The vertex's index in the graph's ``vertices`` list - - Attributes - ---------- - id : int - The vertex's unique ID - index : int, None - The vertex's index in the graph's ``vertices`` list - pose : graphslam.pose.se2.PoseSE2 - The pose associated with the vertex - - """ - def __init__(self, vertex_id, pose, vertex_index=None): - self.id = vertex_id - self.pose = pose - self.index = vertex_index - - def to_g2o(self): - """Export the vertex to the .g2o format. - - Returns - ------- - str - The vertex in .g2o format - - """ - return "VERTEX_SE2 {} {} {} {}\n".format(self.id, self.pose[0], self.pose[1], self.pose[2]) - - def plot(self, color='r', marker='o', markersize=3): - """Plot the vertex. - - Parameters - ---------- - color : str - The color that will be used to plot the vertex - marker : str - The marker that will be used to plot the vertex - markersize : int - The size of the plotted vertex - - """ - x, y = self.pose.position - plt.plot(x, y, color=color, marker=marker, markersize=markersize) diff --git a/SLAM/ICPMatching/icp_matching.py b/SLAM/ICPMatching/icp_matching.py deleted file mode 100644 index 2b44de2c07..0000000000 --- a/SLAM/ICPMatching/icp_matching.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Iterative Closest Point (ICP) SLAM example -author: Atsushi Sakai (@Atsushi_twi), Göktuğ Karakaşlı, Shamil Gemuev -""" - -import math - -from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import -import matplotlib.pyplot as plt -import numpy as np - -# ICP parameters -EPS = 0.0001 -MAX_ITER = 100 - -show_animation = True - - -def icp_matching(previous_points, current_points): - """ - Iterative Closest Point matching - - input - previous_points: 2D or 3D points in the previous frame - current_points: 2D or 3D points in the current frame - - output - R: Rotation matrix - T: Translation vector - """ - H = None # homogeneous transformation matrix - - dError = np.inf - preError = np.inf - count = 0 - - if show_animation: - fig = plt.figure() - if previous_points.shape[0] == 3: - fig.add_subplot(111, projection='3d') - - while dError >= EPS: - count += 1 - - if show_animation: # pragma: no cover - plot_points(previous_points, current_points, fig) - plt.pause(0.1) - - indexes, error = nearest_neighbor_association(previous_points, current_points) - Rt, Tt = svd_motion_estimation(previous_points[:, indexes], current_points) - # update current points - current_points = (Rt @ current_points) + Tt[:, np.newaxis] - - dError = preError - error - print("Residual:", error) - - if dError < 0: # prevent matrix H changing, exit loop - print("Not Converge...", preError, dError, count) - break - - preError = error - H = update_homogeneous_matrix(H, Rt, Tt) - - if dError <= EPS: - print("Converge", error, dError, count) - break - elif MAX_ITER <= count: - print("Not Converge...", error, dError, count) - break - - R = np.array(H[0:-1, 0:-1]) - T = np.array(H[0:-1, -1]) - - return R, T - - -def update_homogeneous_matrix(Hin, R, T): - - r_size = R.shape[0] - H = np.zeros((r_size + 1, r_size + 1)) - - H[0:r_size, 0:r_size] = R - H[0:r_size, r_size] = T - H[r_size, r_size] = 1.0 - - if Hin is None: - return H - else: - return Hin @ H - - -def nearest_neighbor_association(previous_points, current_points): - - # calc the sum of residual errors - delta_points = previous_points - current_points - d = np.linalg.norm(delta_points, axis=0) - error = sum(d) - - # calc index with nearest neighbor assosiation - d = np.linalg.norm(np.repeat(current_points, previous_points.shape[1], axis=1) - - np.tile(previous_points, (1, current_points.shape[1])), axis=0) - indexes = np.argmin(d.reshape(current_points.shape[1], previous_points.shape[1]), axis=1) - - return indexes, error - - -def svd_motion_estimation(previous_points, current_points): - pm = np.mean(previous_points, axis=1) - cm = np.mean(current_points, axis=1) - - p_shift = previous_points - pm[:, np.newaxis] - c_shift = current_points - cm[:, np.newaxis] - - W = c_shift @ p_shift.T - u, s, vh = np.linalg.svd(W) - - R = (u @ vh).T - t = pm - (R @ cm) - - return R, t - - -def plot_points(previous_points, current_points, figure): - # 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]) - if previous_points.shape[0] == 3: - plt.clf() - axes = figure.add_subplot(111, projection='3d') - axes.scatter(previous_points[0, :], previous_points[1, :], - previous_points[2, :], c="r", marker=".") - axes.scatter(current_points[0, :], current_points[1, :], - current_points[2, :], c="b", marker=".") - axes.scatter(0.0, 0.0, 0.0, c="r", marker="x") - figure.canvas.draw() - else: - plt.cla() - plt.plot(previous_points[0, :], previous_points[1, :], ".r") - plt.plot(current_points[0, :], current_points[1, :], ".b") - plt.plot(0.0, 0.0, "xr") - plt.axis("equal") - - -def main(): - print(__file__ + " start!!") - - # simulation parameters - nPoint = 1000 - fieldLength = 50.0 - motion = [0.5, 2.0, np.deg2rad(-10.0)] # movement [x[m],y[m],yaw[deg]] - - nsim = 3 # number of simulation - - for _ in range(nsim): - - # previous points - px = (np.random.rand(nPoint) - 0.5) * fieldLength - py = (np.random.rand(nPoint) - 0.5) * fieldLength - previous_points = np.vstack((px, py)) - - # current points - cx = [math.cos(motion[2]) * x - math.sin(motion[2]) * y + motion[0] - for (x, y) in zip(px, py)] - cy = [math.sin(motion[2]) * x + math.cos(motion[2]) * y + motion[1] - for (x, y) in zip(px, py)] - current_points = np.vstack((cx, cy)) - - R, T = icp_matching(previous_points, current_points) - print("R:", R) - print("T:", T) - - -def main_3d_points(): - print(__file__ + " start!!") - - # simulation parameters for 3d point set - nPoint = 1000 - fieldLength = 50.0 - motion = [0.5, 2.0, -5, np.deg2rad(-10.0)] # [x[m],y[m],z[m],roll[deg]] - - nsim = 3 # number of simulation - - for _ in range(nsim): - - # previous points - px = (np.random.rand(nPoint) - 0.5) * fieldLength - py = (np.random.rand(nPoint) - 0.5) * fieldLength - pz = (np.random.rand(nPoint) - 0.5) * fieldLength - previous_points = np.vstack((px, py, pz)) - - # current points - cx = [math.cos(motion[3]) * x - math.sin(motion[3]) * z + motion[0] - for (x, z) in zip(px, pz)] - cy = [y + motion[1] for y in py] - cz = [math.sin(motion[3]) * x + math.cos(motion[3]) * z + motion[2] - for (x, z) in zip(px, pz)] - current_points = np.vstack((cx, cy, cz)) - - R, T = icp_matching(previous_points, current_points) - print("R:", R) - print("T:", T) - - -if __name__ == '__main__': - main() - main_3d_points() diff --git a/SLAM/__init__.py b/SLAM/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 3ce0be729f..0000000000 --- a/_config.yml +++ /dev/null @@ -1,2 +0,0 @@ -theme: jekyll-theme-slate -show_downloads: true diff --git a/docs/modules/2_localization/histogram_filter_localization/1.png b/_images/1.png similarity index 100% rename from docs/modules/2_localization/histogram_filter_localization/1.png rename to _images/1.png diff --git a/docs/modules/2_localization/histogram_filter_localization/2.png b/_images/2.png similarity index 100% rename from docs/modules/2_localization/histogram_filter_localization/2.png rename to _images/2.png diff --git a/docs/modules/2_localization/histogram_filter_localization/3.png b/_images/3.png similarity index 100% rename from docs/modules/2_localization/histogram_filter_localization/3.png rename to _images/3.png diff --git a/docs/modules/2_localization/histogram_filter_localization/4.png b/_images/4.png similarity index 100% rename from docs/modules/2_localization/histogram_filter_localization/4.png rename to _images/4.png diff --git a/docs/modules/4_slam/FastSLAM1/FastSLAM1_12_0.png b/_images/FastSLAM1_12_0.png similarity index 100% rename from docs/modules/4_slam/FastSLAM1/FastSLAM1_12_0.png rename to _images/FastSLAM1_12_0.png diff --git a/docs/modules/4_slam/FastSLAM1/FastSLAM1_12_1.png b/_images/FastSLAM1_12_1.png similarity index 100% rename from docs/modules/4_slam/FastSLAM1/FastSLAM1_12_1.png rename to _images/FastSLAM1_12_1.png diff --git a/docs/modules/4_slam/FastSLAM1/FastSLAM1_1_0.png b/_images/FastSLAM1_1_0.png similarity index 100% rename from docs/modules/4_slam/FastSLAM1/FastSLAM1_1_0.png rename to _images/FastSLAM1_1_0.png diff --git a/docs/modules/5_path_planning/bezier_path/Figure_1.png b/_images/Figure_1.png similarity index 100% rename from docs/modules/5_path_planning/bezier_path/Figure_1.png rename to _images/Figure_1.png diff --git a/docs/modules/5_path_planning/bezier_path/Figure_2.png b/_images/Figure_2.png similarity index 100% rename from docs/modules/5_path_planning/bezier_path/Figure_2.png rename to _images/Figure_2.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_14_1.png b/_images/Kalmanfilter_basics_14_1.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_14_1.png rename to _images/Kalmanfilter_basics_14_1.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_16_0.png b/_images/Kalmanfilter_basics_16_0.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_16_0.png rename to _images/Kalmanfilter_basics_16_0.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_19_1.png b/_images/Kalmanfilter_basics_19_1.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_19_1.png rename to _images/Kalmanfilter_basics_19_1.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_21_1.png b/_images/Kalmanfilter_basics_21_1.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_21_1.png rename to _images/Kalmanfilter_basics_21_1.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_22_0.png b/_images/Kalmanfilter_basics_22_0.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_22_0.png rename to _images/Kalmanfilter_basics_22_0.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_28_1.png b/_images/Kalmanfilter_basics_28_1.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_files/Kalmanfilter_basics_28_1.png rename to _images/Kalmanfilter_basics_28_1.png diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_2_files/Kalmanfilter_basics_2_5_0.png b/_images/Kalmanfilter_basics_2_5_0.png similarity index 100% rename from docs/modules/12_appendix/Kalmanfilter_basics_2_files/Kalmanfilter_basics_2_5_0.png rename to _images/Kalmanfilter_basics_2_5_0.png diff --git a/docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_12_0.png b/_images/Planar_Two_Link_IK_12_0.png similarity index 100% rename from docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_12_0.png rename to _images/Planar_Two_Link_IK_12_0.png diff --git a/docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_5_0.png b/_images/Planar_Two_Link_IK_5_0.png similarity index 100% rename from docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_5_0.png rename to _images/Planar_Two_Link_IK_5_0.png diff --git a/docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_7_0.png b/_images/Planar_Two_Link_IK_7_0.png similarity index 100% rename from docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_7_0.png rename to _images/Planar_Two_Link_IK_7_0.png diff --git a/docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_9_0.png b/_images/Planar_Two_Link_IK_9_0.png similarity index 100% rename from docs/modules/7_arm_navigation/Planar_Two_Link_IK_files/Planar_Two_Link_IK_9_0.png rename to _images/Planar_Two_Link_IK_9_0.png diff --git a/docs/modules/5_path_planning/dubins_path/RLR.jpg b/_images/RLR.jpg similarity index 100% rename from docs/modules/5_path_planning/dubins_path/RLR.jpg rename to _images/RLR.jpg diff --git a/docs/modules/5_path_planning/dubins_path/RSR.jpg b/_images/RSR.jpg similarity index 100% rename from docs/modules/5_path_planning/dubins_path/RSR.jpg rename to _images/RSR.jpg diff --git a/docs/modules/5_path_planning/bspline_path/approx_and_curvature.png b/_images/approx_and_curvature.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/approx_and_curvature.png rename to _images/approx_and_curvature.png diff --git a/docs/modules/5_path_planning/bspline_path/approximation1.png b/_images/approximation1.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/approximation1.png rename to _images/approximation1.png diff --git a/docs/modules/5_path_planning/bspline_path/basis_functions.png b/_images/basis_functions.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/basis_functions.png rename to _images/basis_functions.png diff --git a/docs/modules/5_path_planning/bspline_path/bspline_path_planning.png b/_images/bspline_path_planning.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/bspline_path_planning.png rename to _images/bspline_path_planning.png diff --git a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_1_0.png b/_images/cgmres_nmpc_1_0.png similarity index 100% rename from docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_1_0.png rename to _images/cgmres_nmpc_1_0.png diff --git a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_2_0.png b/_images/cgmres_nmpc_2_0.png similarity index 100% rename from docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_2_0.png rename to _images/cgmres_nmpc_2_0.png diff --git a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_3_0.png b/_images/cgmres_nmpc_3_0.png similarity index 100% rename from docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_3_0.png rename to _images/cgmres_nmpc_3_0.png diff --git a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_4_0.png b/_images/cgmres_nmpc_4_0.png similarity index 100% rename from docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_4_0.png rename to _images/cgmres_nmpc_4_0.png diff --git a/docs/modules/5_path_planning/cubic_spline/cubic_spline_1d.png b/_images/cubic_spline_1d.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/cubic_spline_1d.png rename to _images/cubic_spline_1d.png diff --git a/docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_curvature.png b/_images/cubic_spline_2d_curvature.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_curvature.png rename to _images/cubic_spline_2d_curvature.png diff --git a/docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_path.png b/_images/cubic_spline_2d_path.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_path.png rename to _images/cubic_spline_2d_path.png diff --git a/docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_yaw.png b/_images/cubic_spline_2d_yaw.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/cubic_spline_2d_yaw.png rename to _images/cubic_spline_2d_yaw.png diff --git a/docs/modules/11_utils/plot/curvature_plot.png b/_images/curvature_plot.png similarity index 100% rename from docs/modules/11_utils/plot/curvature_plot.png rename to _images/curvature_plot.png diff --git a/docs/_static/img/doc_ci.png b/_images/doc_ci.png similarity index 100% rename from docs/_static/img/doc_ci.png rename to _images/doc_ci.png diff --git a/docs/modules/5_path_planning/dubins_path/dubins_path.jpg b/_images/dubins_path.jpg similarity index 100% rename from docs/modules/5_path_planning/dubins_path/dubins_path.jpg rename to _images/dubins_path.jpg diff --git a/docs/modules/4_slam/ekf_slam/ekf_slam_1_0.png b/_images/ekf_slam_1_0.png similarity index 100% rename from docs/modules/4_slam/ekf_slam/ekf_slam_1_0.png rename to _images/ekf_slam_1_0.png diff --git a/docs/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_1_0.png b/_images/extended_kalman_filter_localization_1_0.png similarity index 100% rename from docs/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_1_0.png rename to _images/extended_kalman_filter_localization_1_0.png diff --git a/docs/modules/3_mapping/point_cloud_sampling/farthest_point_sampling.png b/_images/farthest_point_sampling.png similarity index 100% rename from docs/modules/3_mapping/point_cloud_sampling/farthest_point_sampling.png rename to _images/farthest_point_sampling.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_13_0.png b/_images/graphSLAM_SE2_example_13_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_13_0.png rename to _images/graphSLAM_SE2_example_13_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_15_0.png b/_images/graphSLAM_SE2_example_15_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_15_0.png rename to _images/graphSLAM_SE2_example_15_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_16_0.png b/_images/graphSLAM_SE2_example_16_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_16_0.png rename to _images/graphSLAM_SE2_example_16_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_4_0.png b/_images/graphSLAM_SE2_example_4_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_4_0.png rename to _images/graphSLAM_SE2_example_4_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_8_0.png b/_images/graphSLAM_SE2_example_8_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_8_0.png rename to _images/graphSLAM_SE2_example_8_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_9_0.png b/_images/graphSLAM_SE2_example_9_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_SE2_example_files/graphSLAM_SE2_example_9_0.png rename to _images/graphSLAM_SE2_example_9_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_11_1.png b/_images/graphSLAM_doc_11_1.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_11_1.png rename to _images/graphSLAM_doc_11_1.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_11_2.png b/_images/graphSLAM_doc_11_2.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_11_2.png rename to _images/graphSLAM_doc_11_2.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_2_0.png b/_images/graphSLAM_doc_2_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_2_0.png rename to _images/graphSLAM_doc_2_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_2_2.png b/_images/graphSLAM_doc_2_2.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_2_2.png rename to _images/graphSLAM_doc_2_2.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_4_0.png b/_images/graphSLAM_doc_4_0.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_4_0.png rename to _images/graphSLAM_doc_4_0.png diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_9_1.png b/_images/graphSLAM_doc_9_1.png similarity index 100% rename from docs/modules/4_slam/graph_slam/graphSLAM_doc_files/graphSLAM_doc_9_1.png rename to _images/graphSLAM_doc_9_1.png diff --git a/docs/modules/3_mapping/ndt_map/grid_clustering.png b/_images/grid_clustering.png similarity index 100% rename from docs/modules/3_mapping/ndt_map/grid_clustering.png rename to _images/grid_clustering.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/grid_map_example.png b/_images/grid_map_example.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/grid_map_example.png rename to _images/grid_map_example.png diff --git a/docs/modules/5_path_planning/bspline_path/interp_and_curvature.png b/_images/interp_and_curvature.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/interp_and_curvature.png rename to _images/interp_and_curvature.png diff --git a/docs/modules/5_path_planning/bspline_path/interpolation1.png b/_images/interpolation1.png similarity index 100% rename from docs/modules/5_path_planning/bspline_path/interpolation1.png rename to _images/interpolation1.png diff --git a/docs/modules/10_inverted_pendulum/inverted-pendulum.png b/_images/inverted-pendulum.png similarity index 100% rename from docs/modules/10_inverted_pendulum/inverted-pendulum.png rename to _images/inverted-pendulum.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_12_0.png b/_images/lidar_to_grid_map_tutorial_12_0.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_12_0.png rename to _images/lidar_to_grid_map_tutorial_12_0.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_14_1.png b/_images/lidar_to_grid_map_tutorial_14_1.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_14_1.png rename to _images/lidar_to_grid_map_tutorial_14_1.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_5_0.png b/_images/lidar_to_grid_map_tutorial_5_0.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_5_0.png rename to _images/lidar_to_grid_map_tutorial_5_0.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_7_0.png b/_images/lidar_to_grid_map_tutorial_7_0.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_7_0.png rename to _images/lidar_to_grid_map_tutorial_7_0.png diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_8_0.png b/_images/lidar_to_grid_map_tutorial_8_0.png similarity index 100% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_8_0.png rename to _images/lidar_to_grid_map_tutorial_8_0.png diff --git a/docs/modules/5_path_planning/model_predictive_trajectory_generator/lookup_table.png b/_images/lookup_table.png similarity index 100% rename from docs/modules/5_path_planning/model_predictive_trajectory_generator/lookup_table.png rename to _images/lookup_table.png diff --git a/docs/modules/3_mapping/ndt_map/ndt_map1.png b/_images/ndt_map1.png similarity index 100% rename from docs/modules/3_mapping/ndt_map/ndt_map1.png rename to _images/ndt_map1.png diff --git a/docs/modules/3_mapping/ndt_map/ndt_map2.png b/_images/ndt_map2.png similarity index 100% rename from docs/modules/3_mapping/ndt_map/ndt_map2.png rename to _images/ndt_map2.png diff --git a/docs/modules/3_mapping/normal_vector_estimation/normal_vector_calc.png b/_images/normal_vector_calc.png similarity index 100% rename from docs/modules/3_mapping/normal_vector_estimation/normal_vector_calc.png rename to _images/normal_vector_calc.png diff --git a/docs/modules/3_mapping/point_cloud_sampling/poisson_disk_sampling.png b/_images/poisson_disk_sampling.png similarity index 100% rename from docs/modules/3_mapping/point_cloud_sampling/poisson_disk_sampling.png rename to _images/poisson_disk_sampling.png diff --git a/docs/modules/3_mapping/normal_vector_estimation/ransac_normal_vector_estimation.png b/_images/ransac_normal_vector_estimation.png similarity index 100% rename from docs/modules/3_mapping/normal_vector_estimation/ransac_normal_vector_estimation.png rename to _images/ransac_normal_vector_estimation.png diff --git a/docs/modules/3_mapping/ndt_map/raw_observations.png b/_images/raw_observations.png similarity index 100% rename from docs/modules/3_mapping/ndt_map/raw_observations.png rename to _images/raw_observations.png diff --git a/docs/modules/5_path_planning/rrt/rrt_star/rrt_star_1_0.png b/_images/rrt_star_1_0.png similarity index 100% rename from docs/modules/5_path_planning/rrt/rrt_star/rrt_star_1_0.png rename to _images/rrt_star_1_0.png diff --git a/docs/modules/5_path_planning/cubic_spline/spline.png b/_images/spline.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/spline.png rename to _images/spline.png diff --git a/docs/modules/5_path_planning/cubic_spline/spline_continuity.png b/_images/spline_continuity.png similarity index 100% rename from docs/modules/5_path_planning/cubic_spline/spline_continuity.png rename to _images/spline_continuity.png diff --git a/docs/modules/5_path_planning/visibility_road_map_planner/step0.png b/_images/step0.png similarity index 100% rename from docs/modules/5_path_planning/visibility_road_map_planner/step0.png rename to _images/step0.png diff --git a/docs/modules/5_path_planning/visibility_road_map_planner/step1.png b/_images/step1.png similarity index 100% rename from docs/modules/5_path_planning/visibility_road_map_planner/step1.png rename to _images/step1.png diff --git a/docs/modules/5_path_planning/visibility_road_map_planner/step2.png b/_images/step2.png similarity index 100% rename from docs/modules/5_path_planning/visibility_road_map_planner/step2.png rename to _images/step2.png diff --git a/docs/modules/5_path_planning/visibility_road_map_planner/step3.png b/_images/step3.png similarity index 100% rename from docs/modules/5_path_planning/visibility_road_map_planner/step3.png rename to _images/step3.png diff --git a/docs/modules/3_mapping/point_cloud_sampling/voxel_point_sampling.png b/_images/voxel_point_sampling.png similarity index 100% rename from docs/modules/3_mapping/point_cloud_sampling/voxel_point_sampling.png rename to _images/voxel_point_sampling.png diff --git a/_modules/Mapping/ndt_map/ndt_map.html b/_modules/Mapping/ndt_map/ndt_map.html new file mode 100644 index 0000000000..cd4b16eea0 --- /dev/null +++ b/_modules/Mapping/ndt_map/ndt_map.html @@ -0,0 +1,268 @@ + + + + + + Mapping.ndt_map.ndt_map — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • Mapping.ndt_map.ndt_map
  • +
  • +
  • +
+
+
+
+
+ +

Source code for Mapping.ndt_map.ndt_map

+"""
+Normal Distribution Transform (NDTGrid) mapping sample
+"""
+import matplotlib.pyplot as plt
+import numpy as np
+from collections import defaultdict
+
+from Mapping.grid_map_lib.grid_map_lib import GridMap
+from utils.plot import plot_covariance_ellipse
+
+
+
[docs]class NDTMap: + """ + Normal Distribution Transform (NDT) map class + + :param ox: obstacle x position list + :param oy: obstacle y position list + :param resolution: grid resolution [m] + """ + +
[docs] class NDTGrid: + """ + NDT grid + """ + + def __init__(self): + #: Number of points in the NDTGrid grid + self.n_points = 0 + #: Mean x position of points in the NDTGrid cell + self.mean_x = None + #: Mean y position of points in the NDTGrid cell + self.mean_y = None + #: Center x position of the NDT grid + self.center_grid_x = None + #: Center y position of the NDT grid + self.center_grid_y = None + #: Covariance matrix of the NDT grid + self.covariance = None + #: Eigen vectors of the NDT grid + self.eig_vec = None + #: Eigen values of the NDT grid + self.eig_values = None
+ + def __init__(self, ox, oy, resolution): + #: Minimum number of points in the NDT grid + self.min_n_points = 3 + #: Resolution of the NDT grid [m] + self.resolution = resolution + width = int((max(ox) - min(ox))/resolution) + 3 # rounding up + right and left margin + height = int((max(oy) - min(oy))/resolution) + 3 + center_x = np.mean(ox) + center_y = np.mean(oy) + self.ox = ox + self.oy = oy + #: NDT grid index map + self.grid_index_map = self._create_grid_index_map(ox, oy) + + #: NDT grid map. Each grid contains NDTGrid object + self._construct_grid_map(center_x, center_y, height, ox, oy, resolution, width) + + def _construct_grid_map(self, center_x, center_y, height, ox, oy, resolution, width): + self.grid_map = GridMap(width, height, resolution, center_x, center_y, self.NDTGrid()) + for grid_index, inds in self.grid_index_map.items(): + ndt = self.NDTGrid() + ndt.n_points = len(inds) + if ndt.n_points >= self.min_n_points: + ndt.mean_x = np.mean(ox[inds]) + ndt.mean_y = np.mean(oy[inds]) + ndt.center_grid_x, ndt.center_grid_y = \ + self.grid_map.calc_grid_central_xy_position_from_grid_index(grid_index) + ndt.covariance = np.cov(ox[inds], oy[inds]) + ndt.eig_values, ndt.eig_vec = np.linalg.eig(ndt.covariance) + self.grid_map.data[grid_index] = ndt + + def _create_grid_index_map(self, ox, oy): + grid_index_map = defaultdict(list) + for i in range(len(ox)): + grid_index = self.grid_map.calc_grid_index_from_xy_pos(ox[i], oy[i]) + grid_index_map[grid_index].append(i) + return grid_index_map
+ + +def create_dummy_observation_data(): + ox = [] + oy = [] + # left corridor + for y in range(-50, 50): + ox.append(-20.0) + oy.append(y) + # right corridor 1 + for y in range(-50, 0): + ox.append(20.0) + oy.append(y) + # right corridor 2 + for x in range(20, 50): + ox.append(x) + oy.append(0) + # right corridor 3 + for x in range(20, 50): + ox.append(x) + oy.append(x/2.0+10) + # right corridor 4 + for y in range(20, 50): + ox.append(20) + oy.append(y) + ox = np.array(ox) + oy = np.array(oy) + # Adding random noize + ox += np.random.rand(len(ox)) * 1.0 + oy += np.random.rand(len(ox)) * 1.0 + return ox, oy + + +def main(): + print(__file__ + " start!!") + + ox, oy = create_dummy_observation_data() + grid_resolution = 10.0 + ndt_map = NDTMap(ox, oy, grid_resolution) + + # plot raw observation + plt.plot(ox, oy, ".r") + + # plot grid clustering + [plt.plot(ox[inds], oy[inds], "x") for inds in ndt_map.grid_index_map.values()] + + # plot ndt grid map + [plot_covariance_ellipse(ndt.mean_x, ndt.mean_y, ndt.covariance, color="-k") for ndt in ndt_map.grid_map.data if ndt.n_points > 0] + + plt.axis("equal") + plt.show() + + +if __name__ == '__main__': + main() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/Mapping/normal_vector_estimation/normal_vector_estimation.html b/_modules/Mapping/normal_vector_estimation/normal_vector_estimation.html new file mode 100644 index 0000000000..227f848459 --- /dev/null +++ b/_modules/Mapping/normal_vector_estimation/normal_vector_estimation.html @@ -0,0 +1,310 @@ + + + + + + Mapping.normal_vector_estimation.normal_vector_estimation — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • Mapping.normal_vector_estimation.normal_vector_estimation
  • +
  • +
  • +
+
+
+
+
+ +

Source code for Mapping.normal_vector_estimation.normal_vector_estimation

+import numpy as np
+from matplotlib import pyplot as plt
+
+from utils.plot import plot_3d_vector_arrow, plot_triangle, set_equal_3d_axis
+
+show_animation = True
+
+
+
[docs]def calc_normal_vector(p1, p2, p3): + """Calculate normal vector of triangle + + Parameters + ---------- + p1 : np.array + 3D point + p2 : np.array + 3D point + p3 : np.array + 3D point + + Returns + ------- + normal_vector : np.array + normal vector (3,) + + """ + # calculate two vectors of triangle + v1 = p2 - p1 + v2 = p3 - p1 + + # calculate normal vector + normal_vector = np.cross(v1, v2) + + # normalize vector + normal_vector = normal_vector / np.linalg.norm(normal_vector) + + return normal_vector
+ + +def sample_3d_points_from_a_plane(num_samples, normal): + points_2d = np.random.normal(size=(num_samples, 2)) # 2D points on a plane + d = 0 + for i in range(len(points_2d)): + point_3d = np.append(points_2d[i], 0) + d += normal @ point_3d + d /= len(points_2d) + + points_3d = np.zeros((len(points_2d), 3)) + for i in range(len(points_2d)): + point_2d = np.append(points_2d[i], 0) + projection_length = (d - normal @ point_2d) / np.linalg.norm(normal) + points_3d[i] = point_2d + projection_length * normal + + return points_3d + + +def distance_to_plane(point, normal, origin): + dot_product = np.dot(normal, point) - np.dot(normal, origin) + if np.isclose(dot_product, 0): + return 0.0 + else: + distance = abs(dot_product) / np.linalg.norm(normal) + return distance + + +
[docs]def ransac_normal_vector_estimation(points_3d, inlier_radio_th=0.7, + inlier_dist=0.1, p=0.99): + """ + RANSAC based normal vector estimation + + Parameters + ---------- + points_3d : np.array + 3D points (N, 3) + inlier_radio_th : float + Inlier ratio threshold. If inlier ratio is larger than this value, + the iteration is stopped. Default is 0.7. + inlier_dist : float + Inlier distance threshold. If distance between points and estimated + plane is smaller than this value, the point is inlier. Default is 0.1. + p : float + Probability that at least one of the sets of random samples does not + include an outlier. If this probability is near 1, the iteration + number is large. Default is 0.99. + + Returns + ------- + center_vector : np.array + Center of estimated plane. (3,) + normal_vector : np.array + Normal vector of estimated plane. (3,) + + """ + center = np.mean(points_3d, axis=0) + + max_iter = int(np.floor(np.log(1.0-p)/np.log(1.0-(1.0-inlier_radio_th)**3))) + + for ite in range(max_iter): + # Random sampling + sampled_ids = np.random.choice(points_3d.shape[0], size=3, + replace=False) + sampled_points = points_3d[sampled_ids, :] + p1 = sampled_points[0, :] + p2 = sampled_points[1, :] + p3 = sampled_points[2, :] + normal_vector = calc_normal_vector(p1, p2, p3) + + # calc inlier ratio + n_inliner = 0 + for i in range(points_3d.shape[0]): + p = points_3d[i, :] + if distance_to_plane(p, normal_vector, center) <= inlier_dist: + n_inliner += 1 + inlier_ratio = n_inliner / points_3d.shape[0] + print(f"Iter:{ite}, {inlier_ratio=}") + if inlier_ratio > inlier_radio_th: + return center, normal_vector + + return center, None
+ + +def main1(): + p1 = np.array([0.0, 0.0, 1.0]) + p2 = np.array([1.0, 1.0, 0.0]) + p3 = np.array([0.0, 1.0, 0.0]) + + center = np.mean([p1, p2, p3], axis=0) + normal_vector = calc_normal_vector(p1, p2, p3) + print(f"{center=}") + print(f"{normal_vector=}") + + if show_animation: + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + set_equal_3d_axis(ax, [0.0, 2.5], [0.0, 2.5], [0.0, 3.0]) + plot_triangle(p1, p2, p3, ax) + ax.plot(center[0], center[1], center[2], "ro") + plot_3d_vector_arrow(ax, center, center + normal_vector) + plt.show() + + +def main2(rng=None): + true_normal = np.array([0, 1, 1]) + true_normal = true_normal / np.linalg.norm(true_normal) + num_samples = 100 + noise_scale = 0.1 + + points_3d = sample_3d_points_from_a_plane(num_samples, true_normal) + # add random noise + points_3d += np.random.normal(size=points_3d.shape, scale=noise_scale) + + print(f"{points_3d.shape=}") + + center, estimated_normal = ransac_normal_vector_estimation( + points_3d, inlier_dist=noise_scale) + + if estimated_normal is None: + print("Failed to estimate normal vector") + return + + print(f"{true_normal=}") + print(f"{estimated_normal=}") + + if show_animation: + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot(points_3d[:, 0], points_3d[:, 1], points_3d[:, 2], ".r") + plot_3d_vector_arrow(ax, center, center + true_normal) + plot_3d_vector_arrow(ax, center, center + estimated_normal) + set_equal_3d_axis(ax, [-3.0, 3.0], [-3.0, 3.0], [-3.0, 3.0]) + plt.title("RANSAC based Normal vector estimation") + plt.show() + + +if __name__ == '__main__': + # main1() + main2() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/Mapping/point_cloud_sampling/point_cloud_sampling.html b/_modules/Mapping/point_cloud_sampling/point_cloud_sampling.html new file mode 100644 index 0000000000..b1e734dc62 --- /dev/null +++ b/_modules/Mapping/point_cloud_sampling/point_cloud_sampling.html @@ -0,0 +1,301 @@ + + + + + + Mapping.point_cloud_sampling.point_cloud_sampling — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • Mapping.point_cloud_sampling.point_cloud_sampling
  • +
  • +
  • +
+
+
+
+
+ +

Source code for Mapping.point_cloud_sampling.point_cloud_sampling

+"""
+Point cloud sampling example codes. This code supports
+- Voxel point sampling
+- Farthest point sampling
+- Poisson disk sampling
+
+"""
+import matplotlib.pyplot as plt
+import numpy as np
+import numpy.typing as npt
+from collections import defaultdict
+
+do_plot = True
+
+
+
[docs]def voxel_point_sampling(original_points: npt.NDArray, voxel_size: float): + """ + Voxel Point Sampling function. + This function sample N-dimensional points with voxel grid. + Points in a same voxel grid will be merged by mean operation for sampling. + + Parameters + ---------- + original_points : (M, N) N-dimensional points for sampling. + The number of points is M. + voxel_size : voxel grid size + + Returns + ------- + sampled points (M', N) + """ + voxel_dict = defaultdict(list) + for i in range(original_points.shape[0]): + xyz = original_points[i, :] + xyz_index = tuple(xyz // voxel_size) + voxel_dict[xyz_index].append(xyz) + points = np.vstack([np.mean(v, axis=0) for v in voxel_dict.values()]) + return points
+ + +
[docs]def farthest_point_sampling(orig_points: npt.NDArray, + n_points: int, seed: int): + """ + Farthest point sampling function + This function sample N-dimensional points with the farthest point policy. + + Parameters + ---------- + orig_points : (M, N) N-dimensional points for sampling. + The number of points is M. + n_points : number of points for sampling + seed : random seed number + + Returns + ------- + sampled points (n_points, N) + + """ + rng = np.random.default_rng(seed) + n_orig_points = orig_points.shape[0] + first_point_id = rng.choice(range(n_orig_points)) + min_distances = np.ones(n_orig_points) * float("inf") + selected_ids = [first_point_id] + while len(selected_ids) < n_points: + base_point = orig_points[selected_ids[-1], :] + distances = np.linalg.norm(orig_points[np.newaxis, :] - base_point, + axis=2).flatten() + min_distances = np.minimum(min_distances, distances) + distances_rank = np.argsort(-min_distances) # Farthest order + for i in distances_rank: # From the farthest point + if i not in selected_ids: # if not selected yes, select it + selected_ids.append(i) + break + return orig_points[selected_ids, :]
+ + +
[docs]def poisson_disk_sampling(orig_points: npt.NDArray, n_points: int, + min_distance: float, seed: int, MAX_ITER=1000): + """ + Poisson disk sampling function + This function sample N-dimensional points randomly until the number of + points keeping minimum distance between selected points. + + Parameters + ---------- + orig_points : (M, N) N-dimensional points for sampling. + The number of points is M. + n_points : number of points for sampling + min_distance : minimum distance between selected points. + seed : random seed number + MAX_ITER : Maximum number of iteration. Default is 1000. + + Returns + ------- + sampled points (n_points or less, N) + """ + rng = np.random.default_rng(seed) + selected_id = rng.choice(range(orig_points.shape[0])) + selected_ids = [selected_id] + loop = 0 + while len(selected_ids) < n_points and loop <= MAX_ITER: + selected_id = rng.choice(range(orig_points.shape[0])) + base_point = orig_points[selected_id, :] + distances = np.linalg.norm( + orig_points[np.newaxis, selected_ids] - base_point, + axis=2).flatten() + if min(distances) >= min_distance: + selected_ids.append(selected_id) + loop += 1 + if len(selected_ids) != n_points: + print("Could not find the specified number of points...") + + return orig_points[selected_ids, :]
+ + +def plot_sampled_points(original_points, sampled_points, method_name): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter(original_points[:, 0], original_points[:, 1], + original_points[:, 2], marker=".", label="Original points") + ax.scatter(sampled_points[:, 0], sampled_points[:, 1], + sampled_points[:, 2], marker="o", + label="Filtered points") + plt.legend() + plt.title(method_name) + plt.axis("equal") + + +def main(): + n_points = 1000 + seed = 1234 + rng = np.random.default_rng(seed) + + x = rng.normal(0.0, 10.0, n_points) + y = rng.normal(0.0, 1.0, n_points) + z = rng.normal(0.0, 10.0, n_points) + original_points = np.vstack((x, y, z)).T + print(f"{original_points.shape=}") + print("Voxel point sampling") + voxel_size = 20.0 + voxel_sampling_points = voxel_point_sampling(original_points, voxel_size) + print(f"{voxel_sampling_points.shape=}") + + print("Farthest point sampling") + n_points = 20 + farthest_sampling_points = farthest_point_sampling(original_points, + n_points, seed) + print(f"{farthest_sampling_points.shape=}") + + print("Poisson disk sampling") + n_points = 20 + min_distance = 10.0 + poisson_disk_points = poisson_disk_sampling(original_points, n_points, + min_distance, seed) + print(f"{poisson_disk_points.shape=}") + + if do_plot: + plot_sampled_points(original_points, voxel_sampling_points, + "Voxel point sampling") + plot_sampled_points(original_points, farthest_sampling_points, + "Farthest point sampling") + plot_sampled_points(original_points, poisson_disk_points, + "poisson disk sampling") + plt.show() + + +if __name__ == '__main__': + main() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/Mapping/rectangle_fitting/rectangle_fitting.html b/_modules/Mapping/rectangle_fitting/rectangle_fitting.html new file mode 100644 index 0000000000..d2aa0724a7 --- /dev/null +++ b/_modules/Mapping/rectangle_fitting/rectangle_fitting.html @@ -0,0 +1,424 @@ + + + + + + Mapping.rectangle_fitting.rectangle_fitting — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • Mapping.rectangle_fitting.rectangle_fitting
  • +
  • +
  • +
+
+
+
+
+ +

Source code for Mapping.rectangle_fitting.rectangle_fitting

+"""
+
+Object shape recognition with L-shape fitting
+
+author: Atsushi Sakai (@Atsushi_twi)
+
+Ref:
+- Efficient L-Shape Fitting for Vehicle Detection Using Laser Scanners -
+The Robotics Institute Carnegie Mellon University
+https://www.ri.cmu.edu/publications/
+efficient-l-shape-fitting-for-vehicle-detection-using-laser-scanners/
+
+"""
+
+import sys
+import pathlib
+sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
+
+import matplotlib.pyplot as plt
+import numpy as np
+import itertools
+from enum import Enum
+
+from utils.angle import rot_mat_2d
+
+from Mapping.rectangle_fitting.simulator \
+    import VehicleSimulator, LidarSimulator
+
+show_animation = True
+
+
+
[docs]class LShapeFitting: + """ + LShapeFitting class. You can use this class by initializing the class and + changing the parameters, and then calling the fitting method. + + """ + +
[docs] class Criteria(Enum): + AREA = 1 + CLOSENESS = 2 + VARIANCE = 3
+ + def __init__(self): + """ + Default parameter settings + """ + #: Fitting criteria parameter + self.criteria = self.Criteria.VARIANCE + #: Minimum distance for closeness criteria parameter [m] + self.min_dist_of_closeness_criteria = 0.01 + #: Angle difference parameter [deg] + self.d_theta_deg_for_search = 1.0 + #: Range segmentation parameter [m] + self.R0 = 3.0 + #: Range segmentation parameter [m] + self.Rd = 0.001 + +
[docs] def fitting(self, ox, oy): + """ + Fitting L-shape model to object points + + Parameters + ---------- + ox : x positions of range points from an object + oy : y positions of range points from an object + + Returns + ------- + rects: Fitting rectangles + id_sets: id sets of each cluster + + """ + # step1: Adaptive Range Segmentation + id_sets = self._adoptive_range_segmentation(ox, oy) + + # step2 Rectangle search + rects = [] + for ids in id_sets: # for each cluster + cx = [ox[i] for i in range(len(ox)) if i in ids] + cy = [oy[i] for i in range(len(oy)) if i in ids] + rects.append(self._rectangle_search(cx, cy)) + + return rects, id_sets
+ + @staticmethod + def _calc_area_criterion(c1, c2): + c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) + alpha = -(c1_max - c1_min) * (c2_max - c2_min) + return alpha + + def _calc_closeness_criterion(self, c1, c2): + c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) + + # Vectorization + d1 = np.minimum(c1_max - c1, c1 - c1_min) + d2 = np.minimum(c2_max - c2, c2 - c2_min) + d = np.maximum(np.minimum(d1, d2), self.min_dist_of_closeness_criteria) + beta = (1.0 / d).sum() + + return beta + + @staticmethod + def _calc_variance_criterion(c1, c2): + c1_max, c1_min, c2_max, c2_min = LShapeFitting._find_min_max(c1, c2) + + # Vectorization + d1 = np.minimum(c1_max - c1, c1 - c1_min) + d2 = np.minimum(c2_max - c2, c2 - c2_min) + e1 = d1[d1 < d2] + e2 = d2[d1 >= d2] + v1 = - np.var(e1) if len(e1) > 0 else 0. + v2 = - np.var(e2) if len(e2) > 0 else 0. + gamma = v1 + v2 + + return gamma + + @staticmethod + def _find_min_max(c1, c2): + c1_max = max(c1) + c2_max = max(c2) + c1_min = min(c1) + c2_min = min(c2) + return c1_max, c1_min, c2_max, c2_min + + def _rectangle_search(self, x, y): + + xy = np.array([x, y]).T + + d_theta = np.deg2rad(self.d_theta_deg_for_search) + min_cost = (-float('inf'), None) + for theta in np.arange(0.0, np.pi / 2.0 - d_theta, d_theta): + + c = xy @ rot_mat_2d(theta) + c1 = c[:, 0] + c2 = c[:, 1] + + # Select criteria + cost = 0.0 + if self.criteria == self.Criteria.AREA: + cost = self._calc_area_criterion(c1, c2) + elif self.criteria == self.Criteria.CLOSENESS: + cost = self._calc_closeness_criterion(c1, c2) + elif self.criteria == self.Criteria.VARIANCE: + cost = self._calc_variance_criterion(c1, c2) + + if min_cost[0] < cost: + min_cost = (cost, theta) + + # calc best rectangle + sin_s = np.sin(min_cost[1]) + cos_s = np.cos(min_cost[1]) + + c1_s = xy @ np.array([cos_s, sin_s]).T + c2_s = xy @ np.array([-sin_s, cos_s]).T + + rect = RectangleData() + rect.a[0] = cos_s + rect.b[0] = sin_s + rect.c[0] = min(c1_s) + rect.a[1] = -sin_s + rect.b[1] = cos_s + rect.c[1] = min(c2_s) + rect.a[2] = cos_s + rect.b[2] = sin_s + rect.c[2] = max(c1_s) + rect.a[3] = -sin_s + rect.b[3] = cos_s + rect.c[3] = max(c2_s) + + return rect + + def _adoptive_range_segmentation(self, ox, oy): + + # Setup initial cluster + segment_list = [] + for i, _ in enumerate(ox): + c = set() + r = self.R0 + self.Rd * np.linalg.norm([ox[i], oy[i]]) + for j, _ in enumerate(ox): + d = np.hypot(ox[i] - ox[j], oy[i] - oy[j]) + if d <= r: + c.add(j) + segment_list.append(c) + + # Merge cluster + while True: + no_change = True + for (c1, c2) in list(itertools.permutations(range(len(segment_list)), 2)): + if segment_list[c1] & segment_list[c2]: + segment_list[c1] = (segment_list[c1] | segment_list.pop(c2)) + no_change = False + break + if no_change: + break + + return segment_list
+ + +class RectangleData: + + def __init__(self): + self.a = [None] * 4 + self.b = [None] * 4 + self.c = [None] * 4 + + self.rect_c_x = [None] * 5 + self.rect_c_y = [None] * 5 + + def plot(self): + self.calc_rect_contour() + plt.plot(self.rect_c_x, self.rect_c_y, "-r") + + def calc_rect_contour(self): + + self.rect_c_x[0], self.rect_c_y[0] = self.calc_cross_point( + self.a[0:2], self.b[0:2], self.c[0:2]) + self.rect_c_x[1], self.rect_c_y[1] = self.calc_cross_point( + self.a[1:3], self.b[1:3], self.c[1:3]) + self.rect_c_x[2], self.rect_c_y[2] = self.calc_cross_point( + self.a[2:4], self.b[2:4], self.c[2:4]) + self.rect_c_x[3], self.rect_c_y[3] = self.calc_cross_point( + [self.a[3], self.a[0]], [self.b[3], self.b[0]], [self.c[3], self.c[0]]) + self.rect_c_x[4], self.rect_c_y[4] = self.rect_c_x[0], self.rect_c_y[0] + + @staticmethod + def calc_cross_point(a, b, c): + x = (b[0] * -c[1] - b[1] * -c[0]) / (a[0] * b[1] - a[1] * b[0]) + y = (a[1] * -c[0] - a[0] * -c[1]) / (a[0] * b[1] - a[1] * b[0]) + return x, y + + +def main(): + + # simulation parameters + sim_time = 30.0 # simulation time + dt = 0.2 # time tick + + angle_resolution = np.deg2rad(3.0) # sensor angle resolution + + v1 = VehicleSimulator(-10.0, 0.0, np.deg2rad(90.0), + 0.0, 50.0 / 3.6, 3.0, 5.0) + v2 = VehicleSimulator(20.0, 10.0, np.deg2rad(180.0), + 0.0, 50.0 / 3.6, 4.0, 10.0) + + l_shape_fitting = LShapeFitting() + lidar_sim = LidarSimulator() + + time = 0.0 + while time <= sim_time: + time += dt + + v1.update(dt, 0.1, 0.0) + v2.update(dt, 0.1, -0.05) + + ox, oy = lidar_sim.get_observation_points([v1, v2], angle_resolution) + + rects, id_sets = l_shape_fitting.fitting(ox, oy) + + if show_animation: # pragma: no cover + 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]) + plt.axis("equal") + plt.plot(0.0, 0.0, "*r") + v1.plot() + v2.plot() + + # Plot range observation + for ids in id_sets: + x = [ox[i] for i in range(len(ox)) if i in ids] + y = [oy[i] for i in range(len(ox)) if i in ids] + + for (ix, iy) in zip(x, y): + plt.plot([0.0, ix], [0.0, iy], "-og") + + plt.plot([ox[i] for i in range(len(ox)) if i in ids], + [oy[i] for i in range(len(ox)) if i in ids], + "o") + for rect in rects: + rect.plot() + + plt.pause(0.1) + + print("Done") + + +if __name__ == '__main__': + main() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/PathPlanning/BSplinePath/bspline_path.html b/_modules/PathPlanning/BSplinePath/bspline_path.html new file mode 100644 index 0000000000..3725c43074 --- /dev/null +++ b/_modules/PathPlanning/BSplinePath/bspline_path.html @@ -0,0 +1,285 @@ + + + + + + PathPlanning.BSplinePath.bspline_path — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • PathPlanning.BSplinePath.bspline_path
  • +
  • +
  • +
+
+
+
+
+ +

Source code for PathPlanning.BSplinePath.bspline_path

+"""
+
+Path Planner with B-Spline
+
+author: Atsushi Sakai (@Atsushi_twi)
+
+"""
+import sys
+import pathlib
+sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
+
+import numpy as np
+import matplotlib.pyplot as plt
+import scipy.interpolate as interpolate
+
+from utils.plot import plot_curvature
+
+
+
[docs]def approximate_b_spline_path(x: list, + y: list, + n_path_points: int, + degree: int = 3, + s=None, + ) -> tuple: + """ + Approximate points with a B-Spline path + + Parameters + ---------- + x : array_like + x position list of approximated points + y : array_like + y position list of approximated points + n_path_points : int + number of path points + degree : int, optional + B Spline curve degree. Must be 2<= k <= 5. Default: 3. + s : int, optional + smoothing parameter. If this value is bigger, the path will be + smoother, but it will be less accurate. If this value is smaller, + the path will be more accurate, but it will be less smooth. + When `s` is 0, it is equivalent to the interpolation. Default is None, + in this case `s` will be `len(x)`. + + Returns + ------- + x : array + x positions of the result path + y : array + y positions of the result path + heading : array + heading of the result path + curvature : array + curvature of the result path + + """ + distances = _calc_distance_vector(x, y) + + spl_i_x = interpolate.UnivariateSpline(distances, x, k=degree, s=s) + spl_i_y = interpolate.UnivariateSpline(distances, y, k=degree, s=s) + + sampled = np.linspace(0.0, distances[-1], n_path_points) + return _evaluate_spline(sampled, spl_i_x, spl_i_y)
+ + +
[docs]def interpolate_b_spline_path(x, y, + n_path_points: int, + degree: int = 3) -> tuple: + """ + Interpolate x-y points with a B-Spline path + + Parameters + ---------- + x : array_like + x positions of interpolated points + y : array_like + y positions of interpolated points + n_path_points : int + number of path points + degree : int, optional + B-Spline degree. Must be 2<= k <= 5. Default: 3 + + Returns + ------- + x : array + x positions of the result path + y : array + y positions of the result path + heading : array + heading of the result path + curvature : array + curvature of the result path + + """ + return approximate_b_spline_path(x, y, n_path_points, degree, s=0.0)
+ + +def _calc_distance_vector(x, y): + dx, dy = np.diff(x), np.diff(y) + distances = np.cumsum([np.hypot(idx, idy) for idx, idy in zip(dx, dy)]) + distances = np.concatenate(([0.0], distances)) + distances /= distances[-1] + return distances + + +def _evaluate_spline(sampled, spl_i_x, spl_i_y): + x = spl_i_x(sampled) + y = spl_i_y(sampled) + dx = spl_i_x.derivative(1)(sampled) + dy = spl_i_y.derivative(1)(sampled) + heading = np.arctan2(dy, dx) + ddx = spl_i_x.derivative(2)(sampled) + ddy = spl_i_y.derivative(2)(sampled) + curvature = (ddy * dx - ddx * dy) / np.power(dx * dx + dy * dy, 2.0 / 3.0) + return np.array(x), y, heading, curvature, + + +def main(): + print(__file__ + " start!!") + # way points + way_point_x = [-1.0, 3.0, 4.0, 2.0, 1.0] + way_point_y = [0.0, -3.0, 1.0, 1.0, 3.0] + n_course_point = 50 # sampling number + + plt.subplots() + rax, ray, heading, curvature = approximate_b_spline_path( + way_point_x, way_point_y, n_course_point, s=0.5) + plt.plot(rax, ray, '-r', label="Approximated B-Spline path") + plot_curvature(rax, ray, heading, curvature) + + plt.title("B-Spline approximation") + plt.plot(way_point_x, way_point_y, '-og', label="way points") + plt.grid(True) + plt.legend() + plt.axis("equal") + + plt.subplots() + rix, riy, heading, curvature = interpolate_b_spline_path( + way_point_x, way_point_y, n_course_point) + plt.plot(rix, riy, '-b', label="Interpolated B-Spline path") + plot_curvature(rix, riy, heading, curvature) + + plt.title("B-Spline interpolation") + plt.plot(way_point_x, way_point_y, '-og', label="way points") + plt.grid(True) + plt.legend() + plt.axis("equal") + plt.show() + + +if __name__ == '__main__': + main() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/PathPlanning/CubicSpline/cubic_spline_planner.html b/_modules/PathPlanning/CubicSpline/cubic_spline_planner.html new file mode 100644 index 0000000000..cf2b0df073 --- /dev/null +++ b/_modules/PathPlanning/CubicSpline/cubic_spline_planner.html @@ -0,0 +1,521 @@ + + + + + + PathPlanning.CubicSpline.cubic_spline_planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • PathPlanning.CubicSpline.cubic_spline_planner
  • +
  • +
  • +
+
+
+
+
+ +

Source code for PathPlanning.CubicSpline.cubic_spline_planner

+"""
+Cubic spline planner
+
+Author: Atsushi Sakai(@Atsushi_twi)
+
+"""
+import math
+import numpy as np
+import bisect
+
+
+
[docs]class CubicSpline1D: + """ + 1D Cubic Spline class + + Parameters + ---------- + x : list + x coordinates for data points. This x coordinates must be + sorted + in ascending order. + y : list + y coordinates for data points + + Examples + -------- + You can interpolate 1D data points. + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> x = np.arange(5) + >>> y = [1.7, -6, 5, 6.5, 0.0] + >>> sp = CubicSpline1D(x, y) + >>> xi = np.linspace(0.0, 5.0) + >>> yi = [sp.calc_position(x) for x in xi] + >>> plt.plot(x, y, "xb", label="Data points") + >>> plt.plot(xi, yi , "r", label="Cubic spline interpolation") + >>> plt.grid(True) + >>> plt.legend() + >>> plt.show() + + .. image:: cubic_spline_1d.png + + """ + + def __init__(self, x, y): + + h = np.diff(x) + if np.any(h < 0): + raise ValueError("x coordinates must be sorted in ascending order") + + self.a, self.b, self.c, self.d = [], [], [], [] + self.x = x + self.y = y + self.nx = len(x) # dimension of x + + # calc coefficient a + self.a = [iy for iy in y] + + # calc coefficient c + A = self.__calc_A(h) + B = self.__calc_B(h, self.a) + self.c = np.linalg.solve(A, B) + + # calc spline coefficient b and d + for i in range(self.nx - 1): + d = (self.c[i + 1] - self.c[i]) / (3.0 * h[i]) + b = 1.0 / h[i] * (self.a[i + 1] - self.a[i]) \ + - h[i] / 3.0 * (2.0 * self.c[i] + self.c[i + 1]) + self.d.append(d) + self.b.append(b) + +
[docs] def calc_position(self, x): + """ + Calc `y` position for given `x`. + + if `x` is outside the data point's `x` range, return None. + + Returns + ------- + y : float + y position for given x. + """ + if x < self.x[0]: + return None + elif x > self.x[-1]: + return None + + i = self.__search_index(x) + dx = x - self.x[i] + position = self.a[i] + self.b[i] * dx + \ + self.c[i] * dx ** 2.0 + self.d[i] * dx ** 3.0 + + return position
+ +
[docs] def calc_first_derivative(self, x): + """ + Calc first derivative at given x. + + if x is outside the input x, return None + + Returns + ------- + dy : float + first derivative for given x. + """ + + if x < self.x[0]: + return None + elif x > self.x[-1]: + return None + + i = self.__search_index(x) + dx = x - self.x[i] + dy = self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx ** 2.0 + return dy
+ +
[docs] def calc_second_derivative(self, x): + """ + Calc second derivative at given x. + + if x is outside the input x, return None + + Returns + ------- + ddy : float + second derivative for given x. + """ + + if x < self.x[0]: + return None + elif x > self.x[-1]: + return None + + i = self.__search_index(x) + dx = x - self.x[i] + ddy = 2.0 * self.c[i] + 6.0 * self.d[i] * dx + return ddy
+ + def __search_index(self, x): + """ + search data segment index + """ + return bisect.bisect(self.x, x) - 1 + + def __calc_A(self, h): + """ + calc matrix A for spline coefficient c + """ + A = np.zeros((self.nx, self.nx)) + A[0, 0] = 1.0 + for i in range(self.nx - 1): + if i != (self.nx - 2): + A[i + 1, i + 1] = 2.0 * (h[i] + h[i + 1]) + A[i + 1, i] = h[i] + A[i, i + 1] = h[i] + + A[0, 1] = 0.0 + A[self.nx - 1, self.nx - 2] = 0.0 + A[self.nx - 1, self.nx - 1] = 1.0 + return A + + def __calc_B(self, h, a): + """ + calc matrix B for spline coefficient c + """ + B = np.zeros(self.nx) + for i in range(self.nx - 2): + B[i + 1] = 3.0 * (a[i + 2] - a[i + 1]) / h[i + 1]\ + - 3.0 * (a[i + 1] - a[i]) / h[i] + return B
+ + +
[docs]class CubicSpline2D: + """ + Cubic CubicSpline2D class + + Parameters + ---------- + x : list + x coordinates for data points. + y : list + y coordinates for data points. + + Examples + -------- + You can interpolate a 2D data points. + + >>> import matplotlib.pyplot as plt + >>> x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] + >>> y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0] + >>> ds = 0.1 # [m] distance of each interpolated points + >>> sp = CubicSpline2D(x, y) + >>> s = np.arange(0, sp.s[-1], ds) + >>> rx, ry, ryaw, rk = [], [], [], [] + >>> for i_s in s: + ... ix, iy = sp.calc_position(i_s) + ... rx.append(ix) + ... ry.append(iy) + ... ryaw.append(sp.calc_yaw(i_s)) + ... rk.append(sp.calc_curvature(i_s)) + >>> plt.subplots(1) + >>> plt.plot(x, y, "xb", label="Data points") + >>> plt.plot(rx, ry, "-r", label="Cubic spline path") + >>> plt.grid(True) + >>> plt.axis("equal") + >>> plt.xlabel("x[m]") + >>> plt.ylabel("y[m]") + >>> plt.legend() + >>> plt.show() + + .. image:: cubic_spline_2d_path.png + + >>> plt.subplots(1) + >>> plt.plot(s, [np.rad2deg(iyaw) for iyaw in ryaw], "-r", label="yaw") + >>> plt.grid(True) + >>> plt.legend() + >>> plt.xlabel("line length[m]") + >>> plt.ylabel("yaw angle[deg]") + + .. image:: cubic_spline_2d_yaw.png + + >>> plt.subplots(1) + >>> plt.plot(s, rk, "-r", label="curvature") + >>> plt.grid(True) + >>> plt.legend() + >>> plt.xlabel("line length[m]") + >>> plt.ylabel("curvature [1/m]") + + .. image:: cubic_spline_2d_curvature.png + """ + + def __init__(self, x, y): + self.s = self.__calc_s(x, y) + self.sx = CubicSpline1D(self.s, x) + self.sy = CubicSpline1D(self.s, y) + + def __calc_s(self, x, y): + dx = np.diff(x) + dy = np.diff(y) + self.ds = np.hypot(dx, dy) + s = [0] + s.extend(np.cumsum(self.ds)) + return s + +
[docs] def calc_position(self, s): + """ + calc position + + Parameters + ---------- + s : float + distance from the start point. if `s` is outside the data point's + range, return None. + + Returns + ------- + x : float + x position for given s. + y : float + y position for given s. + """ + x = self.sx.calc_position(s) + y = self.sy.calc_position(s) + + return x, y
+ +
[docs] def calc_curvature(self, s): + """ + calc curvature + + Parameters + ---------- + s : float + distance from the start point. if `s` is outside the data point's + range, return None. + + Returns + ------- + k : float + curvature for given s. + """ + dx = self.sx.calc_first_derivative(s) + ddx = self.sx.calc_second_derivative(s) + dy = self.sy.calc_first_derivative(s) + ddy = self.sy.calc_second_derivative(s) + k = (ddy * dx - ddx * dy) / ((dx ** 2 + dy ** 2)**(3 / 2)) + return k
+ +
[docs] def calc_yaw(self, s): + """ + calc yaw + + Parameters + ---------- + s : float + distance from the start point. if `s` is outside the data point's + range, return None. + + Returns + ------- + yaw : float + yaw angle (tangent vector) for given s. + """ + dx = self.sx.calc_first_derivative(s) + dy = self.sy.calc_first_derivative(s) + yaw = math.atan2(dy, dx) + return yaw
+ + +def calc_spline_course(x, y, ds=0.1): + sp = CubicSpline2D(x, y) + s = list(np.arange(0, sp.s[-1], ds)) + + rx, ry, ryaw, rk = [], [], [], [] + for i_s in s: + ix, iy = sp.calc_position(i_s) + rx.append(ix) + ry.append(iy) + ryaw.append(sp.calc_yaw(i_s)) + rk.append(sp.calc_curvature(i_s)) + + return rx, ry, ryaw, rk, s + + +def main_1d(): + print("CubicSpline1D test") + import matplotlib.pyplot as plt + x = np.arange(5) + y = [1.7, -6, 5, 6.5, 0.0] + sp = CubicSpline1D(x, y) + xi = np.linspace(0.0, 5.0) + + plt.plot(x, y, "xb", label="Data points") + plt.plot(xi, [sp.calc_position(x) for x in xi], "r", + label="Cubic spline interpolation") + plt.grid(True) + plt.legend() + plt.show() + + +def main_2d(): # pragma: no cover + print("CubicSpline1D 2D test") + import matplotlib.pyplot as plt + x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0] + y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0] + ds = 0.1 # [m] distance of each interpolated points + + sp = CubicSpline2D(x, y) + s = np.arange(0, sp.s[-1], ds) + + rx, ry, ryaw, rk = [], [], [], [] + for i_s in s: + ix, iy = sp.calc_position(i_s) + rx.append(ix) + ry.append(iy) + ryaw.append(sp.calc_yaw(i_s)) + rk.append(sp.calc_curvature(i_s)) + + plt.subplots(1) + plt.plot(x, y, "xb", label="Data points") + plt.plot(rx, ry, "-r", label="Cubic spline path") + plt.grid(True) + plt.axis("equal") + plt.xlabel("x[m]") + plt.ylabel("y[m]") + plt.legend() + + plt.subplots(1) + plt.plot(s, [np.rad2deg(iyaw) for iyaw in ryaw], "-r", label="yaw") + plt.grid(True) + plt.legend() + plt.xlabel("line length[m]") + plt.ylabel("yaw angle[deg]") + + plt.subplots(1) + plt.plot(s, rk, "-r", label="curvature") + plt.grid(True) + plt.legend() + plt.xlabel("line length[m]") + plt.ylabel("curvature [1/m]") + + plt.show() + + +if __name__ == '__main__': + # main_1d() + main_2d() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/PathPlanning/DubinsPath/dubins_path_planner.html b/_modules/PathPlanning/DubinsPath/dubins_path_planner.html new file mode 100644 index 0000000000..51d0ed396e --- /dev/null +++ b/_modules/PathPlanning/DubinsPath/dubins_path_planner.html @@ -0,0 +1,450 @@ + + + + + + PathPlanning.DubinsPath.dubins_path_planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • PathPlanning.DubinsPath.dubins_path_planner
  • +
  • +
  • +
+
+
+
+
+ +

Source code for PathPlanning.DubinsPath.dubins_path_planner

+"""
+
+Dubins path planner sample code
+
+author Atsushi Sakai(@Atsushi_twi)
+
+"""
+import sys
+import pathlib
+sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
+
+from math import sin, cos, atan2, sqrt, acos, pi, hypot
+import numpy as np
+from utils.angle import angle_mod, rot_mat_2d
+
+show_animation = True
+
+
+
[docs]def plan_dubins_path(s_x, s_y, s_yaw, g_x, g_y, g_yaw, curvature, + step_size=0.1, selected_types=None): + """ + Plan dubins path + + Parameters + ---------- + s_x : float + x position of the start point [m] + s_y : float + y position of the start point [m] + s_yaw : float + yaw angle of the start point [rad] + g_x : float + x position of the goal point [m] + g_y : float + y position of the end point [m] + g_yaw : float + yaw angle of the end point [rad] + curvature : float + curvature for curve [1/m] + step_size : float (optional) + step size between two path points [m]. Default is 0.1 + selected_types : a list of string or None + selected path planning types. If None, all types are used for + path planning, and minimum path length result is returned. + You can select used path plannings types by a string list. + e.g.: ["RSL", "RSR"] + + Returns + ------- + x_list: array + x positions of the path + y_list: array + y positions of the path + yaw_list: array + yaw angles of the path + modes: array + mode list of the path + lengths: array + arrow_length list of the path segments. + + Examples + -------- + You can generate a dubins path. + + >>> start_x = 1.0 # [m] + >>> start_y = 1.0 # [m] + >>> start_yaw = np.deg2rad(45.0) # [rad] + >>> end_x = -3.0 # [m] + >>> end_y = -3.0 # [m] + >>> end_yaw = np.deg2rad(-45.0) # [rad] + >>> curvature = 1.0 + >>> path_x, path_y, path_yaw, mode, _ = plan_dubins_path( + start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature) + >>> plt.plot(path_x, path_y, label="final course " + "".join(mode)) + >>> plot_arrow(start_x, start_y, start_yaw) + >>> plot_arrow(end_x, end_y, end_yaw) + >>> plt.legend() + >>> plt.grid(True) + >>> plt.axis("equal") + >>> plt.show() + + .. image:: dubins_path.jpg + """ + if selected_types is None: + planning_funcs = _PATH_TYPE_MAP.values() + else: + planning_funcs = [_PATH_TYPE_MAP[ptype] for ptype in selected_types] + + # calculate local goal x, y, yaw + l_rot = rot_mat_2d(s_yaw) + le_xy = np.stack([g_x - s_x, g_y - s_y]).T @ l_rot + local_goal_x = le_xy[0] + local_goal_y = le_xy[1] + local_goal_yaw = g_yaw - s_yaw + + lp_x, lp_y, lp_yaw, modes, lengths = _dubins_path_planning_from_origin( + local_goal_x, local_goal_y, local_goal_yaw, curvature, step_size, + planning_funcs) + + # Convert a local coordinate path to the global coordinate + rot = rot_mat_2d(-s_yaw) + converted_xy = np.stack([lp_x, lp_y]).T @ rot + x_list = converted_xy[:, 0] + s_x + y_list = converted_xy[:, 1] + s_y + yaw_list = angle_mod(np.array(lp_yaw) + s_yaw) + + return x_list, y_list, yaw_list, modes, lengths
+ + +def _mod2pi(theta): + return angle_mod(theta, zero_2_2pi=True) + + +def _calc_trig_funcs(alpha, beta): + sin_a = sin(alpha) + sin_b = sin(beta) + cos_a = cos(alpha) + cos_b = cos(beta) + cos_ab = cos(alpha - beta) + return sin_a, sin_b, cos_a, cos_b, cos_ab + + +def _LSL(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + mode = ["L", "S", "L"] + p_squared = 2 + d ** 2 - (2 * cos_ab) + (2 * d * (sin_a - sin_b)) + if p_squared < 0: # invalid configuration + return None, None, None, mode + tmp = atan2((cos_b - cos_a), d + sin_a - sin_b) + d1 = _mod2pi(-alpha + tmp) + d2 = sqrt(p_squared) + d3 = _mod2pi(beta - tmp) + return d1, d2, d3, mode + + +def _RSR(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + mode = ["R", "S", "R"] + p_squared = 2 + d ** 2 - (2 * cos_ab) + (2 * d * (sin_b - sin_a)) + if p_squared < 0: + return None, None, None, mode + tmp = atan2((cos_a - cos_b), d - sin_a + sin_b) + d1 = _mod2pi(alpha - tmp) + d2 = sqrt(p_squared) + d3 = _mod2pi(-beta + tmp) + return d1, d2, d3, mode + + +def _LSR(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + p_squared = -2 + d ** 2 + (2 * cos_ab) + (2 * d * (sin_a + sin_b)) + mode = ["L", "S", "R"] + if p_squared < 0: + return None, None, None, mode + d1 = sqrt(p_squared) + tmp = atan2((-cos_a - cos_b), (d + sin_a + sin_b)) - atan2(-2.0, d1) + d2 = _mod2pi(-alpha + tmp) + d3 = _mod2pi(-_mod2pi(beta) + tmp) + return d2, d1, d3, mode + + +def _RSL(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + p_squared = d ** 2 - 2 + (2 * cos_ab) - (2 * d * (sin_a + sin_b)) + mode = ["R", "S", "L"] + if p_squared < 0: + return None, None, None, mode + d1 = sqrt(p_squared) + tmp = atan2((cos_a + cos_b), (d - sin_a - sin_b)) - atan2(2.0, d1) + d2 = _mod2pi(alpha - tmp) + d3 = _mod2pi(beta - tmp) + return d2, d1, d3, mode + + +def _RLR(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + mode = ["R", "L", "R"] + tmp = (6.0 - d ** 2 + 2.0 * cos_ab + 2.0 * d * (sin_a - sin_b)) / 8.0 + if abs(tmp) > 1.0: + return None, None, None, mode + d2 = _mod2pi(2 * pi - acos(tmp)) + d1 = _mod2pi(alpha - atan2(cos_a - cos_b, d - sin_a + sin_b) + d2 / 2.0) + d3 = _mod2pi(alpha - beta - d1 + d2) + return d1, d2, d3, mode + + +def _LRL(alpha, beta, d): + sin_a, sin_b, cos_a, cos_b, cos_ab = _calc_trig_funcs(alpha, beta) + mode = ["L", "R", "L"] + tmp = (6.0 - d ** 2 + 2.0 * cos_ab + 2.0 * d * (- sin_a + sin_b)) / 8.0 + if abs(tmp) > 1.0: + return None, None, None, mode + d2 = _mod2pi(2 * pi - acos(tmp)) + d1 = _mod2pi(-alpha - atan2(cos_a - cos_b, d + sin_a - sin_b) + d2 / 2.0) + d3 = _mod2pi(_mod2pi(beta) - alpha - d1 + _mod2pi(d2)) + return d1, d2, d3, mode + + +_PATH_TYPE_MAP = {"LSL": _LSL, "RSR": _RSR, "LSR": _LSR, "RSL": _RSL, + "RLR": _RLR, "LRL": _LRL, } + + +def _dubins_path_planning_from_origin(end_x, end_y, end_yaw, curvature, + step_size, planning_funcs): + dx = end_x + dy = end_y + d = hypot(dx, dy) * curvature + + theta = _mod2pi(atan2(dy, dx)) + alpha = _mod2pi(-theta) + beta = _mod2pi(end_yaw - theta) + + best_cost = float("inf") + b_d1, b_d2, b_d3, b_mode = None, None, None, None + + for planner in planning_funcs: + d1, d2, d3, mode = planner(alpha, beta, d) + if d1 is None: + continue + + cost = (abs(d1) + abs(d2) + abs(d3)) + if best_cost > cost: # Select minimum length one. + b_d1, b_d2, b_d3, b_mode, best_cost = d1, d2, d3, mode, cost + + lengths = [b_d1, b_d2, b_d3] + x_list, y_list, yaw_list = _generate_local_course(lengths, b_mode, + curvature, step_size) + + lengths = [length / curvature for length in lengths] + + return x_list, y_list, yaw_list, b_mode, lengths + + +def _interpolate(length, mode, max_curvature, origin_x, origin_y, + origin_yaw, path_x, path_y, path_yaw): + if mode == "S": + path_x.append(origin_x + length / max_curvature * cos(origin_yaw)) + path_y.append(origin_y + length / max_curvature * sin(origin_yaw)) + path_yaw.append(origin_yaw) + else: # curve + ldx = sin(length) / max_curvature + ldy = 0.0 + if mode == "L": # left turn + ldy = (1.0 - cos(length)) / max_curvature + elif mode == "R": # right turn + ldy = (1.0 - cos(length)) / -max_curvature + gdx = cos(-origin_yaw) * ldx + sin(-origin_yaw) * ldy + gdy = -sin(-origin_yaw) * ldx + cos(-origin_yaw) * ldy + path_x.append(origin_x + gdx) + path_y.append(origin_y + gdy) + + if mode == "L": # left turn + path_yaw.append(origin_yaw + length) + elif mode == "R": # right turn + path_yaw.append(origin_yaw - length) + + return path_x, path_y, path_yaw + + +def _generate_local_course(lengths, modes, max_curvature, step_size): + p_x, p_y, p_yaw = [0.0], [0.0], [0.0] + + for (mode, length) in zip(modes, lengths): + if length == 0.0: + continue + + # set origin state + origin_x, origin_y, origin_yaw = p_x[-1], p_y[-1], p_yaw[-1] + + current_length = step_size + while abs(current_length + step_size) <= abs(length): + p_x, p_y, p_yaw = _interpolate(current_length, mode, max_curvature, + origin_x, origin_y, origin_yaw, + p_x, p_y, p_yaw) + current_length += step_size + + p_x, p_y, p_yaw = _interpolate(length, mode, max_curvature, origin_x, + origin_y, origin_yaw, p_x, p_y, p_yaw) + + return p_x, p_y, p_yaw + + +def main(): + print("Dubins path planner sample start!!") + import matplotlib.pyplot as plt + from utils.plot import plot_arrow + + start_x = 1.0 # [m] + start_y = 1.0 # [m] + start_yaw = np.deg2rad(45.0) # [rad] + + end_x = -3.0 # [m] + end_y = -3.0 # [m] + end_yaw = np.deg2rad(-45.0) # [rad] + + curvature = 1.0 + + path_x, path_y, path_yaw, mode, lengths = plan_dubins_path(start_x, + start_y, + start_yaw, + end_x, + end_y, + end_yaw, + curvature) + + if show_animation: + plt.plot(path_x, path_y, label="".join(mode)) + plot_arrow(start_x, start_y, start_yaw) + plot_arrow(end_x, end_y, end_yaw) + plt.legend() + plt.grid(True) + plt.axis("equal") + plt.show() + + +if __name__ == '__main__': + main() +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000000..f5dff4f9d3 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,140 @@ + + + + + + Overview: module code — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/_modules/utils/plot.html b/_modules/utils/plot.html new file mode 100644 index 0000000000..bae2f2c727 --- /dev/null +++ b/_modules/utils/plot.html @@ -0,0 +1,367 @@ + + + + + + utils.plot — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for utils.plot

+"""
+Matplotlib based plotting utilities
+"""
+import math
+import matplotlib.pyplot as plt
+import numpy as np
+from mpl_toolkits.mplot3d import art3d
+from matplotlib.patches import FancyArrowPatch
+from mpl_toolkits.mplot3d.proj3d import proj_transform
+from mpl_toolkits.mplot3d import Axes3D
+
+from utils.angle import rot_mat_2d
+
+
+def plot_covariance_ellipse(x, y, cov, chi2=3.0, color="-r", ax=None):
+    """
+    This function plots an ellipse that represents a covariance matrix. The ellipse is centered at (x, y) and its shape, size and rotation are determined by the covariance matrix.
+
+    Parameters:
+    x : (float) The x-coordinate of the center of the ellipse.
+    y : (float) The y-coordinate of the center of the ellipse.
+    cov : (numpy.ndarray) A 2x2 covariance matrix that determines the shape, size, and rotation of the ellipse.
+    chi2 : (float, optional) A scalar value that scales the ellipse size. This value is typically set based on chi-squared distribution quantiles to achieve certain confidence levels (e.g., 3.0 corresponds to ~95% confidence for a 2D Gaussian). Defaults to 3.0.
+    color : (str, optional) The color and line style of the ellipse plot, following matplotlib conventions. Defaults to "-r" (a red solid line).
+    ax : (matplotlib.axes.Axes, optional) The Axes object to draw the ellipse on. If None (default), a new figure and axes are created.
+
+    Returns:
+    None. This function plots the covariance ellipse on the specified axes.
+    """
+    eig_val, eig_vec = np.linalg.eig(cov)
+
+    if eig_val[0] >= eig_val[1]:
+        big_ind = 0
+        small_ind = 1
+    else:
+        big_ind = 1
+        small_ind = 0
+    a = math.sqrt(chi2 * eig_val[big_ind])
+    b = math.sqrt(chi2 * eig_val[small_ind])
+    angle = math.atan2(eig_vec[1, big_ind], eig_vec[0, big_ind])
+    plot_ellipse(x, y, a, b, angle, color=color, ax=ax)
+
+
+def plot_ellipse(x, y, a, b, angle, color="-r", ax=None, **kwargs):
+    """
+    This function plots an ellipse based on the given parameters.
+
+    Parameters
+    ----------
+    x : (float) The x-coordinate of the center of the ellipse.
+    y : (float) The y-coordinate of the center of the ellipse.
+    a : (float) The length of the semi-major axis of the ellipse.
+    b : (float) The length of the semi-minor axis of the ellipse.
+    angle : (float) The rotation angle of the ellipse, in radians.
+    color : (str, optional) The color and line style of the ellipse plot, following matplotlib conventions. Defaults to "-r" (a red solid line).
+    ax : (matplotlib.axes.Axes, optional) The Axes object to draw the ellipse on. If None (default), a new figure and axes are created.
+    **kwargs: Additional keyword arguments to pass to plt.plot or ax.plot.
+
+    Returns
+    ---------
+    None. This function plots the ellipse based on the specified parameters.
+    """
+
+    t = np.arange(0, 2 * math.pi + 0.1, 0.1)
+    px = [a * math.cos(it) for it in t]
+    py = [b * math.sin(it) for it in t]
+    fx = rot_mat_2d(angle) @ (np.array([px, py]))
+    px = np.array(fx[0, :] + x).flatten()
+    py = np.array(fx[1, :] + y).flatten()
+    if ax is None:
+        plt.plot(px, py, color, **kwargs)
+    else:
+        ax.plot(px, py, color, **kwargs)
+
+
+def plot_arrow(x, y, yaw, arrow_length=1.0,
+               origin_point_plot_style="xr",
+               head_width=0.1, fc="r", ec="k", **kwargs):
+    """
+    Plot an arrow or arrows based on 2D state (x, y, yaw)
+
+    All optional settings of matplotlib.pyplot.arrow can be used.
+    - matplotlib.pyplot.arrow:
+    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.arrow.html
+
+    Parameters
+    ----------
+    x : a float or array_like
+        a value or a list of arrow origin x position.
+    y : a float or array_like
+        a value or a list of arrow origin y position.
+    yaw : a float or array_like
+        a value or a list of arrow yaw angle (orientation).
+    arrow_length : a float (optional)
+        arrow length. default is 1.0
+    origin_point_plot_style : str (optional)
+        origin point plot style. If None, not plotting.
+    head_width : a float (optional)
+        arrow head width. default is 0.1
+    fc : string (optional)
+        face color
+    ec : string (optional)
+        edge color
+    """
+    if not isinstance(x, float):
+        for (i_x, i_y, i_yaw) in zip(x, y, yaw):
+            plot_arrow(i_x, i_y, i_yaw, head_width=head_width,
+                       fc=fc, ec=ec, **kwargs)
+    else:
+        plt.arrow(x, y,
+                  arrow_length * math.cos(yaw),
+                  arrow_length * math.sin(yaw),
+                  head_width=head_width,
+                  fc=fc, ec=ec,
+                  **kwargs)
+        if origin_point_plot_style is not None:
+            plt.plot(x, y, origin_point_plot_style)
+
+
+
[docs]def plot_curvature(x_list, y_list, heading_list, curvature, + k=0.01, c="-c", label="Curvature"): + """ + Plot curvature on 2D path. This plot is a line from the original path, + the lateral distance from the original path shows curvature magnitude. + Left turning shows right side plot, right turning shows left side plot. + For straight path, the curvature plot will be on the path, because + curvature is 0 on the straight path. + + Parameters + ---------- + x_list : array_like + x position list of the path + y_list : array_like + y position list of the path + heading_list : array_like + heading list of the path + curvature : array_like + curvature list of the path + k : float + curvature scale factor to calculate distance from the original path + c : string + color of the plot + label : string + label of the plot + """ + cx = [x + d * k * np.cos(yaw - np.pi / 2.0) for x, y, yaw, d in + zip(x_list, y_list, heading_list, curvature)] + cy = [y + d * k * np.sin(yaw - np.pi / 2.0) for x, y, yaw, d in + zip(x_list, y_list, heading_list, curvature)] + + plt.plot(cx, cy, c, label=label) + for ix, iy, icx, icy in zip(x_list, y_list, cx, cy): + plt.plot([ix, icx], [iy, icy], c)
+ + +class Arrow3D(FancyArrowPatch): + + def __init__(self, x, y, z, dx, dy, dz, *args, **kwargs): + super().__init__((0, 0), (0, 0), *args, **kwargs) + self._xyz = (x, y, z) + self._dxdydz = (dx, dy, dz) + + def draw(self, renderer): + x1, y1, z1 = self._xyz + dx, dy, dz = self._dxdydz + x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz) + + xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M) + self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) + super().draw(renderer) + + def do_3d_projection(self, renderer=None): + x1, y1, z1 = self._xyz + dx, dy, dz = self._dxdydz + x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz) + + xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M) + self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) + + return np.min(zs) + + +def _arrow3D(ax, x, y, z, dx, dy, dz, *args, **kwargs): + '''Add an 3d arrow to an `Axes3D` instance.''' + arrow = Arrow3D(x, y, z, dx, dy, dz, *args, **kwargs) + ax.add_artist(arrow) + + +def plot_3d_vector_arrow(ax, p1, p2): + setattr(Axes3D, 'arrow3D', _arrow3D) + ax.arrow3D(p1[0], p1[1], p1[2], + p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2], + mutation_scale=20, + arrowstyle="-|>", + ) + + +def plot_triangle(p1, p2, p3, ax): + ax.add_collection3d(art3d.Poly3DCollection([[p1, p2, p3]], color='b')) + + +def set_equal_3d_axis(ax, x_lims, y_lims, z_lims): + """Helper function to set equal axis + + Args: + ax (Axes3DSubplot): matplotlib 3D axis, created by + `ax = fig.add_subplot(projection='3d')` + x_lims (np.array): array containing min and max value of x + y_lims (np.array): array containing min and max value of y + z_lims (np.array): array containing min and max value of z + """ + x_lims = np.asarray(x_lims) + y_lims = np.asarray(y_lims) + z_lims = np.asarray(z_lims) + # compute max required range + max_range = np.array([x_lims.max() - x_lims.min(), + y_lims.max() - y_lims.min(), + z_lims.max() - z_lims.min()]).max() / 2.0 + # compute mid-point along each axis + mid_x = (x_lims.max() + x_lims.min()) * 0.5 + mid_y = (y_lims.max() + y_lims.min()) * 0.5 + mid_z = (z_lims.max() + z_lims.min()) * 0.5 + + # set limits to axis + ax.set_xlim(mid_x - max_range, mid_x + max_range) + ax.set_ylim(mid_y - max_range, mid_y + max_range) + ax.set_zlim(mid_z - max_range, mid_z + max_range) + + +if __name__ == '__main__': + plot_ellipse(0, 0, 1, 2, np.deg2rad(15)) + plt.axis('equal') + plt.show() + +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_sources/getting_started_main.rst.txt b/_sources/getting_started_main.rst.txt new file mode 100644 index 0000000000..497b85a23a --- /dev/null +++ b/_sources/getting_started_main.rst.txt @@ -0,0 +1,85 @@ +.. _`getting started`: + +Getting Started +=============== + +What is this? +------------- + +This is an Open Source Software (OSS) project: PythonRobotics, which is a Python code collection of robotics algorithms. + +The focus of the project is on autonomous navigation, and the goal is for beginners in robotics to understand the basic ideas behind each algorithm. + +In this project, the algorithms which are practical and widely used in both academia and industry are selected. + +Each sample code is written in Python3 and only depends on some standard modules for readability and ease of use. + +It includes intuitive animations to understand the behavior of the simulation. + + +See this paper for more details: + +- PythonRobotics: a Python code collection of robotics algorithms: https://arxiv.org/abs/1808.10703 + +.. _`Requirements`: + +Requirements +------------- + +- `Python 3.11.x`_ +- `NumPy`_ +- `SciPy`_ +- `Matplotlib`_ +- `cvxpy`_ + +For development: + +- `pytest`_ (for unit tests) +- `pytest-xdist`_ (for parallel unit tests) +- `mypy`_ (for type check) +- `sphinx`_ (for document generation) +- `ruff`_ (for code style check) + +.. _`Python 3.11.x`: https://www.python.org/ +.. _`NumPy`: https://numpy.org/ +.. _`SciPy`: https://scipy.org/ +.. _`Matplotlib`: https://matplotlib.org/ +.. _`cvxpy`: https://www.cvxpy.org/ +.. _`pytest`: https://docs.pytest.org/en/latest/ +.. _`pytest-xdist`: https://github.com/pytest-dev/pytest-xdist +.. _`mypy`: https://mypy-lang.org/ +.. _`sphinx`: https://www.sphinx-doc.org/en/master/index.html +.. _`ruff`: https://github.com/charliermarsh/ruff + + +How to use +---------- + +1. Clone this repo and go into dir. + +.. code-block:: + + >$ git clone https://github.com/AtsushiSakai/PythonRobotics.git + + >$ cd PythonRobotics + + +2. Install the required libraries. + +using conda : + +.. code-block:: + + >$ conda env create -f requirements/environment.yml + +using pip : + +.. code-block:: + + >$ pip install -r requirements/requirements.txt + + +3. Execute python script in each directory. + +4. Add star to this repo if you like it 😃. + diff --git a/docs/modules/0_getting_started/3_how_to_contribute_main.rst b/_sources/how_to_contribute_main.rst.txt similarity index 65% rename from docs/modules/0_getting_started/3_how_to_contribute_main.rst rename to _sources/how_to_contribute_main.rst.txt index 0325aaacae..48895d6fda 100644 --- a/docs/modules/0_getting_started/3_how_to_contribute_main.rst +++ b/_sources/how_to_contribute_main.rst.txt @@ -1,39 +1,10 @@ -How to contribute +How To Contribute ================= This document describes how to contribute this project. -There are several ways to contribute to this project as below: -#. `Adding a new algorithm example`_ -#. `Reporting and fixing a defect`_ -#. `Adding missed documentations for existing examples`_ -#. `Supporting this project`_ - -Before contributing -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Please check following items before contributing: - -Understanding this project ---------------------------- - -Please check this :ref:`What is PythonRobotics?` section and this paper -`PythonRobotics: a Python code collection of robotics algorithms`_ -to understand the philosophies of this project. - -.. _`PythonRobotics: a Python code collection of robotics algorithms`: https://arxiv.org/abs/1808.10703 - -Check your Python version. ---------------------------- - -We only accept a PR for Python 3.13.x or higher. - -We will not accept a PR for Python 2.x. - -.. _`Adding a new algorithm example`: - -1. Adding a new algorithm example -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Adding a new algorithm example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is a step by step manual to add a new algorithm example. @@ -96,28 +67,9 @@ Please check other documents for details. You can build the doc locally based on `doc README`_. -For creating a gif animation, you can use this tool: `matplotrecorder`_. - -The created gif file should be stored in the `PythonRoboticsGifs`_ repository, -so please create a PR to add it and refer to it in the doc. - -Note that the `reStructuredText`_ based doc should only focus on the -mathematics and the algorithm of the example. - -Documentations related codes should be in the python script as the header -comments of the script or docstrings of each function. - -Also, each document should have a link to the code in Github. -You can easily add the link by using the `.. autoclass::`, `.. autofunction::`, and `.. automodule` by Sphinx's `autodoc`_ module. - -Using this `autodoc`_ module, the generated documentations have the link to the code in Github like: - -.. image:: /_static/img/source_link_1.png - -When you click the link, you will jump to the source code in Github like: - -.. image:: /_static/img/source_link_2.png +Note that the `reStructuredText`_ based doc should only focus on the mathematics and the algorithm of the example. +Documentations related codes should be in the python script as the header comments of the script or docstrings of each function. .. _`submit a pull request`: @@ -140,12 +92,9 @@ After that, I will start the review. Note that this is my hobby project; I appreciate your patience during the review process. -  - -.. _`Reporting and fixing a defect`: -2. Reporting and fixing a defect -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Reporting and fixing a defect +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Reporting and fixing a defect is also great contribution. @@ -166,24 +115,19 @@ in the test code to show the issue was solved. This doc `submit a pull request`_ can be helpful to submit a pull request. -.. _`Adding missed documentations for existing examples`: - -3. Adding missed documentations for existing examples -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Adding missed documentations for existing examples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Adding the missed documentations for existing examples is also great contribution. If you check the `Python Robotics Docs`_, you can notice that some of the examples -only have a simulation gif or short overview descriptions or just TBD., +only have a simulation gif or short overview descriptions, but no detailed algorithm or mathematical description. -These documents needs to be improved. This doc `how to write doc`_ can be helpful to write documents. -.. _`Supporting this project`: - -4. Supporting this project -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Supporting this project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Supporting this project financially is also a great contribution!!. @@ -197,12 +141,10 @@ If you or your company would like to support this project, please consider: If you would like to support us in some other way, please contact with creating an issue. -Current Major Sponsors: - -#. `GitHub`_ : They are providing a GitHub Copilot Pro license for this OSS development. -#. `JetBrains`_ : They are providing a free license of their IDEs for this OSS development. -#. `1Password`_ : They are providing a free license of their 1Password team license for this OSS project. +Sponsors +--------- +1. `JetBrains`_ : They are providing a free license of their IDEs for this OSS development. .. _`Python Robotics Docs`: https://atsushisakai.github.io/PythonRobotics @@ -215,13 +157,8 @@ Current Major Sponsors: .. _`doc README`: https://github.com/AtsushiSakai/PythonRobotics/blob/master/docs/README.md .. _`test_codestyle.py`: https://github.com/AtsushiSakai/PythonRobotics/blob/master/tests/test_codestyle.py .. _`JetBrains`: https://www.jetbrains.com/ -.. _`GitHub`: https://www.github.com/ .. _`Sponsor @AtsushiSakai on GitHub Sponsors`: https://github.com/sponsors/AtsushiSakai .. _`Become a backer or sponsor on Patreon`: https://www.patreon.com/myenigma -.. _`One-time donation via PayPal`: https://www.paypal.com/paypalme/myenigmapay/ -.. _`1Password`: https://github.com/1Password/for-open-source -.. _`matplotrecorder`: https://github.com/AtsushiSakai/matplotrecorder -.. _`PythonRoboticsGifs`: https://github.com/AtsushiSakai/PythonRoboticsGifs -.. _`autodoc`: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html +.. _`One-time donation via PayPal`: https://www.paypal.me/myenigmapay/ diff --git a/_sources/index_main.rst.txt b/_sources/index_main.rst.txt new file mode 100644 index 0000000000..67bd9889f2 --- /dev/null +++ b/_sources/index_main.rst.txt @@ -0,0 +1,57 @@ +.. PythonRobotics documentation master file, created by + sphinx-quickstart on Sat Sep 15 13:15:55 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PythonRobotics's documentation! +========================================== + +Python codes for robotics algorithm. The project is on `GitHub`_. + +This is a Python code collection of robotics algorithms. + +Features: + +1. Easy to read for understanding each algorithm's basic idea. + +2. Widely used and practical algorithms are selected. + +3. Minimum dependency. + +See this paper for more details: + +- `[1808.10703] PythonRobotics: a Python code collection of robotics + algorithms`_ (`BibTeX`_) + + +.. _`[1808.10703] PythonRobotics: a Python code collection of robotics algorithms`: https://arxiv.org/abs/1808.10703 +.. _BibTeX: https://github.com/AtsushiSakai/PythonRoboticsPaper/blob/master/python_robotics.bib +.. _GitHub: https://github.com/AtsushiSakai/PythonRobotics + + +.. toctree:: + :maxdepth: 2 + :caption: Contents + + getting_started + modules/introduction + modules/localization/localization + modules/mapping/mapping + modules/slam/slam + modules/path_planning/path_planning + modules/path_tracking/path_tracking + modules/arm_navigation/arm_navigation + modules/aerial_navigation/aerial_navigation + modules/bipedal/bipedal + modules/control/control + modules/utils/utils + modules/appendix/appendix + how_to_contribute + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/modules/8_aerial_navigation/aerial_navigation_main.rst b/_sources/modules/aerial_navigation/aerial_navigation_main.rst.txt similarity index 89% rename from docs/modules/8_aerial_navigation/aerial_navigation_main.rst rename to _sources/modules/aerial_navigation/aerial_navigation_main.rst.txt index 7f76689770..b2ccb071af 100644 --- a/docs/modules/8_aerial_navigation/aerial_navigation_main.rst +++ b/_sources/modules/aerial_navigation/aerial_navigation_main.rst.txt @@ -1,4 +1,4 @@ -.. _`Aerial Navigation`: +.. _aerial_navigation: Aerial Navigation ================= diff --git a/docs/modules/8_aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst b/_sources/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst.txt similarity index 66% rename from docs/modules/8_aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst rename to _sources/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst.txt index 1805bb3f6d..c3bdc33cdc 100644 --- a/docs/modules/8_aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst +++ b/_sources/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst.txt @@ -4,8 +4,3 @@ Drone 3d trajectory following This is a 3d trajectory following simulation for a quadrotor. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/AerialNavigation/drone_3d_trajectory_following/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: AerialNavigation.drone_3d_trajectory_following.drone_3d_trajectory_following.quad_sim diff --git a/docs/modules/8_aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst b/_sources/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst.txt similarity index 96% rename from docs/modules/8_aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst rename to _sources/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst.txt index 4bf5117500..6c90d2b44e 100644 --- a/docs/modules/8_aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst +++ b/_sources/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst.txt @@ -1,14 +1,6 @@ Rocket powered landing ----------------------------- -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/AerialNavigation/rocket_powered_landing/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: AerialNavigation.rocket_powered_landing.rocket_powered_landing.main - - Simulation ~~~~~~~~~~ diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_2_main.rst b/_sources/modules/appendix/Kalmanfilter_basics_2_main.rst.txt similarity index 99% rename from docs/modules/12_appendix/Kalmanfilter_basics_2_main.rst rename to _sources/modules/appendix/Kalmanfilter_basics_2_main.rst.txt index b7ff84e6f6..9ae6fc5bcb 100644 --- a/docs/modules/12_appendix/Kalmanfilter_basics_2_main.rst +++ b/_sources/modules/appendix/Kalmanfilter_basics_2_main.rst.txt @@ -331,7 +331,7 @@ are vectors and matrices, but the concepts are exactly the same: - Use the process model to predict the next state (the prior) - Form an estimate part way between the measurement and the prior -Reference +References: ~~~~~~~~~~~ 1. Roger Labbe’s diff --git a/docs/modules/12_appendix/Kalmanfilter_basics_main.rst b/_sources/modules/appendix/Kalmanfilter_basics_main.rst.txt similarity index 99% rename from docs/modules/12_appendix/Kalmanfilter_basics_main.rst rename to _sources/modules/appendix/Kalmanfilter_basics_main.rst.txt index a1d99d47ef..6548377e07 100644 --- a/docs/modules/12_appendix/Kalmanfilter_basics_main.rst +++ b/_sources/modules/appendix/Kalmanfilter_basics_main.rst.txt @@ -552,7 +552,7 @@ a given (X,Y) value. .. image:: Kalmanfilter_basics_files/Kalmanfilter_basics_28_1.png -Reference +References: ~~~~~~~~~~~ 1. Roger Labbe’s diff --git a/docs/modules/12_appendix/appendix_main.rst b/_sources/modules/appendix/appendix_main.rst.txt similarity index 61% rename from docs/modules/12_appendix/appendix_main.rst rename to _sources/modules/appendix/appendix_main.rst.txt index d0b9eeea3a..8a29d84676 100644 --- a/docs/modules/12_appendix/appendix_main.rst +++ b/_sources/modules/appendix/appendix_main.rst.txt @@ -1,4 +1,4 @@ -.. _`Appendix`: +.. _appendix: Appendix ============== @@ -7,9 +7,6 @@ Appendix :maxdepth: 2 :caption: Contents - steering_motion_model Kalmanfilter_basics Kalmanfilter_basics_2 - internal_sensors - external_sensors diff --git a/docs/modules/7_arm_navigation/arm_navigation_main.rst b/_sources/modules/arm_navigation/arm_navigation_main.rst.txt similarity index 88% rename from docs/modules/7_arm_navigation/arm_navigation_main.rst rename to _sources/modules/arm_navigation/arm_navigation_main.rst.txt index 7acd3ee7d3..bbbc872c58 100644 --- a/docs/modules/7_arm_navigation/arm_navigation_main.rst +++ b/_sources/modules/arm_navigation/arm_navigation_main.rst.txt @@ -1,4 +1,4 @@ -.. _`Arm Navigation`: +.. _arm_navigation: Arm Navigation ============== diff --git a/docs/modules/7_arm_navigation/n_joint_arm_to_point_control_main.rst b/_sources/modules/arm_navigation/n_joint_arm_to_point_control_main.rst.txt similarity index 76% rename from docs/modules/7_arm_navigation/n_joint_arm_to_point_control_main.rst rename to _sources/modules/arm_navigation/n_joint_arm_to_point_control_main.rst.txt index 56900acde1..cc6279f681 100644 --- a/docs/modules/7_arm_navigation/n_joint_arm_to_point_control_main.rst +++ b/_sources/modules/arm_navigation/n_joint_arm_to_point_control_main.rst.txt @@ -11,10 +11,3 @@ plotting area. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/n_joint_arm_to_point_control/animation.gif In this simulation N = 10, however, you can change it. - - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: ArmNavigation.n_joint_arm_to_point_control.n_joint_arm_to_point_control.main - diff --git a/docs/modules/7_arm_navigation/obstacle_avoidance_arm_navigation_main.rst b/_sources/modules/arm_navigation/obstacle_avoidance_arm_navigation_main.rst.txt similarity index 52% rename from docs/modules/7_arm_navigation/obstacle_avoidance_arm_navigation_main.rst rename to _sources/modules/arm_navigation/obstacle_avoidance_arm_navigation_main.rst.txt index 4433ab531b..899a64a5cd 100644 --- a/docs/modules/7_arm_navigation/obstacle_avoidance_arm_navigation_main.rst +++ b/_sources/modules/arm_navigation/obstacle_avoidance_arm_navigation_main.rst.txt @@ -3,9 +3,4 @@ Arm navigation with obstacle avoidance Arm navigation with obstacle avoidance simulation. -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/arm_obstacle_navigation/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: ArmNavigation.arm_obstacle_navigation.arm_obstacle_navigation.main +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/arm_obstacle_navigation/animation.gif \ No newline at end of file diff --git a/docs/modules/7_arm_navigation/planar_two_link_ik_main.rst b/_sources/modules/arm_navigation/planar_two_link_ik_main.rst.txt similarity index 98% rename from docs/modules/7_arm_navigation/planar_two_link_ik_main.rst rename to _sources/modules/arm_navigation/planar_two_link_ik_main.rst.txt index 5b2712eb48..2da2b0dc30 100644 --- a/docs/modules/7_arm_navigation/planar_two_link_ik_main.rst +++ b/_sources/modules/arm_navigation/planar_two_link_ik_main.rst.txt @@ -11,12 +11,6 @@ This is a interactive simulation. You can set the goal position of the end effector with left-click on the plotting area. -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: ArmNavigation.two_joint_arm_to_point_control.two_joint_arm_to_point_control.main - - Inverse Kinematics for a Planar Two-Link Robotic Arm ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/modules/9_bipedal/bipedal_main.rst b/_sources/modules/bipedal/bipedal_main.rst.txt similarity index 88% rename from docs/modules/9_bipedal/bipedal_main.rst rename to _sources/modules/bipedal/bipedal_main.rst.txt index dc387dc4e8..fc5b933055 100644 --- a/docs/modules/9_bipedal/bipedal_main.rst +++ b/_sources/modules/bipedal/bipedal_main.rst.txt @@ -1,4 +1,4 @@ -.. _`Bipedal`: +.. _bipedal: Bipedal ================= diff --git a/docs/modules/9_bipedal/bipedal_planner/bipedal_planner_main.rst b/_sources/modules/bipedal/bipedal_planner/bipedal_planner_main.rst.txt similarity index 67% rename from docs/modules/9_bipedal/bipedal_planner/bipedal_planner_main.rst rename to _sources/modules/bipedal/bipedal_planner/bipedal_planner_main.rst.txt index 6253715cc1..2ee5971e7a 100644 --- a/docs/modules/9_bipedal/bipedal_planner/bipedal_planner_main.rst +++ b/_sources/modules/bipedal/bipedal_planner/bipedal_planner_main.rst.txt @@ -4,8 +4,3 @@ Bipedal Planner Bipedal Walking with modifying designated footsteps .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Bipedal/bipedal_planner/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: Bipedal.bipedal_planner.bipedal_planner.BipedalPlanner diff --git a/_sources/modules/control/control_main.rst.txt b/_sources/modules/control/control_main.rst.txt new file mode 100644 index 0000000000..cee2aa9e8e --- /dev/null +++ b/_sources/modules/control/control_main.rst.txt @@ -0,0 +1,12 @@ +.. _control: + +Control +================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents + + inverted_pendulum_control/inverted_pendulum_control + move_to_a_pose_control/move_to_a_pose_control + diff --git a/docs/modules/10_inverted_pendulum/inverted_pendulum_main.rst b/_sources/modules/control/inverted_pendulum_control/inverted_pendulum_control_main.rst.txt similarity index 92% rename from docs/modules/10_inverted_pendulum/inverted_pendulum_main.rst rename to _sources/modules/control/inverted_pendulum_control/inverted_pendulum_control_main.rst.txt index 58dc0f2e57..e41729fd61 100644 --- a/docs/modules/10_inverted_pendulum/inverted_pendulum_main.rst +++ b/_sources/modules/control/inverted_pendulum_control/inverted_pendulum_control_main.rst.txt @@ -1,7 +1,5 @@ -.. _`Inverted Pendulum`: - -Inverted Pendulum ------------------- +Inverted Pendulum Control +----------------------------- An inverted pendulum on a cart consists of a mass :math:`m` at the top of a pole of length :math:`l` pivoted on a horizontally moving base as shown in the adjacent. @@ -89,12 +87,6 @@ and :math:`P` is the unique positive definite solution to the discrete time .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation_lqr.gif -Code Link -^^^^^^^^^^^ - -.. autofunction:: InvertedPendulum.inverted_pendulum_lqr_control.main - - MPC control ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The MPC controller minimize this cost function defined as: @@ -107,9 +99,3 @@ subject to: - Initial state .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation.gif - -Code Link -^^^^^^^^^^^ - -.. autofunction:: InvertedPendulum.inverted_pendulum_mpc_control.main - diff --git a/docs/modules/6_path_tracking/move_to_a_pose/move_to_a_pose_main.rst b/_sources/modules/control/move_to_a_pose_control/move_to_a_pose_control_main.rst.txt similarity index 97% rename from docs/modules/6_path_tracking/move_to_a_pose/move_to_a_pose_main.rst rename to _sources/modules/control/move_to_a_pose_control/move_to_a_pose_control_main.rst.txt index 19bb0e4c14..77ec682a30 100644 --- a/docs/modules/6_path_tracking/move_to_a_pose/move_to_a_pose_main.rst +++ b/_sources/modules/control/move_to_a_pose_control/move_to_a_pose_control_main.rst.txt @@ -3,13 +3,7 @@ Move to a Pose Control In this section, we present the logic of PathFinderController that drives a car from a start pose (x, y, theta) to a goal pose. A simulation of moving to a pose control is presented below. -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/move_to_pose/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.move_to_pose.move_to_pose.move_to_pose - +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/move_to_pose/animation.gif Position Control of non-Holonomic Systems ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/_sources/modules/introduction_main.rst.txt b/_sources/modules/introduction_main.rst.txt new file mode 100644 index 0000000000..f9fb487297 --- /dev/null +++ b/_sources/modules/introduction_main.rst.txt @@ -0,0 +1,42 @@ + +Introduction +============ + +TBD + +Definition Of Robotics +---------------------- + +TBD + +History Of Robotics +---------------------- + +TBD + +Application Of Robotics +------------------------ + +TBD + +Software for Robotics +---------------------- + +TBD + +Software for Robotics +---------------------- + +TBD + +Python for Robotics +---------------------- + +TBD + +Learning Robotics Algorithms +---------------------------- + +TBD + + diff --git a/docs/modules/2_localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst b/_sources/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst.txt similarity index 65% rename from docs/modules/2_localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst rename to _sources/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst.txt index 214e645d10..2543d1186a 100644 --- a/docs/modules/2_localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst +++ b/_sources/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst.txt @@ -5,8 +5,3 @@ Ensamble Kalman Filter Localization This is a sensor fusion localization with Ensamble Kalman Filter(EnKF). -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Localization.ensemble_kalman_filter.ensemble_kalman_filter.enkf_localization - diff --git a/docs/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst b/_sources/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst.txt similarity index 51% rename from docs/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst rename to _sources/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst.txt index adb41e5e40..0ec920c9df 100644 --- a/docs/modules/2_localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst +++ b/_sources/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst.txt @@ -2,9 +2,6 @@ Extended Kalman Filter Localization ----------------------------------- -Position Estimation Kalman Filter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. image:: extended_kalman_filter_localization_1_0.png :width: 600px @@ -12,7 +9,6 @@ Position Estimation Kalman Filter .. figure:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/extended_kalman_filter/animation.gif - This is a sensor fusion localization with Extended Kalman Filter(EKF). The blue line is true trajectory, the black line is dead reckoning @@ -23,40 +19,11 @@ is estimated trajectory with EKF. The red ellipse is estimated covariance ellipse with EKF. -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: Localization.extended_kalman_filter.extended_kalman_filter.ekf_estimation - -Extended Kalman Filter algorithm -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Localization process using Extended Kalman Filter:EKF is - -=== Predict === - -:math:`x_{Pred} = Fx_t+Bu_t` - -:math:`P_{Pred} = J_f P_t J_f^T + Q` - -=== Update === - -:math:`z_{Pred} = Hx_{Pred}` - -:math:`y = z - z_{Pred}` - -:math:`S = J_g P_{Pred}.J_g^T + R` - -:math:`K = P_{Pred}.J_g^T S^{-1}` - -:math:`x_{t+1} = x_{Pred} + Ky` - -:math:`P_{t+1} = ( I - K J_g) P_{Pred}` - - +Code: `PythonRobotics/extended_kalman_filter.py at master · +AtsushiSakai/PythonRobotics `__ Filter design -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~ In this simulation, the robot has a state vector includes 4 states at time :math:`t`. @@ -94,9 +61,8 @@ In the code, “observation” function generates the input and observation vector with noise `code `__ - Motion Model -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~ The robot model is @@ -134,7 +100,7 @@ Its Jacobian matrix is Observation Model ~~~~~~~~~~~~~~~~~ -The robot can get x-y position information from GPS. +The robot can get x-y position infomation from GPS. So GPS Observation model is @@ -154,99 +120,32 @@ Its Jacobian matrix is :math:`\begin{equation*}  = \begin{bmatrix} 1& 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ \end{bmatrix} \end{equation*}` +Extended Kalman Filter +~~~~~~~~~~~~~~~~~~~~~~ -Kalman Filter with Speed Scale Factor Correction -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This is a Extended kalman filter (EKF) localization with velocity correction. - -This is for correcting the vehicle speed measured with scale factor errors due to factors such as wheel wear. - - -In this simulation, the robot has a state vector includes 5 states at -time :math:`t`. - -.. math:: \textbf{x}_t=[x_t, y_t, \phi_t, v_t, s_t] - -x, y are a 2D x-y position, :math:`\phi` is orientation, v is -velocity, and s is a scale factor of velocity. - -In the code, “xEst” means the state vector. -`code `__ - -The rest is the same as the Position Estimation Kalman Filter. - -.. image:: ekf_with_velocity_correction_1_0.png - :width: 600px - -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: Localization.extended_kalman_filter.ekf_with_velocity_correction.ekf_estimation - - -Motion Model -~~~~~~~~~~~~ - -The robot model is - -.. math:: \dot{x} = v s \cos(\phi) - -.. math:: \dot{y} = v s \sin(\phi) - -.. math:: \dot{\phi} = \omega - -So, the motion model is - -.. math:: \textbf{x}_{t+1} = f(\textbf{x}_t, \textbf{u}_t) = F\textbf{x}_t+B\textbf{u}_t - -where - -:math:`\begin{equation*} F= \begin{bmatrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1\\ \end{bmatrix} \end{equation*}` - -:math:`\begin{equation*} B= \begin{bmatrix} cos(\phi) \Delta t s & 0\\ sin(\phi) \Delta t s & 0\\ 0 & \Delta t\\ 1 & 0\\ 0 & 0\\ \end{bmatrix} \end{equation*}` - -:math:`\Delta t` is a time interval. - -This is implemented at -`code `__ - -The motion function is that - -:math:`\begin{equation*} \begin{bmatrix} x' \\ y' \\ w' \\ v' \end{bmatrix} = f(\textbf{x}, \textbf{u}) = \begin{bmatrix} x + v s \cos(\phi)\Delta t \\ y + v s \sin(\phi)\Delta t \\ \phi + \omega \Delta t \\ v \end{bmatrix} \end{equation*}` - -Its Jacobian matrix is - -:math:`\begin{equation*} J_f = \begin{bmatrix} \frac{\partial x'}{\partial x}& \frac{\partial x'}{\partial y} & \frac{\partial x'}{\partial \phi} & \frac{\partial x'}{\partial v} & \frac{\partial x'}{\partial s}\\ \frac{\partial y'}{\partial x}& \frac{\partial y'}{\partial y} & \frac{\partial y'}{\partial \phi} & \frac{\partial y'}{\partial v} & \frac{\partial y'}{\partial s}\\ \frac{\partial \phi'}{\partial x}& \frac{\partial \phi'}{\partial y} & \frac{\partial \phi'}{\partial \phi} & \frac{\partial \phi'}{\partial v} & \frac{\partial \phi'}{\partial s}\\ \frac{\partial v'}{\partial x}& \frac{\partial v'}{\partial y} & \frac{\partial v'}{\partial \phi} & \frac{\partial v'}{\partial v} & \frac{\partial v'}{\partial s} \\ \frac{\partial s'}{\partial x}& \frac{\partial s'}{\partial y} & \frac{\partial s'}{\partial \phi} & \frac{\partial s'}{\partial v} & \frac{\partial s'}{\partial s} \end{bmatrix} \end{equation*}` - -:math:`\begin{equation*}  = \begin{bmatrix} 1& 0 & -v s \sin(\phi) \Delta t & s \cos(\phi) \Delta t & \cos(\phi) v \Delta t\\ 0 & 1 & v s \cos(\phi) \Delta t & s \sin(\phi) \Delta t & v \sin(\phi) \Delta t\\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \end{bmatrix} \end{equation*}` - - -Observation Model -~~~~~~~~~~~~~~~~~ - -The robot can get x-y position information from GPS. - -So GPS Observation model is +Localization process using Extended Kalman Filter:EKF is -.. math:: \textbf{z}_{t} = g(\textbf{x}_t) = H \textbf{x}_t +=== Predict === -where +:math:`x_{Pred} = Fx_t+Bu_t` -:math:`\begin{equation*} H = \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ \end{bmatrix} \end{equation*}` +:math:`P_{Pred} = J_f P_t J_f^T + Q` -The observation function states that +=== Update === -:math:`\begin{equation*} \begin{bmatrix} x' \\ y' \end{bmatrix} = g(\textbf{x}) = \begin{bmatrix} x \\ y \end{bmatrix} \end{equation*}` +:math:`z_{Pred} = Hx_{Pred}` -Its Jacobian matrix is +:math:`y = z - z_{Pred}` -:math:`\begin{equation*} J_g = \begin{bmatrix} \frac{\partial x'}{\partial x} & \frac{\partial x'}{\partial y} & \frac{\partial x'}{\partial \phi} & \frac{\partial x'}{\partial v} & \frac{\partial x'}{\partial s}\\ \frac{\partial y'}{\partial x}& \frac{\partial y'}{\partial y} & \frac{\partial y'}{\partial \phi} & \frac{\partial y'}{ \partial v} & \frac{\partial y'}{ \partial s}\\ \end{bmatrix} \end{equation*}` +:math:`S = J_g P_{Pred}.J_g^T + R` -:math:`\begin{equation*}  = \begin{bmatrix} 1& 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ \end{bmatrix} \end{equation*}` +:math:`K = P_{Pred}.J_g^T S^{-1}` +:math:`x_{t+1} = x_{Pred} + Ky` +:math:`P_{t+1} = ( I - K J_g) P_{Pred}` -Reference -^^^^^^^^^^^ +Ref: +~~~~ - `PROBABILISTIC-ROBOTICS.ORG `__ diff --git a/docs/modules/2_localization/histogram_filter_localization/histogram_filter_localization_main.rst b/_sources/modules/localization/histogram_filter_localization/histogram_filter_localization_main.rst.txt similarity index 96% rename from docs/modules/2_localization/histogram_filter_localization/histogram_filter_localization_main.rst rename to _sources/modules/localization/histogram_filter_localization/histogram_filter_localization_main.rst.txt index 3a175b1316..fafd578333 100644 --- a/docs/modules/2_localization/histogram_filter_localization/histogram_filter_localization_main.rst +++ b/_sources/modules/localization/histogram_filter_localization/histogram_filter_localization_main.rst.txt @@ -16,11 +16,6 @@ The filter uses speed input and range observations from RFID for localization. Initial position information is not needed. -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: Localization.histogram_filter.histogram_filter.histogram_filter_localization - Filtering algorithm ~~~~~~~~~~~~~~~~~~~~ @@ -112,7 +107,7 @@ There are two ways to calculate the final positions: -Reference +References: ~~~~~~~~~~~ - `_PROBABILISTIC ROBOTICS: `_ diff --git a/docs/modules/2_localization/localization_main.rst b/_sources/modules/localization/localization_main.rst.txt similarity index 55% rename from docs/modules/2_localization/localization_main.rst rename to _sources/modules/localization/localization_main.rst.txt index 770a234b69..db6651f6b8 100644 --- a/docs/modules/2_localization/localization_main.rst +++ b/_sources/modules/localization/localization_main.rst.txt @@ -1,8 +1,7 @@ -.. _`Localization`: +.. _localization: Localization ============ -Localization is the ability of a robot to know its position and orientation with sensors such as Global Navigation Satellite System:GNSS etc. In localization, Bayesian filters such as Kalman filters, histogram filter, and particle filter are widely used[31]. Fig.2 shows localization simulations using histogram filter and particle filter. .. toctree:: :maxdepth: 2 diff --git a/docs/modules/2_localization/particle_filter_localization/particle_filter_localization_main.rst b/_sources/modules/localization/particle_filter_localization/particle_filter_localization_main.rst.txt similarity index 87% rename from docs/modules/2_localization/particle_filter_localization/particle_filter_localization_main.rst rename to _sources/modules/localization/particle_filter_localization/particle_filter_localization_main.rst.txt index d648d8e080..c8652ce8a7 100644 --- a/docs/modules/2_localization/particle_filter_localization/particle_filter_localization_main.rst +++ b/_sources/modules/localization/particle_filter_localization/particle_filter_localization_main.rst.txt @@ -15,12 +15,6 @@ It is assumed that the robot can measure a distance from landmarks This measurements are used for PF localization. -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: Localization.particle_filter.particle_filter.pf_localization - - How to calculate covariance matrix from particles ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -36,8 +30,8 @@ The covariance matrix :math:`\Xi` from particle information is calculated by the - :math:`\mu_j` is the :math:`j` th mean state of particles. -Reference +References: ~~~~~~~~~~~ - `_PROBABILISTIC ROBOTICS: `_ -- `Improving the particle filter in high dimensions using conjugate artificial process noise `_ +- `Improving the particle filter in high dimensions using conjugate artificial process noise `_ diff --git a/docs/modules/2_localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst b/_sources/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst.txt similarity index 81% rename from docs/modules/2_localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst rename to _sources/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst.txt index a7a5aab61b..bb6b5b664b 100644 --- a/docs/modules/2_localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst +++ b/_sources/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst.txt @@ -7,13 +7,7 @@ This is a sensor fusion localization with Unscented Kalman Filter(UKF). The lines and points are same meaning of the EKF simulation. -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: Localization.unscented_kalman_filter.unscented_kalman_filter.ukf_estimation - - -Reference +References: ~~~~~~~~~~~ - `Discriminatively Trained Unscented Kalman Filter for Mobile Robot Localization `_ diff --git a/docs/modules/3_mapping/circle_fitting/circle_fitting_main.rst b/_sources/modules/mapping/circle_fitting/circle_fitting_main.rst.txt similarity index 78% rename from docs/modules/3_mapping/circle_fitting/circle_fitting_main.rst rename to _sources/modules/mapping/circle_fitting/circle_fitting_main.rst.txt index e243529a9c..1892d1f8f7 100644 --- a/docs/modules/3_mapping/circle_fitting/circle_fitting_main.rst +++ b/_sources/modules/mapping/circle_fitting/circle_fitting_main.rst.txt @@ -11,7 +11,3 @@ The red crosses are observations from a ranging sensor. The red circle is the estimated object shape using circle fitting. -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.circle_fitting.circle_fitting.circle_fitting diff --git a/docs/modules/3_mapping/gaussian_grid_map/gaussian_grid_map_main.rst b/_sources/modules/mapping/gaussian_grid_map/gaussian_grid_map_main.rst.txt similarity index 61% rename from docs/modules/3_mapping/gaussian_grid_map/gaussian_grid_map_main.rst rename to _sources/modules/mapping/gaussian_grid_map/gaussian_grid_map_main.rst.txt index 50033d2a20..b0f112a871 100644 --- a/docs/modules/3_mapping/gaussian_grid_map/gaussian_grid_map_main.rst +++ b/_sources/modules/mapping/gaussian_grid_map/gaussian_grid_map_main.rst.txt @@ -6,9 +6,3 @@ Gaussian grid map This is a 2D Gaussian grid mapping example. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/gaussian_grid_map/animation.gif - -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.gaussian_grid_map.gaussian_grid_map.generate_gaussian_grid_map - diff --git a/docs/modules/3_mapping/k_means_object_clustering/k_means_object_clustering_main.rst b/_sources/modules/mapping/k_means_object_clustering/k_means_object_clustering_main.rst.txt similarity index 63% rename from docs/modules/3_mapping/k_means_object_clustering/k_means_object_clustering_main.rst rename to _sources/modules/mapping/k_means_object_clustering/k_means_object_clustering_main.rst.txt index 0ece604009..e098ca5409 100644 --- a/docs/modules/3_mapping/k_means_object_clustering/k_means_object_clustering_main.rst +++ b/_sources/modules/mapping/k_means_object_clustering/k_means_object_clustering_main.rst.txt @@ -4,9 +4,3 @@ k-means object clustering This is a 2D object clustering with k-means algorithm. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/kmeans_clustering/animation.gif - -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.kmeans_clustering.kmeans_clustering.kmeans_clustering - diff --git a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst b/_sources/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst.txt similarity index 98% rename from docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst rename to _sources/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst.txt index 29f5878e48..1f62179efd 100644 --- a/docs/modules/3_mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst +++ b/_sources/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst.txt @@ -196,9 +196,3 @@ Let’s use this flood fill on real data: .. image:: lidar_to_grid_map_tutorial_14_1.png -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.lidar_to_grid_map.lidar_to_grid_map.main - - diff --git a/docs/modules/3_mapping/mapping_main.rst b/_sources/modules/mapping/mapping_main.rst.txt similarity index 50% rename from docs/modules/3_mapping/mapping_main.rst rename to _sources/modules/mapping/mapping_main.rst.txt index 34a3744893..a98acaaf50 100644 --- a/docs/modules/3_mapping/mapping_main.rst +++ b/_sources/modules/mapping/mapping_main.rst.txt @@ -1,9 +1,7 @@ -.. _`Mapping`: +.. _mapping: Mapping ======= -Mapping is the ability of a robot to understand its surroundings with external sensors such as LIDAR and camera. Robots have to recognize the position and shape of obstacles to avoid them. In mapping, grid mapping and machine learning algorithms are widely used[31][18]. Fig.3 shows mapping simulation results using grid mapping with 2D ray casting and 2D object clustering with k-means algorithm. - .. toctree:: :maxdepth: 2 :caption: Contents @@ -17,4 +15,3 @@ Mapping is the ability of a robot to understand its surroundings with external s circle_fitting/circle_fitting rectangle_fitting/rectangle_fitting normal_vector_estimation/normal_vector_estimation - distance_map/distance_map diff --git a/docs/modules/3_mapping/ndt_map/ndt_map_main.rst b/_sources/modules/mapping/ndt_map/ndt_map_main.rst.txt similarity index 100% rename from docs/modules/3_mapping/ndt_map/ndt_map_main.rst rename to _sources/modules/mapping/ndt_map/ndt_map_main.rst.txt diff --git a/docs/modules/3_mapping/normal_vector_estimation/normal_vector_estimation_main.rst b/_sources/modules/mapping/normal_vector_estimation/normal_vector_estimation_main.rst.txt similarity index 97% rename from docs/modules/3_mapping/normal_vector_estimation/normal_vector_estimation_main.rst rename to _sources/modules/mapping/normal_vector_estimation/normal_vector_estimation_main.rst.txt index 68a19e1534..a4d1bf0df2 100644 --- a/docs/modules/3_mapping/normal_vector_estimation/normal_vector_estimation_main.rst +++ b/_sources/modules/mapping/normal_vector_estimation/normal_vector_estimation_main.rst.txt @@ -25,8 +25,8 @@ This is an example of normal vector calculation: .. figure:: normal_vector_calc.png -Code Link -========== +API +===== .. autofunction:: Mapping.normal_vector_estimation.normal_vector_estimation.calc_normal_vector @@ -67,8 +67,8 @@ This is an example of RANSAC based normal vector estimation: .. figure:: ransac_normal_vector_estimation.png -Code Link -========== +API +===== .. autofunction:: Mapping.normal_vector_estimation.normal_vector_estimation.ransac_normal_vector_estimation diff --git a/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst b/_sources/modules/mapping/point_cloud_sampling/point_cloud_sampling_main.rst.txt similarity index 98% rename from docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst rename to _sources/modules/mapping/point_cloud_sampling/point_cloud_sampling_main.rst.txt index 8cb08d4b78..cbb5652f56 100644 --- a/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst +++ b/_sources/modules/mapping/point_cloud_sampling/point_cloud_sampling_main.rst.txt @@ -27,8 +27,8 @@ This method determines which each point is in a grid, and replaces the point clouds that are in the same Voxel with their average to reduce the number of points. -Code Link -========== +API +===== .. autofunction:: Mapping.point_cloud_sampling.point_cloud_sampling.voxel_point_sampling @@ -61,8 +61,8 @@ Although this method does not have good performance comparing the Farthest distance sample where each point is distributed farther from each other, this is suitable for real-time processing because of its fast computation time. -Code Link -========== +API +===== .. autofunction:: Mapping.point_cloud_sampling.point_cloud_sampling.poisson_disk_sampling diff --git a/_sources/modules/mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst.txt b/_sources/modules/mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst.txt new file mode 100644 index 0000000000..cc5a1a1c5b --- /dev/null +++ b/_sources/modules/mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst.txt @@ -0,0 +1,6 @@ +Ray casting grid map +-------------------- + +This is a 2D ray casting grid mapping example. + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/raycasting_grid_map/animation.gif \ No newline at end of file diff --git a/docs/modules/3_mapping/rectangle_fitting/rectangle_fitting_main.rst b/_sources/modules/mapping/rectangle_fitting/rectangle_fitting_main.rst.txt similarity index 96% rename from docs/modules/3_mapping/rectangle_fitting/rectangle_fitting_main.rst rename to _sources/modules/mapping/rectangle_fitting/rectangle_fitting_main.rst.txt index 06d85efe61..50a50141b2 100644 --- a/docs/modules/3_mapping/rectangle_fitting/rectangle_fitting_main.rst +++ b/_sources/modules/mapping/rectangle_fitting/rectangle_fitting_main.rst.txt @@ -7,7 +7,7 @@ This is an object shape recognition using rectangle fitting. This example code is based on this paper algorithm: -- `Efficient L\-Shape Fitting for Vehicle Detection Using Laser Scanners \- The Robotics Institute Carnegie Mellon University `_ +- `Efficient L\-Shape Fitting for Vehicle Detection Using Laser Scanners \- The Robotics Institute Carnegie Mellon University `_ The algorithm consists of 2 steps as below. @@ -57,8 +57,8 @@ This evaluation function uses the squreed distances between the edges of the rec Calculating the squared error is the same as calculating the variance. The smaller this variance, the more it signifies that the points fit within the rectangle. -Code Link -~~~~~~~~~~~ +API +~~~~~~ .. autoclass:: Mapping.rectangle_fitting.rectangle_fitting.LShapeFitting :members: @@ -66,4 +66,4 @@ Code Link References ~~~~~~~~~~ -- `Efficient L\-Shape Fitting for Vehicle Detection Using Laser Scanners \- The Robotics Institute Carnegie Mellon University `_ +- `Efficient L\-Shape Fitting for Vehicle Detection Using Laser Scanners \- The Robotics Institute Carnegie Mellon University `_ diff --git a/docs/modules/5_path_planning/bezier_path/bezier_path_main.rst b/_sources/modules/path_planning/bezier_path/bezier_path_main.rst.txt similarity index 58% rename from docs/modules/5_path_planning/bezier_path/bezier_path_main.rst rename to _sources/modules/path_planning/bezier_path/bezier_path_main.rst.txt index 19fb89a1b1..fbba6a4496 100644 --- a/docs/modules/5_path_planning/bezier_path/bezier_path_main.rst +++ b/_sources/modules/path_planning/bezier_path/bezier_path_main.rst.txt @@ -13,15 +13,8 @@ You can get different Beizer course: .. image:: Figure_2.png -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.BezierPath.bezier_path.calc_4points_bezier_path - - -Reference -~~~~~~~~~~~~~~~ +Ref: - `Continuous Curvature Path Generation Based on Bezier Curves for Autonomous - Vehicles `__ + Vehicles `__ diff --git a/docs/modules/5_path_planning/bspline_path/bspline_path_main.rst b/_sources/modules/path_planning/bspline_path/bspline_path_main.rst.txt similarity index 99% rename from docs/modules/5_path_planning/bspline_path/bspline_path_main.rst rename to _sources/modules/path_planning/bspline_path/bspline_path_main.rst.txt index 00e5ef2fdb..e734352e34 100644 --- a/docs/modules/5_path_planning/bspline_path/bspline_path_main.rst +++ b/_sources/modules/path_planning/bspline_path/bspline_path_main.rst.txt @@ -105,8 +105,8 @@ The default spline degree is 3, so curvature changes smoothly. .. image:: interp_and_curvature.png -Code link -++++++++++ +API +++++ .. autofunction:: PathPlanning.BSplinePath.bspline_path.interpolate_b_spline_path @@ -133,8 +133,8 @@ The default spline degree is 3, so curvature changes smoothly. .. image:: approx_and_curvature.png -Code Link -++++++++++ +API +++++ .. autofunction:: PathPlanning.BSplinePath.bspline_path.approximate_b_spline_path diff --git a/_sources/modules/path_planning/bugplanner/bugplanner_main.rst.txt b/_sources/modules/path_planning/bugplanner/bugplanner_main.rst.txt new file mode 100644 index 0000000000..72cc46709f --- /dev/null +++ b/_sources/modules/path_planning/bugplanner/bugplanner_main.rst.txt @@ -0,0 +1,8 @@ +Bug planner +----------- + +This is a 2D planning with Bug algorithm. + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/BugPlanner/animation.gif + +- `ECE452 Bug Algorithms `_ diff --git a/docs/modules/5_path_planning/clothoid_path/clothoid_path_main.rst b/_sources/modules/path_planning/clothoid_path/clothoid_path_main.rst.txt similarity index 95% rename from docs/modules/5_path_planning/clothoid_path/clothoid_path_main.rst rename to _sources/modules/path_planning/clothoid_path/clothoid_path_main.rst.txt index 16d0ec03c1..1e8cee694a 100644 --- a/docs/modules/5_path_planning/clothoid_path/clothoid_path_main.rst +++ b/_sources/modules/path_planning/clothoid_path/clothoid_path_main.rst.txt @@ -73,11 +73,6 @@ The final clothoid path can be calculated with the path parameters and Fresnel i &y(s)=y_{0}+\int_{0}^{s} \sin \left(\frac{1}{2} \kappa^{\prime} \tau^{2}+\kappa \tau+\vartheta_{0}\right) \mathrm{d} \tau \end{aligned} -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.ClothoidPath.clothoid_path_planner.generate_clothoid_path - References ~~~~~~~~~~ diff --git a/docs/modules/5_path_planning/coverage_path/coverage_path_main.rst b/_sources/modules/path_planning/coverage_path/coverage_path_main.rst.txt similarity index 73% rename from docs/modules/5_path_planning/coverage_path/coverage_path_main.rst rename to _sources/modules/path_planning/coverage_path/coverage_path_main.rst.txt index eaa876c80b..828342a294 100644 --- a/docs/modules/5_path_planning/coverage_path/coverage_path_main.rst +++ b/_sources/modules/path_planning/coverage_path/coverage_path_main.rst.txt @@ -8,11 +8,6 @@ This is a 2D grid based sweep coverage path planner simulation: .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/GridBasedSweepCPP/animation.gif -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.GridBasedSweepCPP.grid_based_sweep_coverage_path_planner.planning - Spiral Spanning Tree ~~~~~~~~~~~~~~~~~~~~ @@ -22,14 +17,6 @@ This is a 2D grid based spiral spanning tree coverage path planner simulation: .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/SpiralSpanningTreeCPP/animation2.gif .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/SpiralSpanningTreeCPP/animation3.gif -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.SpiralSpanningTreeCPP.spiral_spanning_tree_coverage_path_planner.main - -Reference -+++++++++++++ - - `Spiral-STC: An On-Line Coverage Algorithm of Grid Environments by a Mobile Robot `_ @@ -42,14 +29,6 @@ This is a 2D grid based wavefront coverage path planner simulation: .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/WavefrontCPP/animation2.gif .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/WavefrontCPP/animation3.gif -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.WavefrontCPP.wavefront_coverage_path_planner.wavefront - -Reference -+++++++++++++ - -- `Planning paths of complete coverage of an unstructured environment by a mobile robot `_ +- `Planning paths of complete coverage of an unstructured environment by a mobile robot `_ diff --git a/docs/modules/5_path_planning/cubic_spline/cubic_spline_main.rst b/_sources/modules/path_planning/cubic_spline/cubic_spline_main.rst.txt similarity index 99% rename from docs/modules/5_path_planning/cubic_spline/cubic_spline_main.rst rename to _sources/modules/path_planning/cubic_spline/cubic_spline_main.rst.txt index a110217a2e..33471f7c85 100644 --- a/docs/modules/5_path_planning/cubic_spline/cubic_spline_main.rst +++ b/_sources/modules/path_planning/cubic_spline/cubic_spline_main.rst.txt @@ -171,8 +171,8 @@ the second derivative by: These equations can be calculated by differentiating the cubic polynomial. -Code Link -========== +API +=== This is the 1D cubic spline class API: @@ -199,8 +199,8 @@ Curvature of each point can be also calculated analytically by: :math:`\kappa=\frac{y^{\prime \prime} x^{\prime}-x^{\prime \prime} y^{\prime}}{\left(x^{\prime2}+y^{\prime2}\right)^{\frac{2}{3}}}` -Code Link -========== +API +=== .. autoclass:: PathPlanning.CubicSpline.cubic_spline_planner.CubicSpline2D :members: diff --git a/docs/modules/5_path_planning/dubins_path/dubins_path_main.rst b/_sources/modules/path_planning/dubins_path/dubins_path_main.rst.txt similarity index 97% rename from docs/modules/5_path_planning/dubins_path/dubins_path_main.rst rename to _sources/modules/path_planning/dubins_path/dubins_path_main.rst.txt index 5a3d14464b..516638d83d 100644 --- a/docs/modules/5_path_planning/dubins_path/dubins_path_main.rst +++ b/_sources/modules/path_planning/dubins_path/dubins_path_main.rst.txt @@ -62,7 +62,7 @@ You can generate a path from these information and the maximum curvature informa A path type which has minimum course length among 6 types is selected, and then a path is constructed based on the selected type and its distances. -Code Link +API ~~~~~~~~~~~~~~~~~~~~ .. autofunction:: PathPlanning.DubinsPath.dubins_path_planner.plan_dubins_path @@ -72,5 +72,5 @@ Reference ~~~~~~~~~~~~~~~~~~~~ - `On Curves of Minimal Length with a Constraint on Average Curvature, and with Prescribed Initial and Terminal Positions and Tangents `__ - `Dubins path - Wikipedia `__ -- `15.3.1 Dubins Curves `__ +- `15.3.1 Dubins Curves `__ - `A Comprehensive, Step-by-Step Tutorial to Computing Dubin’s Paths `__ diff --git a/docs/modules/5_path_planning/dynamic_window_approach/dynamic_window_approach_main.rst b/_sources/modules/path_planning/dynamic_window_approach/dynamic_window_approach_main.rst.txt similarity index 74% rename from docs/modules/5_path_planning/dynamic_window_approach/dynamic_window_approach_main.rst rename to _sources/modules/path_planning/dynamic_window_approach/dynamic_window_approach_main.rst.txt index ac5322df94..d645838597 100644 --- a/docs/modules/5_path_planning/dynamic_window_approach/dynamic_window_approach_main.rst +++ b/_sources/modules/path_planning/dynamic_window_approach/dynamic_window_approach_main.rst.txt @@ -5,17 +5,7 @@ Dynamic Window Approach This is a 2D navigation sample code with Dynamic Window Approach. -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DynamicWindowApproach/animation.gif - -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.DynamicWindowApproach.dynamic_window_approach.dwa_control - - -Reference -~~~~~~~~~~~~ - - `The Dynamic Window Approach to Collision Avoidance `__ +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DynamicWindowApproach/animation.gif diff --git a/docs/modules/5_path_planning/eta3_spline/eta3_spline_main.rst b/_sources/modules/path_planning/eta3_spline/eta3_spline_main.rst.txt similarity index 71% rename from docs/modules/5_path_planning/eta3_spline/eta3_spline_main.rst rename to _sources/modules/path_planning/eta3_spline/eta3_spline_main.rst.txt index 9ee343e8a7..ffc3cc6451 100644 --- a/docs/modules/5_path_planning/eta3_spline/eta3_spline_main.rst +++ b/_sources/modules/path_planning/eta3_spline/eta3_spline_main.rst.txt @@ -7,14 +7,7 @@ Eta^3 Spline path planning This is a path planning with Eta^3 spline. -Code Link -~~~~~~~~~~~~~~~ - -.. autoclass:: PathPlanning.Eta3SplineTrajectory.eta3_spline_trajectory.Eta3SplineTrajectory - - -Reference -~~~~~~~~~~~~~~~ +Ref: - `\\eta^3-Splines for the Smooth Path Generation of Wheeled Mobile Robots `__ diff --git a/_sources/modules/path_planning/frenet_frame_path/frenet_frame_path_main.rst.txt b/_sources/modules/path_planning/frenet_frame_path/frenet_frame_path_main.rst.txt new file mode 100644 index 0000000000..e9d9041e0e --- /dev/null +++ b/_sources/modules/path_planning/frenet_frame_path/frenet_frame_path_main.rst.txt @@ -0,0 +1,20 @@ +Optimal Trajectory in a Frenet Frame +------------------------------------ + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/animation.gif + +This is optimal trajectory generation in a Frenet Frame. + +The cyan line is the target course and black crosses are obstacles. + +The red line is predicted path. + +Ref: + +- `Optimal Trajectory Generation for Dynamic Street Scenarios in a + Frenet + Frame `__ + +- `Optimal trajectory generation for dynamic street scenarios in a + Frenet Frame `__ + diff --git a/docs/modules/5_path_planning/grid_base_search/grid_base_search_main.rst b/_sources/modules/path_planning/grid_base_search/grid_base_search_main.rst.txt similarity index 75% rename from docs/modules/5_path_planning/grid_base_search/grid_base_search_main.rst rename to _sources/modules/path_planning/grid_base_search/grid_base_search_main.rst.txt index c4aa6882aa..1644ed00cc 100644 --- a/docs/modules/5_path_planning/grid_base_search/grid_base_search_main.rst +++ b/_sources/modules/path_planning/grid_base_search/grid_base_search_main.rst.txt @@ -10,12 +10,6 @@ This is a 2D grid based path planning with Breadth first search algorithm. In the animation, cyan points are searched nodes. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.BreadthFirstSearch.breadth_first_search.BreadthFirstSearchPlanner - - Depth First Search ~~~~~~~~~~~~~~~~~~~~ @@ -25,12 +19,6 @@ This is a 2D grid based path planning with Depth first search algorithm. In the animation, cyan points are searched nodes. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.DepthFirstSearch.depth_first_search.DepthFirstSearchPlanner - - .. _dijkstra: Dijkstra algorithm @@ -42,12 +30,6 @@ This is a 2D grid based shortest path planning with Dijkstra's algorithm. In the animation, cyan points are searched nodes. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.Dijkstra.dijkstra.DijkstraPlanner - - .. _a*-algorithm: A\* algorithm @@ -61,12 +43,6 @@ In the animation, cyan points are searched nodes. Its heuristic is 2D Euclid distance. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.AStar.a_star.AStarPlanner - - Bidirectional A\* algorithm ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -76,12 +52,6 @@ This is a 2D grid based shortest path planning with bidirectional A star algorit In the animation, cyan points are searched nodes. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.BidirectionalAStar.bidirectional_a_star.BidirectionalAStarPlanner - - .. _D*-algorithm: D\* algorithm @@ -93,14 +63,7 @@ This is a 2D grid based shortest path planning with D star algorithm. The animation shows a robot finding its path avoiding an obstacle using the D* search algorithm. -Code Link -+++++++++++++ - -.. autoclass:: PathPlanning.DStar.dstar.Dstar - - -Reference -++++++++++++ +Ref: - `D* search Wikipedia `__ @@ -111,13 +74,7 @@ This is a 2D grid based path planning and replanning with D star lite algorithm. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DStarLite/animation.gif -Code Link -+++++++++++++ - -.. autoclass:: PathPlanning.DStarLite.d_star_lite.DStarLite - -Reference -++++++++++++ +Ref: - `Improved Fast Replanning for Robot Navigation in Unknown Terrain `_ @@ -131,14 +88,7 @@ This is a 2D grid based path planning with Potential Field algorithm. In the animation, the blue heat map shows potential value on each grid. -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.PotentialFieldPlanning.potential_field_planning.potential_field_planning - - -Reference -++++++++++++ +Ref: - `Robotic Motion Planning:Potential Functions `__ diff --git a/docs/modules/5_path_planning/hybridastar/hybridastar_main.rst b/_sources/modules/path_planning/hybridastar/hybridastar_main.rst.txt similarity index 66% rename from docs/modules/5_path_planning/hybridastar/hybridastar_main.rst rename to _sources/modules/path_planning/hybridastar/hybridastar_main.rst.txt index 36f340e0c2..a9876fa3d4 100644 --- a/docs/modules/5_path_planning/hybridastar/hybridastar_main.rst +++ b/_sources/modules/path_planning/hybridastar/hybridastar_main.rst.txt @@ -4,8 +4,3 @@ Hybrid a star This is a simple vehicle model based hybrid A\* path planner. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/HybridAStar/animation.gif - -Code Link -+++++++++++++ - -.. autofunction:: PathPlanning.HybridAStar.hybrid_a_star.hybrid_a_star_planning diff --git a/docs/modules/5_path_planning/lqr_path/lqr_path_main.rst b/_sources/modules/path_planning/lqr_path/lqr_path_main.rst.txt similarity index 73% rename from docs/modules/5_path_planning/lqr_path/lqr_path_main.rst rename to _sources/modules/path_planning/lqr_path/lqr_path_main.rst.txt index 1eb1e4d840..788442ff63 100644 --- a/docs/modules/5_path_planning/lqr_path/lqr_path_main.rst +++ b/_sources/modules/path_planning/lqr_path/lqr_path_main.rst.txt @@ -4,8 +4,3 @@ LQR based path planning A sample code using LQR based path planning for double integrator model. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/LQRPlanner/animation.gif?raw=true - -Code Link -+++++++++++++ - -.. autoclass:: PathPlanning.LQRPlanner.lqr_planner.LQRPlanner diff --git a/docs/modules/5_path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst b/_sources/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst.txt similarity index 70% rename from docs/modules/5_path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst rename to _sources/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst.txt index 76472a6792..0fc997898e 100644 --- a/docs/modules/5_path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst +++ b/_sources/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst.txt @@ -6,12 +6,6 @@ generator. This algorithm is used for state lattice planner. -Code Link -~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.ModelPredictiveTrajectoryGenerator.trajectory_generator.optimize_trajectory - - Path optimization sample ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -22,8 +16,7 @@ Lookup table generation sample .. image:: lookup_table.png -Reference -~~~~~~~~~~~~ +Ref: - `Optimal rough terrain trajectory generation for wheeled mobile - robots `__ + robots `__ diff --git a/docs/modules/5_path_planning/path_planning_main.rst b/_sources/modules/path_planning/path_planning_main.rst.txt similarity index 57% rename from docs/modules/5_path_planning/path_planning_main.rst rename to _sources/modules/path_planning/path_planning_main.rst.txt index 0c84a19c22..a3237f16f1 100644 --- a/docs/modules/5_path_planning/path_planning_main.rst +++ b/_sources/modules/path_planning/path_planning_main.rst.txt @@ -1,10 +1,8 @@ -.. _`Path Planning`: +.. _path_planning: Path Planning ============= -Path planning is the ability of a robot to search feasible and efficient path to the goal. The path has to satisfy some constraints based on the robot’s motion model and obstacle positions, and optimize some objective functions such as time to goal and distance to obstacle. In path planning, dynamic programming based approaches and sampling based approaches are widely used[22]. Fig.5 shows simulation results of potential field path planning and LQRRRT* path planning[27]. - .. toctree:: :maxdepth: 2 :caption: Contents @@ -12,7 +10,6 @@ Path planning is the ability of a robot to search feasible and efficient path to dynamic_window_approach/dynamic_window_approach bugplanner/bugplanner grid_base_search/grid_base_search - time_based_grid_search/time_based_grid_search model_predictive_trajectory_generator/model_predictive_trajectory_generator state_lattice_planner/state_lattice_planner prm_planner/prm_planner @@ -21,7 +18,6 @@ Path planning is the ability of a robot to search feasible and efficient path to rrt/rrt cubic_spline/cubic_spline bspline_path/bspline_path - catmull_rom_spline/catmull_rom_spline clothoid_path/clothoid_path eta3_spline/eta3_spline bezier_path/bezier_path @@ -32,4 +28,3 @@ Path planning is the ability of a robot to search feasible and efficient path to hybridastar/hybridastar frenet_frame_path/frenet_frame_path coverage_path/coverage_path - elastic_bands/elastic_bands \ No newline at end of file diff --git a/docs/modules/5_path_planning/prm_planner/prm_planner_main.rst b/_sources/modules/path_planning/prm_planner/prm_planner_main.rst.txt similarity index 79% rename from docs/modules/5_path_planning/prm_planner/prm_planner_main.rst rename to _sources/modules/path_planning/prm_planner/prm_planner_main.rst.txt index d58d1e2633..0628719176 100644 --- a/docs/modules/5_path_planning/prm_planner/prm_planner_main.rst +++ b/_sources/modules/path_planning/prm_planner/prm_planner_main.rst.txt @@ -13,14 +13,7 @@ Cyan crosses means searched points with Dijkstra method, The red line is the final path of PRM. -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.ProbabilisticRoadMap.probabilistic_road_map.prm_planning - - -Reference -~~~~~~~~~~~ +Ref: - `Probabilistic roadmap - Wikipedia `__ diff --git a/docs/modules/5_path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst b/_sources/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst.txt similarity index 94% rename from docs/modules/5_path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst rename to _sources/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst.txt index c7bc3fb55c..feec345bae 100644 --- a/docs/modules/5_path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst +++ b/_sources/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst.txt @@ -9,11 +9,6 @@ Motion planning with quintic polynomials. It can calculate 2D path, velocity, and acceleration profile based on quintic polynomials. -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.QuinticPolynomialsPlanner.quintic_polynomials_planner.quintic_polynomials_planner - Quintic polynomials for one dimensional robot motion @@ -102,10 +97,10 @@ Each velocity and acceleration boundary condition can be calculated with each or :math:`v_{xe}=v_ecos(\theta_e), v_{ye}=v_esin(\theta_e)` -Reference +References: ~~~~~~~~~~~ - `Local Path Planning And Motion Control For Agv In - Positioning `__ + Positioning `__ diff --git a/_sources/modules/path_planning/reeds_shepp_path/reeds_shepp_path_main.rst.txt b/_sources/modules/path_planning/reeds_shepp_path/reeds_shepp_path_main.rst.txt new file mode 100644 index 0000000000..81e565888e --- /dev/null +++ b/_sources/modules/path_planning/reeds_shepp_path/reeds_shepp_path_main.rst.txt @@ -0,0 +1,17 @@ +Reeds Shepp planning +-------------------- + +A sample code with Reeds Shepp path planning. + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ReedsSheppPath/animation.gif?raw=true + +Ref: + +- `15.3.2 Reeds-Shepp + Curves `__ + +- `optimal paths for a car that goes both forwards and + backwards `__ + +- `ghliu/pyReedsShepp: Implementation of Reeds Shepp + curve. `__ diff --git a/docs/modules/5_path_planning/rrt/rrt_main.rst b/_sources/modules/path_planning/rrt/rrt_main.rst.txt similarity index 74% rename from docs/modules/5_path_planning/rrt/rrt_main.rst rename to _sources/modules/path_planning/rrt/rrt_main.rst.txt index 1bd5497f4c..5a3a30dcff 100644 --- a/docs/modules/5_path_planning/rrt/rrt_main.rst +++ b/_sources/modules/path_planning/rrt/rrt_main.rst.txt @@ -14,12 +14,6 @@ This is a simple path planning code with Rapidly-Exploring Random Trees Black circles are obstacles, green line is a searched tree, red crosses are start and goal positions. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.RRT.rrt.RRT - - .. include:: rrt_star.rst @@ -30,12 +24,6 @@ RRT with dubins path Path planning for a car robot with RRT and dubins path planner. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.RRTDubins.rrt_dubins.RRTDubins - - .. _rrt*-with-dubins-path: RRT\* with dubins path @@ -45,12 +33,6 @@ RRT\* with dubins path Path planning for a car robot with RRT\* and dubins path planner. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.RRTStarDubins.rrt_star_dubins.RRTStarDubins - - .. _rrt*-with-reeds-sheep-path: RRT\* with reeds-sheep path @@ -60,12 +42,6 @@ RRT\* with reeds-sheep path Path planning for a car robot with RRT\* and reeds sheep path planner. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.RRTStarReedsShepp.rrt_star_reeds_shepp.RRTStarReedsShepp - - .. _informed-rrt*: Informed RRT\* @@ -77,18 +53,11 @@ This is a path planning code with Informed RRT*. The cyan ellipse is the heuristic sampling domain of Informed RRT*. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.InformedRRTStar.informed_rrt_star.InformedRRTStar - - -Reference -^^^^^^^^^^ +Ref: - `Informed RRT\*: Optimal Sampling-based Path Planning Focused via Direct Sampling of an Admissible Ellipsoidal - Heuristic `__ + Heuristic `__ .. _batch-informed-rrt*: @@ -99,20 +68,12 @@ Batch Informed RRT\* This is a path planning code with Batch Informed RRT*. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.BatchInformedRRTStar.batch_informed_rrt_star.BITStar - - -Reference -^^^^^^^^^^^ +Ref: - `Batch Informed Trees (BIT*): Sampling-based Optimal Planning via the Heuristically Guided Search of Implicit Random Geometric Graphs `__ - .. _closed-loop-rrt*: Closed Loop RRT\* @@ -126,25 +87,17 @@ In this code, pure-pursuit algorithm is used for steering control, PID is used for speed control. -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.ClosedLoopRRTStar.closed_loop_rrt_star_car.ClosedLoopRRTStar - - -Reference -^^^^^^^^^^^^ +Ref: - `Motion Planning in Complex Environments using Closed-loop - Prediction `__ + Prediction `__ - `Real-time Motion Planning with Applications to Autonomous Urban - Driving `__ + Driving `__ - `[1601.06326] Sampling-based Algorithms for Optimal Motion Planning Using Closed-loop Prediction `__ - .. _lqr-rrt*: LQR-RRT\* @@ -156,17 +109,10 @@ A double integrator motion model is used for LQR local planner. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/LQRRRTStar/animation.gif -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.LQRRRTStar.lqr_rrt_star.LQRRRTStar - - -Reference -~~~~~~~~~~~~~ +Ref: - `LQR-RRT\*: Optimal Sampling-Based Motion Planning with Automatically Derived Extension - Heuristics `__ + Heuristics `__ - `MahanFathi/LQR-RRTstar: LQR-RRT\* method is used for random motion planning of a simple pendulum in its phase plot `__ \ No newline at end of file diff --git a/docs/modules/5_path_planning/state_lattice_planner/state_lattice_planner_main.rst b/_sources/modules/path_planning/state_lattice_planner/state_lattice_planner_main.rst.txt similarity index 61% rename from docs/modules/5_path_planning/state_lattice_planner/state_lattice_planner_main.rst rename to _sources/modules/path_planning/state_lattice_planner/state_lattice_planner_main.rst.txt index 9f8cc0c50f..3ef0c7eca2 100644 --- a/docs/modules/5_path_planning/state_lattice_planner/state_lattice_planner_main.rst +++ b/_sources/modules/path_planning/state_lattice_planner/state_lattice_planner_main.rst.txt @@ -12,39 +12,22 @@ Uniform polar sampling .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/UniformPolarSampling.gif -Code Link -^^^^^^^^^^^^^ - -.. autofunction:: PathPlanning.StateLatticePlanner.state_lattice_planner.calc_uniform_polar_states - - Biased polar sampling ~~~~~~~~~~~~~~~~~~~~~ .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/BiasedPolarSampling.gif -Code Link -^^^^^^^^^^^^^ -.. autofunction:: PathPlanning.StateLatticePlanner.state_lattice_planner.calc_biased_polar_states - - Lane sampling ~~~~~~~~~~~~~ .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/LaneSampling.gif -Code Link -^^^^^^^^^^^^^ -.. autofunction:: PathPlanning.StateLatticePlanner.state_lattice_planner.calc_lane_states - - -Reference -~~~~~~~~~~~~~~~ +Ref: - `Optimal rough terrain trajectory generation for wheeled mobile - robots `__ + robots `__ - `State Space Sampling of Feasible Motions for High-Performance Mobile Robot Navigation in Complex - Environments `__ + Environments `__ diff --git a/docs/modules/5_path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst b/_sources/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst.txt similarity index 94% rename from docs/modules/5_path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst rename to _sources/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst.txt index aac96d6e19..3c9b7c008c 100644 --- a/docs/modules/5_path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst +++ b/_sources/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst.txt @@ -13,11 +13,6 @@ red crosses are visibility nodes, and blue lines area collision free visibility The red line is the final path searched by dijkstra algorithm frm the visibility graphs. -Code Link -~~~~~~~~~~~~ -.. autoclass:: PathPlanning.VisibilityRoadMap.visibility_road_map.VisibilityRoadMap - - Algorithms ~~~~~~~~~~ @@ -69,7 +64,7 @@ The red line is searched path in the figure: You can find the details of Dijkstra algorithm in :ref:`dijkstra`. References -~~~~~~~~~~~~ +^^^^^^^^^^ - `Visibility graph - Wikipedia `_ diff --git a/docs/modules/5_path_planning/vrm_planner/vrm_planner_main.rst b/_sources/modules/path_planning/vrm_planner/vrm_planner_main.rst.txt similarity index 79% rename from docs/modules/5_path_planning/vrm_planner/vrm_planner_main.rst rename to _sources/modules/path_planning/vrm_planner/vrm_planner_main.rst.txt index a9a41aab74..92e729ab29 100644 --- a/docs/modules/5_path_planning/vrm_planner/vrm_planner_main.rst +++ b/_sources/modules/path_planning/vrm_planner/vrm_planner_main.rst.txt @@ -11,13 +11,7 @@ Cyan crosses mean searched points with Dijkstra method, The red line is the final path of Vornoi Road-Map. -Code Link -~~~~~~~~~~~~~~~ -.. autoclass:: PathPlanning.VoronoiRoadMap.voronoi_road_map.VoronoiRoadMapPlanner - - -Reference -~~~~~~~~~~~~ +Ref: - `Robotic Motion Planning `__ diff --git a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst b/_sources/modules/path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst.txt similarity index 92% rename from docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst rename to _sources/modules/path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst.txt index 914a4555ff..a1980ba17e 100644 --- a/docs/modules/6_path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst +++ b/_sources/modules/path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst.txt @@ -17,11 +17,6 @@ Nonlinear Model Predictive Control with C-GMRES .. figure:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/cgmres_nmpc/animation.gif :alt: gif -Code Link -~~~~~~~~~~~~ - -.. autofunction:: PathTracking.cgmres_nmpc.cgmres_nmpc.NMPCControllerCGMRES - Mathematical Formulation ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -65,7 +60,7 @@ Ref - `Shunichi09/nonlinear_control: Implementing the nonlinear model predictive control, sliding mode - control `__ + control `__ - `非線形モデル予測制御におけるCGMRES法をpythonで実装する - Qiita `__ diff --git a/_sources/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst.txt b/_sources/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst.txt new file mode 100644 index 0000000000..68ea9c88b2 --- /dev/null +++ b/_sources/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst.txt @@ -0,0 +1,13 @@ +.. _linearquadratic-regulator-(lqr)-speed-and-steering-control: + +Linear–quadratic regulator (LQR) speed and steering control +----------------------------------------------------------- + +Path tracking simulation with LQR speed and steering control. + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_speed_steer_control/animation.gif + +References: +~~~~~~~~~~~ + +- `Towards fully autonomous driving: Systems and algorithms `__ diff --git a/_sources/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst.txt b/_sources/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst.txt new file mode 100644 index 0000000000..bf6d6b5854 --- /dev/null +++ b/_sources/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst.txt @@ -0,0 +1,14 @@ +.. _linearquadratic-regulator-(lqr)-steering-control: + +Linear–quadratic regulator (LQR) steering control +------------------------------------------------- + +Path tracking simulation with LQR steering control and PID speed +control. + +.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_steer_control/animation.gif + +References: +~~~~~~~~~~~ +- `ApolloAuto/apollo: An open autonomous driving platform `_ + diff --git a/docs/modules/6_path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst b/_sources/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst.txt similarity index 92% rename from docs/modules/6_path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst rename to _sources/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst.txt index 76a6819a46..59a7166674 100644 --- a/docs/modules/6_path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst +++ b/_sources/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst.txt @@ -5,6 +5,13 @@ Model predictive speed and steering control .. figure:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/model_predictive_speed_and_steer_control/animation.gif?raw=true :alt: Model predictive speed and steering control + Model predictive speed and steering control + +code: + +`PythonRobotics/model_predictive_speed_and_steer_control.py at master · +AtsushiSakai/PythonRobotics `__ + This is a path tracking simulation using model predictive control (MPC). The MPC controller controls vehicle speed and steering base on @@ -15,12 +22,6 @@ This code uses cvxpy as an optimization modeling tool. - `Welcome to CVXPY 1.0 — CVXPY 1.0.6 documentation `__ -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.model_predictive_speed_and_steer_control.model_predictive_speed_and_steer_control.iterative_linear_mpc_control - - MPC modeling ~~~~~~~~~~~~ @@ -132,5 +133,5 @@ Reference - `Vehicle Dynamics and Control \| Rajesh Rajamani \| Springer `__ -- `MPC Book - MPC Lab @ - UC-Berkeley `__ +- `MPC Course Material - MPC Lab @ + UC-Berkeley `__ diff --git a/_sources/modules/path_tracking/path_tracking_main.rst.txt b/_sources/modules/path_tracking/path_tracking_main.rst.txt new file mode 100644 index 0000000000..504ba08795 --- /dev/null +++ b/_sources/modules/path_tracking/path_tracking_main.rst.txt @@ -0,0 +1,16 @@ +.. _path_tracking: + +Path Tracking +============= + +.. toctree:: + :maxdepth: 2 + :caption: Contents + + pure_pursuit_tracking/pure_pursuit_tracking + stanley_control/stanley_control + rear_wheel_feedback_control/rear_wheel_feedback_control + lqr_steering_control/lqr_steering_control + lqr_speed_and_steering_control/lqr_speed_and_steering_control + model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control + cgmres_nmpc/cgmres_nmpc diff --git a/docs/modules/6_path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst b/_sources/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst.txt similarity index 80% rename from docs/modules/6_path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst rename to _sources/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst.txt index ff6749bbbe..5c7bcef85f 100644 --- a/docs/modules/6_path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst +++ b/_sources/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst.txt @@ -9,13 +9,7 @@ speed control. The red line is a target course, the green cross means the target point for pure pursuit control, the blue line is the tracking. -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.pure_pursuit.pure_pursuit.pure_pursuit_steer_control - - -Reference +References: ~~~~~~~~~~~ - `A Survey of Motion Planning and Control Techniques for Self-driving diff --git a/docs/modules/6_path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst b/_sources/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst.txt similarity index 72% rename from docs/modules/6_path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst rename to _sources/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst.txt index 56d0db63ad..70875fdc6c 100644 --- a/docs/modules/6_path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst +++ b/_sources/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst.txt @@ -6,13 +6,7 @@ PID speed control. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/rear_wheel_feedback/animation.gif -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.rear_wheel_feedback_control.rear_wheel_feedback_control.rear_wheel_feedback_control - - -Reference +References: ~~~~~~~~~~~ - `A Survey of Motion Planning and Control Techniques for Self-driving Urban Vehicles `__ diff --git a/docs/modules/6_path_tracking/stanley_control/stanley_control_main.rst b/_sources/modules/path_tracking/stanley_control/stanley_control_main.rst.txt similarity index 82% rename from docs/modules/6_path_tracking/stanley_control/stanley_control_main.rst rename to _sources/modules/path_tracking/stanley_control/stanley_control_main.rst.txt index 3c491804f6..fe325b0102 100644 --- a/docs/modules/6_path_tracking/stanley_control/stanley_control_main.rst +++ b/_sources/modules/path_tracking/stanley_control/stanley_control_main.rst.txt @@ -6,13 +6,7 @@ control. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/stanley_controller/animation.gif -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.stanley_control.stanley_control.stanley_control - - -Reference +References: ~~~~~~~~~~~ - `Stanley: The robot that won the DARPA grand diff --git a/docs/modules/4_slam/FastSLAM1/FastSLAM1_main.rst b/_sources/modules/slam/FastSLAM1/FastSLAM1_main.rst.txt similarity index 99% rename from docs/modules/4_slam/FastSLAM1/FastSLAM1_main.rst rename to _sources/modules/slam/FastSLAM1/FastSLAM1_main.rst.txt index b6bafa0982..a2aa521216 100644 --- a/docs/modules/4_slam/FastSLAM1/FastSLAM1_main.rst +++ b/_sources/modules/slam/FastSLAM1/FastSLAM1_main.rst.txt @@ -22,11 +22,6 @@ The red points are particles of FastSLAM. Black points are landmarks, blue crosses are estimated landmark positions by FastSLAM. -Code Link -~~~~~~~~~~~~ - -.. autofunction:: SLAM.FastSLAM1.fast_slam1.fast_slam1 - Introduction ~~~~~~~~~~~~ diff --git a/docs/modules/4_slam/FastSLAM2/FastSLAM2_main.rst b/_sources/modules/slam/FastSLAM2/FastSLAM2_main.rst.txt similarity index 85% rename from docs/modules/4_slam/FastSLAM2/FastSLAM2_main.rst rename to _sources/modules/slam/FastSLAM2/FastSLAM2_main.rst.txt index 59ed3b9f75..9e79b496a3 100644 --- a/docs/modules/4_slam/FastSLAM2/FastSLAM2_main.rst +++ b/_sources/modules/slam/FastSLAM2/FastSLAM2_main.rst.txt @@ -7,12 +7,6 @@ The animation has the same meanings as one of FastSLAM 1.0. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/FastSLAM2/animation.gif -Code Link -~~~~~~~~~~~ - -.. autofunction:: SLAM.FastSLAM2.fast_slam2.fast_slam2 - - References ~~~~~~~~~~ diff --git a/docs/modules/4_slam/ekf_slam/ekf_slam_main.rst b/_sources/modules/slam/ekf_slam/ekf_slam_main.rst.txt similarity index 95% rename from docs/modules/4_slam/ekf_slam/ekf_slam_main.rst rename to _sources/modules/slam/ekf_slam/ekf_slam_main.rst.txt index 3967a7ae4d..70a7d131ae 100644 --- a/docs/modules/4_slam/ekf_slam/ekf_slam_main.rst +++ b/_sources/modules/slam/ekf_slam/ekf_slam_main.rst.txt @@ -21,12 +21,6 @@ This is a simulation of EKF SLAM. - Blue line: ground truth - Red line: EKF SLAM position estimation -Code Link -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autofunction:: SLAM.EKFSLAM.ekf_slam.ekf_slam - - Introduction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -69,27 +63,27 @@ Take care to note the difference between :math:`X` (state) and :math:`x` original author: Atsushi Sakai (@Atsushi_twi) notebook author: Andrew Tu (drewtu2) """ - + import math import numpy as np %matplotlib notebook import matplotlib.pyplot as plt - - + + # EKF state covariance Cx = np.diag([0.5, 0.5, np.deg2rad(30.0)])**2 # Change in covariance - + # Simulation parameter Qsim = np.diag([0.2, np.deg2rad(1.0)])**2 # Sensor Noise Rsim = np.diag([1.0, np.deg2rad(10.0)])**2 # Process Noise - + DT = 0.1 # time tick [s] SIM_TIME = 50.0 # simulation time [s] MAX_RANGE = 20.0 # maximum observation range M_DIST_TH = 2.0 # Threshold of Mahalanobis distance for data association. STATE_SIZE = 3 # State size [x,y,yaw] LM_SIZE = 2 # LM state size [x,y] - + show_animation = True Algorithm Walk through @@ -103,22 +97,23 @@ the estimated state and measurements def ekf_slam(xEst, PEst, u, z): """ - Performs an iteration of EKF SLAM from the available information. - + Performs an iteration of EKF SLAM from the available information. + :param xEst: the belief in last position :param PEst: the uncertainty in last position - :param u: the control function applied to the last position + :param u: the control function applied to the last position :param z: measurements at this step :returns: the next estimated position and associated covariance """ - + S = STATE_SIZE + # Predict - xEst, PEst = predict(xEst, PEst, u) + xEst, PEst, G, Fx = predict(xEst, PEst, u) initP = np.eye(2) - + # Update - xEst, PEst = update(xEst, PEst, z, initP) - + xEst, PEst = update(xEst, PEst, u, z, initP) + return xEst, PEst @@ -175,25 +170,26 @@ the landmarks. def predict(xEst, PEst, u): """ Performs the prediction step of EKF SLAM - + :param xEst: nx1 state vector :param PEst: nxn covariance matrix :param u: 2x1 control vector :returns: predicted state vector, predicted covariance, jacobian of control vector, transition fx """ - G, Fx = jacob_motion(xEst, u) - xEst[0:STATE_SIZE] = motion_model(xEst[0:STATE_SIZE], u) + S = STATE_SIZE + G, Fx = jacob_motion(xEst[0:S], u) + xEst[0:S] = motion_model(xEst[0:S], u) # Fx is an an identity matrix of size (STATE_SIZE) # sigma = G*sigma*G.T + Noise - PEst = G.T @ PEst @ G + Fx.T @ Cx @ Fx - return xEst, PEst + PEst[0:S, 0:S] = G.T @ PEst[0:S, 0:S] @ G + Fx.T @ Cx @ Fx + return xEst, PEst, G, Fx .. code:: ipython3 def motion_model(x, u): """ Computes the motion model based on current state and input function. - + :param x: 3x1 pose estimation :param u: 2x1 control input [v; w] :returns: the resulting state after the control function is applied @@ -201,11 +197,11 @@ the landmarks. F = np.array([[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]) - + B = np.array([[DT * math.cos(x[2, 0]), 0], [DT * math.sin(x[2, 0]), 0], [0.0, DT]]) - + x = (F @ x) + (B @ u) return x @@ -265,21 +261,22 @@ the changing uncertainty. .. code:: ipython3 - def update(xEst, PEst, z, initP): + def update(xEst, PEst, u, z, initP): """ Performs the update step of EKF SLAM - + :param xEst: nx1 the predicted pose of the system and the pose of the landmarks :param PEst: nxn the predicted covariance + :param u: 2x1 the control function :param z: the measurements read at new position :param initP: 2x2 an identity matrix acting as the initial covariance :returns: the updated state and covariance for the system """ for iz in range(len(z[:, 0])): # for each observation minid = search_correspond_LM_ID(xEst, PEst, z[iz, 0:2]) # associate to a known landmark - + nLM = calc_n_LM(xEst) # number of landmarks we currently know about - + if minid == nLM: # Landmark is a NEW landmark print("New LM") # Extend state and covariance matrix @@ -288,14 +285,14 @@ the changing uncertainty. np.hstack((np.zeros((LM_SIZE, len(xEst))), initP)))) xEst = xAug PEst = PAug - + lm = get_LM_Pos_from_state(xEst, minid) y, S, H = calc_innovation(lm, xEst, PEst, z[iz, 0:2], minid) - + K = (PEst @ H.T) @ np.linalg.inv(S) # Calculate Kalman Gain xEst = xEst + (K @ y) PEst = (np.eye(len(xEst)) - (K @ H)) @ PEst - + xEst[2] = pi_2_pi(xEst[2]) return xEst, PEst @@ -305,7 +302,7 @@ the changing uncertainty. def calc_innovation(lm, xEst, PEst, z, LMid): """ Calculates the innovation based on expected position and landmark position - + :param lm: landmark position :param xEst: estimated position/state :param PEst: estimated covariance @@ -318,19 +315,19 @@ the changing uncertainty. zangle = math.atan2(delta[1, 0], delta[0, 0]) - xEst[2, 0] zp = np.array([[math.sqrt(q), pi_2_pi(zangle)]]) # zp is the expected measurement based on xEst and the expected landmark position - + y = (z - zp).T # y = innovation y[1] = pi_2_pi(y[1]) - + H = jacobH(q, delta, xEst, LMid + 1) S = H @ PEst @ H.T + Cx[0:2, 0:2] - + return y, S, H - + def jacobH(q, delta, x, i): """ Calculates the jacobian of the measurement function - + :param q: the range from the system pose to the landmark :param delta: the difference between a landmark position and the estimated system position :param x: the state, including the estimated system position @@ -340,17 +337,17 @@ the changing uncertainty. sq = math.sqrt(q) G = np.array([[-sq * delta[0, 0], - sq * delta[1, 0], 0, sq * delta[0, 0], sq * delta[1, 0]], [delta[1, 0], - delta[0, 0], - q, - delta[1, 0], delta[0, 0]]]) - + G = G / q nLM = calc_n_LM(x) F1 = np.hstack((np.eye(3), np.zeros((3, 2 * nLM)))) F2 = np.hstack((np.zeros((2, 3)), np.zeros((2, 2 * (i - 1))), np.eye(2), np.zeros((2, 2 * nLM - 2 * i)))) - + F = np.vstack((F1, F2)) - + H = G @ F - + return H Observation Step @@ -373,17 +370,17 @@ reckoning and control functions are passed along here as well. :param xd: the current noisy estimate of the system :param u: the current control input :param RFID: the true position of the landmarks - - :returns: Computes the true position, observations, dead reckoning (noisy) position, + + :returns: Computes the true position, observations, dead reckoning (noisy) position, and noisy control function """ xTrue = motion_model(xTrue, u) - + # add noise to gps x-y z = np.zeros((0, 3)) - + for i in range(len(RFID[:, 0])): # Test all beacons, only add the ones we can see (within MAX_RANGE) - + dx = RFID[i, 0] - xTrue[0, 0] dy = RFID[i, 1] - xTrue[1, 0] d = math.sqrt(dx**2 + dy**2) @@ -393,12 +390,12 @@ reckoning and control functions are passed along here as well. anglen = angle + np.random.randn() * Qsim[1, 1] # add noise zi = np.array([dn, anglen, i]) z = np.vstack((z, zi)) - + # add noise to input ud = np.array([[ u[0, 0] + np.random.randn() * Rsim[0, 0], u[1, 0] + np.random.randn() * Rsim[1, 1]]]).T - + xd = motion_model(xd, ud) return xTrue, z, xd, ud @@ -412,30 +409,32 @@ reckoning and control functions are passed along here as well. """ n = int((len(x) - STATE_SIZE) / LM_SIZE) return n - - + + def jacob_motion(x, u): """ - Calculates the jacobian of motion model. - + Calculates the jacobian of motion model. + :param x: The state, including the estimated position of the system :param u: The control function :returns: G: Jacobian Fx: STATE_SIZE x (STATE_SIZE + 2 * num_landmarks) matrix where the left side is an identity matrix """ - + # [eye(3) [0 x y; 0 x y; 0 x y]] Fx = np.hstack((np.eye(STATE_SIZE), np.zeros( (STATE_SIZE, LM_SIZE * calc_n_LM(x))))) - + jF = np.array([[0.0, 0.0, -DT * u[0] * math.sin(x[2, 0])], [0.0, 0.0, DT * u[0] * math.cos(x[2, 0])], [0.0, 0.0, 0.0]],dtype=object) - + G = np.eye(STATE_SIZE) + Fx.T @ jF @ Fx if calc_n_LM(x) > 0: print(Fx.shape) return G, Fx, + + .. code:: ipython3 @@ -443,68 +442,68 @@ reckoning and control functions are passed along here as well. def calc_LM_Pos(x, z): """ Calculates the pose in the world coordinate frame of a landmark at the given measurement. - + :param x: [x; y; theta] :param z: [range; bearing] :returns: [x; y] for given measurement """ zp = np.zeros((2, 1)) - + zp[0, 0] = x[0, 0] + z[0] * math.cos(x[2, 0] + z[1]) zp[1, 0] = x[1, 0] + z[0] * math.sin(x[2, 0] + z[1]) #zp[0, 0] = x[0, 0] + z[0, 0] * math.cos(x[2, 0] + z[0, 1]) #zp[1, 0] = x[1, 0] + z[0, 0] * math.sin(x[2, 0] + z[0, 1]) - + return zp - - + + def get_LM_Pos_from_state(x, ind): """ Returns the position of a given landmark - + :param x: The state containing all landmark positions :param ind: landmark id :returns: The position of the landmark """ lm = x[STATE_SIZE + LM_SIZE * ind: STATE_SIZE + LM_SIZE * (ind + 1), :] - + return lm - - + + def search_correspond_LM_ID(xAug, PAug, zi): """ Landmark association with Mahalanobis distance. - - If this landmark is at least M_DIST_TH units away from all known landmarks, + + If this landmark is at least M_DIST_TH units away from all known landmarks, it is a NEW landmark. - + :param xAug: The estimated state :param PAug: The estimated covariance :param zi: the read measurements of specific landmark :returns: landmark id """ - + nLM = calc_n_LM(xAug) - + mdist = [] - + for i in range(nLM): lm = get_LM_Pos_from_state(xAug, i) y, S, H = calc_innovation(lm, xAug, PAug, zi, i) mdist.append(y.T @ np.linalg.inv(S) @ y) - + mdist.append(M_DIST_TH) # new landmark - + minid = mdist.index(min(mdist)) - + return minid - + def calc_input(): v = 1.0 # [m/s] yawrate = 0.1 # [rad/s] u = np.array([[v, yawrate]]).T return u - + def pi_2_pi(angle): return (angle + math.pi) % (2 * math.pi) - math.pi @@ -512,53 +511,53 @@ reckoning and control functions are passed along here as well. def main(): print(" start!!") - + time = 0.0 - + # RFID positions [x, y] RFID = np.array([[10.0, -2.0], [15.0, 10.0], [3.0, 15.0], [-5.0, 20.0]]) - + # State Vector [x y yaw v]' xEst = np.zeros((STATE_SIZE, 1)) xTrue = np.zeros((STATE_SIZE, 1)) PEst = np.eye(STATE_SIZE) - + xDR = np.zeros((STATE_SIZE, 1)) # Dead reckoning - + # history hxEst = xEst hxTrue = xTrue hxDR = xTrue - + while SIM_TIME >= time: time += DT u = calc_input() - + xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) - + xEst, PEst = ekf_slam(xEst, PEst, ud, z) - + x_state = xEst[0:STATE_SIZE] - + # store data history hxEst = np.hstack((hxEst, x_state)) hxDR = np.hstack((hxDR, xDR)) hxTrue = np.hstack((hxTrue, xTrue)) - + if show_animation: # pragma: no cover plt.cla() - + plt.plot(RFID[:, 0], RFID[:, 1], "*k") plt.plot(xEst[0], xEst[1], ".r") - + # plot landmark for i in range(calc_n_LM(xEst)): plt.plot(xEst[STATE_SIZE + i * 2], xEst[STATE_SIZE + i * 2 + 1], "xg") - + plt.plot(hxTrue[0, :], hxTrue[1, :], "-b") plt.plot(hxDR[0, :], @@ -584,7 +583,9 @@ reckoning and control functions are passed along here as well. .. image:: ekf_slam_1_0.png -Reference +References: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - `PROBABILISTIC ROBOTICS `_ + + diff --git a/docs/modules/4_slam/graph_slam/graph_slam_main.rst b/_sources/modules/slam/graph_slam/graph_slam_main.rst.txt similarity index 84% rename from docs/modules/4_slam/graph_slam/graph_slam_main.rst rename to _sources/modules/slam/graph_slam/graph_slam_main.rst.txt index 2ef17e4179..987eed385c 100644 --- a/docs/modules/4_slam/graph_slam/graph_slam_main.rst +++ b/_sources/modules/slam/graph_slam/graph_slam_main.rst.txt @@ -13,17 +13,11 @@ The black stars are landmarks for graph edge generation. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/GraphBasedSLAM/animation.gif -Code Link -~~~~~~~~~~~~ - -.. autofunction:: SLAM.GraphBasedSLAM.graph_based_slam.graph_based_slam - - .. include:: graphSLAM_doc.rst .. include:: graphSLAM_formulation.rst .. include:: graphSLAM_SE2_example.rst -Reference +References: ~~~~~~~~~~~ - `A Tutorial on Graph-Based SLAM `_ diff --git a/docs/modules/4_slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst b/_sources/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst.txt similarity index 78% rename from docs/modules/4_slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst rename to _sources/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst.txt index 772fe62889..a30b1fc99b 100644 --- a/docs/modules/4_slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst +++ b/_sources/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst.txt @@ -10,13 +10,7 @@ points to points. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/iterative_closest_point/animation.gif -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: SLAM.ICPMatching.icp_matching.icp_matching - - References -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~ - `Introduction to Mobile Robotics: Iterative Closest Point Algorithm `_ diff --git a/_sources/modules/slam/slam_main.rst.txt b/_sources/modules/slam/slam_main.rst.txt new file mode 100644 index 0000000000..86befa6e35 --- /dev/null +++ b/_sources/modules/slam/slam_main.rst.txt @@ -0,0 +1,16 @@ +.. _slam: + +SLAM +==== + +Simultaneous Localization and Mapping(SLAM) examples + +.. toctree:: + :maxdepth: 2 + :caption: Contents + + iterative_closest_point_matching/iterative_closest_point_matching + ekf_slam/ekf_slam + FastSLAM1/FastSLAM1 + FastSLAM2/FastSLAM2 + graph_slam/graph_slam diff --git a/docs/modules/11_utils/plot/plot_main.rst b/_sources/modules/utils/plot/plot_main.rst.txt similarity index 100% rename from docs/modules/11_utils/plot/plot_main.rst rename to _sources/modules/utils/plot/plot_main.rst.txt diff --git a/docs/modules/11_utils/utils_main.rst b/_sources/modules/utils/utils_main.rst.txt similarity index 90% rename from docs/modules/11_utils/utils_main.rst rename to _sources/modules/utils/utils_main.rst.txt index 95c982b077..ff79a26205 100644 --- a/docs/modules/11_utils/utils_main.rst +++ b/_sources/modules/utils/utils_main.rst.txt @@ -1,4 +1,4 @@ -.. _`utils`: +.. _utils: Utilities ========== diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000000..603f6a8798 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,905 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/check-solid.svg b/_static/check-solid.svg new file mode 100644 index 0000000000..92fad4b5c0 --- /dev/null +++ b/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/_static/clipboard.min.js b/_static/clipboard.min.js new file mode 100644 index 0000000000..54b3c46381 --- /dev/null +++ b/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 + + + + diff --git a/_static/copybutton.css b/_static/copybutton.css new file mode 100644 index 0000000000..f1916ec7d1 --- /dev/null +++ b/_static/copybutton.css @@ -0,0 +1,94 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + background-color: #f6f8fa; + color: #57606a; +} + +button.copybtn.success { + border-color: #22863a; + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 0000000000..2ea7ff3e21 --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 0000000000..dbe1aaad79 --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000000..e380325bc6 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000000..6cb6000018 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000000..7059e23142 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000000..f815f63f99 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000000..f2c76e5bda Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000..e9f60ca953 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000..855c845e53 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..35acda2fa1 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000..400014a4b0 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000..4d13fc6040 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000000..88ad05b9ff Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000000..c4e3d804b5 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000000..c6dff51f06 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000000..bb195043cf Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000000..76114bc033 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000000..3404f37e2e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000000..ae1307ff5f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000000..3bf9843328 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000000..0d9ae7e1a4 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_static/custom.css b/_static/custom.css similarity index 100% rename from docs/_static/custom.css rename to _static/custom.css diff --git a/_static/dark_mode_css/custom.css b/_static/dark_mode_css/custom.css new file mode 100644 index 0000000000..989c2ada1e --- /dev/null +++ b/_static/dark_mode_css/custom.css @@ -0,0 +1,77 @@ +.wy-side-nav-search input[type='text'] { + border-radius: 3px; +} + +input[type='color'], +input[type='date'], +input[type='datetime-local'], +input[type='datetime'], +input[type='email'], +input[type='month'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='text'], +input[type='time'], +input[type='url'], +input[type='week'] { + box-shadow: none; +} + +.theme-switcher { + border-radius: 50%; + position: fixed; + right: 1.6em; + bottom: 1.4em; + z-index: 3; + border: none; + height: 2.2em; + width: 2.2em; + background-color: #fcfcfc; + font-size: 20px; + -webkit-box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + color: #404040; + transition: all 0.3s ease-in-out; +} + +.wy-nav-content a, +.wy-nav-content a:visited { + color: #3091d1; +} + +body, +.wy-nav-content-wrap, +.wy-nav-content, +.section, +.highlight, +.rst-content div[class^='highlight'], +.wy-nav-content a, +.btn-neutral, +.btn, +footer, +.wy-nav-side, +.wy-menu-vertical li, +.wy-menu-vertical a, +.wy-side-nav-search .wy-dropdown, +.wy-side-nav-search a, +.wy-side-nav-search input, +html.writer-html4 .rst-content dl:not(.docutils) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + > dt, +.rst-content code, +.rst-content tt, +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + dl:not(.field-list) + > dt, +code, +.rst-content code.xref, +.rst-content tt.xref { + transition: all 0.2s ease-in-out; +} diff --git a/_static/dark_mode_css/dark.css b/_static/dark_mode_css/dark.css new file mode 100644 index 0000000000..fb8802d7e7 --- /dev/null +++ b/_static/dark_mode_css/dark.css @@ -0,0 +1,501 @@ +:root { + --dark-text-color: #c1c1c1; + --dark-link-color: #249ee8; +} + +html[data-theme='dark'] body { + color: #bfbfbf; +} + +html[data-theme='dark'] .wy-nav-content-wrap { + background-color: #101010; +} + +html[data-theme='dark'] .wy-nav-content { + background-color: #141414; +} + +html[data-theme='dark'] .section { + color: var(--dark-text-color); +} + +html[data-theme='dark'] .highlight { + background-color: #17181c; +} + +html[data-theme='dark'] .highlight .nn { + color: var(--dark-text-color); +} + +html[data-theme='dark'] .highlight .nb { + color: #8bb8df; +} + +html[data-theme='dark'] .highlight .kn, +html[data-theme='dark'] .highlight .kc, +html[data-theme='dark'] .highlight .k { + color: #41c2ea; +} + +html[data-theme='dark'] .highlight .s1, +html[data-theme='dark'] .highlight .s2 { + color: #b3e87f; +} + +html[data-theme='dark'] .highlight .nt { + color: #ccb350; +} + +html[data-theme='dark'] .highlight .c1 { + color: #686868; +} + +html[data-theme='dark'] .rst-content div[class^='highlight'] { + border-color: #1a1a1a; +} + +html[data-theme='dark'] .wy-nav-content a, +html[data-theme='dark'] .wy-nav-content a:visited { + color: var(--dark-link-color); +} + +html[data-theme='dark'] .btn-neutral { + background-color: #17181c !important; +} + +html[data-theme='dark'] .btn-neutral:hover { + background-color: #101114 !important; +} + +html[data-theme='dark'] .btn-neutral:visited { + color: #c1c1c1 !important; +} + +html[data-theme='dark'] .btn { + box-shadow: none; +} + +html[data-theme='dark'] footer { + color: #bdbdbd; +} + +html[data-theme='dark'] .wy-nav-side { + background-color: #0d0d0d; +} + +html[data-theme='dark'] .wy-menu-vertical li.current { + background-color: #141414; +} + +html[data-theme='dark'] .wy-menu-vertical li.current > a, +html[data-theme='dark'] .wy-menu-vertical li.on a { + background-color: #141415; + color: var(--dark-text-color); +} + +html[data-theme='dark'] .wy-menu-vertical li.toctree-l1.current > a, +html[data-theme='dark'] .wy-menu-vertical li.current a { + border-color: #0b0c0d; +} + +html[data-theme='dark'] .wy-menu-vertical li.current a { + color: #bbb; +} + +html[data-theme='dark'] .wy-menu-vertical li.current a:hover { + background-color: #222; +} + +html[data-theme='dark'] .wy-menu-vertical a:hover, +html[data-theme='dark'] .wy-menu-vertical li.current > a:hover, +html[data-theme='dark'] .wy-menu-vertical li.on a:hover { + background-color: #1e1e1e; +} + +html[data-theme='dark'] .wy-menu-vertical li.toctree-l2.current > a, +html[data-theme='dark'] + .wy-menu-vertical + li.toctree-l2.current + li.toctree-l3 + > a { + background-color: #18181a; +} + +html[data-theme='dark'] .wy-side-nav-search { + background-color: #0b152d; +} + +html[data-theme='dark'] .wy-side-nav-search .wy-dropdown > a, +html[data-theme='dark'] .wy-side-nav-search > a { + color: #ddd; +} + +html[data-theme='dark'] .wy-side-nav-search input[type='text'] { + border-color: #111; + background-color: #141414; + color: var(--dark-text-color); +} + +html[data-theme='dark'] .theme-switcher { + background-color: #0b0c0d; + color: var(--dark-text-color); +} + +html[data-theme='dark'].writer-html4 .rst-content dl:not(.docutils) > dt, +html[data-theme='dark'].writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + > dt { + background-color: #0b0b0b; + color: #007dce; + border-color: #282828; +} + +html[data-theme='dark'] .rst-content code, +html[data-theme='dark'] .rst-content tt { + color: var(--dark-text-color); +} + +html[data-theme='dark'].writer-html4 + .rst-content + dl:not(.docutils) + dl:not(.field-list) + > dt, +html[data-theme='dark'].writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + dl:not(.field-list) + > dt { + background-color: #0f0f0f; + color: #959595; + border-color: #2b2b2b; +} + +html[data-theme='dark'] .rst-content code, +html[data-theme='dark'] .rst-content tt, +html[data-theme='dark'] code { + background-color: #2d2d2d; + border-color: #1c1c1c; +} + +html[data-theme='dark'] .rst-content code.xref, +html[data-theme='dark'] .rst-content tt.xref, +html[data-theme='dark'] a .rst-content code, +html[data-theme='dark'] a .rst-content tt { + color: #cecece; +} + +html[data-theme='dark'] .rst-content .hint, +html[data-theme='dark'] .rst-content .important, +html[data-theme='dark'] .rst-content .tip, +html[data-theme='dark'] .rst-content .wy-alert-success.admonition, +html[data-theme='dark'] .rst-content .wy-alert-success.admonition-todo, +html[data-theme='dark'] .rst-content .wy-alert-success.attention, +html[data-theme='dark'] .rst-content .wy-alert-success.caution, +html[data-theme='dark'] .rst-content .wy-alert-success.danger, +html[data-theme='dark'] .rst-content .wy-alert-success.error, +html[data-theme='dark'] .rst-content .wy-alert-success.note, +html[data-theme='dark'] .rst-content .wy-alert-success.seealso, +html[data-theme='dark'] .rst-content .wy-alert-success.warning, +html[data-theme='dark'] .wy-alert.wy-alert-success { + background-color: #00392e; +} + +html[data-theme='dark'] .rst-content .hint .admonition-title, +html[data-theme='dark'] .rst-content .hint .wy-alert-title, +html[data-theme='dark'] .rst-content .important .admonition-title, +html[data-theme='dark'] .rst-content .important .wy-alert-title, +html[data-theme='dark'] .rst-content .tip .admonition-title, +html[data-theme='dark'] .rst-content .tip .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.admonition-todo + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.admonition-todo + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.admonition + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.admonition + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.attention + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.attention + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.caution + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.caution .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-success.danger .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.danger .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-success.error .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.error .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-success.note .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.note .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.seealso + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.seealso .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-success.warning + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-success.warning .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert.wy-alert-success + .admonition-title, +html[data-theme='dark'] + .wy-alert.wy-alert-success + .rst-content + .admonition-title, +html[data-theme='dark'] .wy-alert.wy-alert-success .wy-alert-title { + background-color: #006a56; +} + +html[data-theme='dark'] .rst-content .note, +html[data-theme='dark'] .rst-content .seealso, +html[data-theme='dark'] .rst-content .wy-alert-info.admonition, +html[data-theme='dark'] .rst-content .wy-alert-info.admonition-todo, +html[data-theme='dark'] .rst-content .wy-alert-info.attention, +html[data-theme='dark'] .rst-content .wy-alert-info.caution, +html[data-theme='dark'] .rst-content .wy-alert-info.danger, +html[data-theme='dark'] .rst-content .wy-alert-info.error, +html[data-theme='dark'] .rst-content .wy-alert-info.hint, +html[data-theme='dark'] .rst-content .wy-alert-info.important, +html[data-theme='dark'] .rst-content .wy-alert-info.tip, +html[data-theme='dark'] .rst-content .wy-alert-info.warning, +html[data-theme='dark'] .wy-alert.wy-alert-info { + background-color: #002c4d; +} + +html[data-theme='dark'] .rst-content .note .admonition-title, +html[data-theme='dark'] .rst-content .note .wy-alert-title, +html[data-theme='dark'] .rst-content .seealso .admonition-title, +html[data-theme='dark'] .rst-content .seealso .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-info.admonition-todo + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-info.admonition-todo + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-info.admonition + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.admonition .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.attention .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.attention .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.caution .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.caution .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.danger .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.danger .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.error .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.error .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.hint .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.hint .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.important .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.important .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.tip .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.tip .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-info.warning .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-info.warning .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert.wy-alert-info .admonition-title, +html[data-theme='dark'] .wy-alert.wy-alert-info .rst-content .admonition-title, +html[data-theme='dark'] .wy-alert.wy-alert-info .wy-alert-title { + background-color: #004a7b; +} + +html[data-theme='dark'] .rst-content .admonition-todo, +html[data-theme='dark'] .rst-content .attention, +html[data-theme='dark'] .rst-content .caution, +html[data-theme='dark'] .rst-content .warning, +html[data-theme='dark'] .rst-content .wy-alert-warning.admonition, +html[data-theme='dark'] .rst-content .wy-alert-warning.danger, +html[data-theme='dark'] .rst-content .wy-alert-warning.error, +html[data-theme='dark'] .rst-content .wy-alert-warning.hint, +html[data-theme='dark'] .rst-content .wy-alert-warning.important, +html[data-theme='dark'] .rst-content .wy-alert-warning.note, +html[data-theme='dark'] .rst-content .wy-alert-warning.seealso, +html[data-theme='dark'] .rst-content .wy-alert-warning.tip, +html[data-theme='dark'] .wy-alert.wy-alert-warning { + background-color: #533500; +} + +html[data-theme='dark'] .rst-content .admonition-todo .admonition-title, +html[data-theme='dark'] .rst-content .admonition-todo .wy-alert-title, +html[data-theme='dark'] .rst-content .attention .admonition-title, +html[data-theme='dark'] .rst-content .attention .wy-alert-title, +html[data-theme='dark'] .rst-content .caution .admonition-title, +html[data-theme='dark'] .rst-content .caution .wy-alert-title, +html[data-theme='dark'] .rst-content .warning .admonition-title, +html[data-theme='dark'] .rst-content .warning .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-warning.admonition + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-warning.admonition + .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.danger .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.danger .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.error .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.error .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.hint .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.hint .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-warning.important + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-warning.important + .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.note .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.note .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-warning.seealso + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.seealso .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.tip .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-warning.tip .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert.wy-alert-warning + .admonition-title, +html[data-theme='dark'] + .wy-alert.wy-alert-warning + .rst-content + .admonition-title, +html[data-theme='dark'] .wy-alert.wy-alert-warning .wy-alert-title { + background-color: #803b00; +} + +html[data-theme='dark'] .rst-content .danger, +html[data-theme='dark'] .rst-content .error, +html[data-theme='dark'] .rst-content .wy-alert-danger.admonition, +html[data-theme='dark'] .rst-content .wy-alert-danger.admonition-todo, +html[data-theme='dark'] .rst-content .wy-alert-danger.attention, +html[data-theme='dark'] .rst-content .wy-alert-danger.caution, +html[data-theme='dark'] .rst-content .wy-alert-danger.hint, +html[data-theme='dark'] .rst-content .wy-alert-danger.important, +html[data-theme='dark'] .rst-content .wy-alert-danger.note, +html[data-theme='dark'] .rst-content .wy-alert-danger.seealso, +html[data-theme='dark'] .rst-content .wy-alert-danger.tip, +html[data-theme='dark'] .rst-content .wy-alert-danger.warning, +html[data-theme='dark'] .wy-alert.wy-alert-danger { + background-color: #82231a; +} + +html[data-theme='dark'] .rst-content .danger .admonition-title, +html[data-theme='dark'] .rst-content .danger .wy-alert-title, +html[data-theme='dark'] .rst-content .error .admonition-title, +html[data-theme='dark'] .rst-content .error .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.admonition-todo + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.admonition-todo + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.admonition + .admonition-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.admonition + .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.attention + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.attention .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.caution .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.caution .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.hint .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.hint .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert-danger.important + .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.important .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.note .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.note .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.seealso .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.seealso .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.tip .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.tip .wy-alert-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.warning .admonition-title, +html[data-theme='dark'] .rst-content .wy-alert-danger.warning .wy-alert-title, +html[data-theme='dark'] + .rst-content + .wy-alert.wy-alert-danger + .admonition-title, +html[data-theme='dark'] + .wy-alert.wy-alert-danger + .rst-content + .admonition-title, +html[data-theme='dark'] .wy-alert.wy-alert-danger .wy-alert-title { + background-color: #b9372b; +} + +html[data-theme='dark'] .wy-nav-top { + background-color: #0b152d; +} + +html[data-theme='dark'] .rst-content table.docutils thead, +html[data-theme='dark'] .rst-content table.field-list thead, +html[data-theme='dark'] .wy-table thead { + color: var(--dark-text-color); +} + +html[data-theme='dark'] + .rst-content + table.docutils:not(.field-list) + tr:nth-child(2n-1) + td, +html[data-theme='dark'] .wy-table-backed, +html[data-theme='dark'] html[data-theme='dark'] .wy-table-odd td, +html[data-theme='dark'] .wy-table-striped tr:nth-child(2n-1) td { + background-color: #181818; +} + +html[data-theme='dark'] .rst-content table.docutils td, +html[data-theme='dark'] .wy-table-bordered-all td, +html[data-theme='dark'].writer-html5 .rst-content table.docutils th, +html[data-theme='dark'] .rst-content table.docutils, +html[data-theme='dark'] .wy-table-bordered-all { + border-color: #262626; +} + +html[data-theme='dark'] .rst-content table.docutils caption, +html[data-theme='dark'] .rst-content table.field-list caption, +html[data-theme='dark'] .wy-table caption { + color: var(--dark-text-color); +} + +html[data-theme='dark'] .wy-menu-vertical li.toctree-l3.current > a, +html[data-theme='dark'] + .wy-menu-vertical + li.toctree-l3.current + li.toctree-l4 + > a { + background-color: #18181a; +} diff --git a/_static/dark_mode_css/general.css b/_static/dark_mode_css/general.css new file mode 100644 index 0000000000..aa614f8178 --- /dev/null +++ b/_static/dark_mode_css/general.css @@ -0,0 +1,68 @@ +input[type='color'], +input[type='date'], +input[type='datetime-local'], +input[type='datetime'], +input[type='email'], +input[type='month'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='text'], +input[type='time'], +input[type='url'], +input[type='week'] { + box-shadow: none; +} + +.theme-switcher { + border-radius: 50%; + position: fixed; + right: 1.6em; + bottom: 1.4em; + z-index: 3; + border: none; + height: 2.2em; + width: 2.2em; + background-color: #fcfcfc; + font-size: 20px; + -webkit-box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + color: #404040; + transition: all 0.3s ease-in-out; +} + +body, +.wy-nav-content-wrap, +.wy-nav-content, +.section, +.highlight, +.rst-content div[class^='highlight'], +.wy-nav-content a, +.btn-neutral, +.btn, +footer, +.wy-nav-side, +.wy-menu-vertical li, +.wy-menu-vertical a, +.wy-side-nav-search .wy-dropdown, +.wy-side-nav-search a, +.wy-side-nav-search input, +html.writer-html4 .rst-content dl:not(.docutils) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + > dt, +.rst-content code, +.rst-content tt, +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + dl:not(.field-list) + > dt, +code, +.rst-content code.xref, +.rst-content tt.xref { + transition: all 0.2s ease-in-out; +} diff --git a/_static/dark_mode_js/default_dark.js b/_static/dark_mode_js/default_dark.js new file mode 100644 index 0000000000..ea63e07258 --- /dev/null +++ b/_static/dark_mode_js/default_dark.js @@ -0,0 +1,13 @@ +const loadTheme = () => { + let theme = localStorage.getItem('theme'); + + if (theme !== null) { + if (theme === 'dark') + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + localStorage.setItem('theme', 'dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + } +}; + +loadTheme(); diff --git a/_static/dark_mode_js/default_light.js b/_static/dark_mode_js/default_light.js new file mode 100644 index 0000000000..2b19f92eab --- /dev/null +++ b/_static/dark_mode_js/default_light.js @@ -0,0 +1,13 @@ +const loadTheme = () => { + let theme = localStorage.getItem('theme'); + + if (theme !== null) { + if (theme === 'dark') + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + localStorage.setItem('theme', 'light'); + document.documentElement.setAttribute('data-theme', 'light'); + } +}; + +loadTheme(); diff --git a/_static/dark_mode_js/theme_switcher.js b/_static/dark_mode_js/theme_switcher.js new file mode 100644 index 0000000000..860bd5d7e0 --- /dev/null +++ b/_static/dark_mode_js/theme_switcher.js @@ -0,0 +1,39 @@ +const createThemeSwitcher = () => { + let btn = document.createElement('BUTTON'); + btn.className = 'theme-switcher'; + btn.id = 'themeSwitcher'; + btn.innerHTML = + ''; + document.body.appendChild(btn); + + if (localStorage.getItem('theme') === 'dark') $('#themeMoon').hide(0); + else $('#themeSun').hide(0); +}; + +$(document).ready(() => { + createThemeSwitcher(); + $('#themeSwitcher').click(switchTheme); + + $('footer').html( + $('footer').html() + + 'Dark theme provided by MrDogeBro.' + ); +}); + +const switchTheme = () => { + if (localStorage.getItem('theme') === 'dark') { + localStorage.setItem('theme', 'light'); + document.documentElement.setAttribute('data-theme', 'light'); + + $('#themeSun').fadeOut(200, () => { + $('#themeMoon').fadeIn(200); + }); + } else { + localStorage.setItem('theme', 'dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + + $('#themeMoon').fadeOut(200, () => { + $('#themeSun').fadeIn(200); + }); + } +}; diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000000..8cbf1b161a --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,323 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000000..4daa6b50bd --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000..a858a410e4 Binary files /dev/null and b/_static/file.png differ diff --git a/icon.png b/_static/icon.png similarity index 100% rename from icon.png rename to _static/icon.png diff --git a/_static/img/doc_ci.png b/_static/img/doc_ci.png new file mode 100644 index 0000000000..a9a73f7e92 Binary files /dev/null and b/_static/img/doc_ci.png differ diff --git a/_static/jquery-3.5.1.js b/_static/jquery-3.5.1.js new file mode 100644 index 0000000000..50937333b9 --- /dev/null +++ b/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " - - - -{% endblock %} diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index eeabab11b1..0000000000 --- a/docs/conf.py +++ /dev/null @@ -1,230 +0,0 @@ -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# https://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -from pathlib import Path - -sys.path.insert(0, os.path.abspath('../')) - - -# -- Project information ----------------------------------------------------- - -project = 'PythonRobotics' -copyright = '2018-now, Atsushi Sakai' -author = 'Atsushi Sakai' - -# The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = '' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'matplotlib.sphinxext.plot_directive', - 'sphinx.ext.autodoc', - 'sphinx.ext.mathjax', - 'sphinx.ext.linkcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.imgconverter', - 'IPython.sphinxext.ipython_console_highlighting', - 'sphinx_copybutton', - 'sphinx_rtd_dark_mode', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '_main.rst' - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -# Fix for read the docs -on_rtd = os.environ.get('READTHEDOCS') == 'True' -if on_rtd: - html_theme = 'default' -else: - html_theme = 'sphinx_rtd_light_them' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_logo = '../icon.png' -html_theme_options = { - 'display_version': False, -} - -# replace "view page source" with "edit on github" in Read The Docs theme -# * https://github.com/readthedocs/sphinx_rtd_theme/issues/529 -html_context = { - 'display_github': True, - 'github_user': 'AtsushiSakai', - 'github_repo': 'PythonRobotics', - 'github_version': 'master', - "conf_py_path": "/docs/", - "source_suffix": source_suffix, -} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -html_css_files = ['custom.css'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PythonRoboticsdoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'PythonRobotics.tex', 'PythonRobotics Documentation', - 'Atsushi Sakai', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pythonrobotics', 'PythonRobotics Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'PythonRobotics', 'PythonRobotics Documentation', - author, 'PythonRobotics', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- linkcode setting ------------------------------------------------- - -import inspect -import os -import sys -import functools - -GITHUB_REPO = "https://github.com/AtsushiSakai/PythonRobotics" -GITHUB_BRANCH = "master" - - -def linkcode_resolve(domain, info): - if domain != "py": - return None - - modname = info["module"] - fullname = info["fullname"] - - try: - module = __import__(modname, fromlist=[fullname]) - obj = functools.reduce(getattr, fullname.split("."), module) - except (ImportError, AttributeError): - return None - - try: - srcfile = inspect.getsourcefile(obj) - srcfile = get_relative_path_from_parent(srcfile, "PythonRobotics") - lineno = inspect.getsourcelines(obj)[1] - except Exception: - return None - - return f"{GITHUB_REPO}/blob/{GITHUB_BRANCH}/{srcfile}#L{lineno}" - - -def get_relative_path_from_parent(file_path: str, parent_dir: str): - path = Path(file_path).resolve() - - try: - parent_path = next(p for p in path.parents if p.name == parent_dir) - return str(path.relative_to(parent_path)) - except StopIteration: - raise ValueError(f"Parent directory '{parent_dir}' not found in {file_path}") diff --git a/docs/doc_requirements.txt b/docs/doc_requirements.txt deleted file mode 100644 index b29f4ba289..0000000000 --- a/docs/doc_requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx == 7.2.6 # For sphinx documentation -sphinx_rtd_theme == 2.0.0 -IPython == 8.20.0 # For sphinx documentation -sphinxcontrib-napoleon == 0.7 # For auto doc -sphinx-copybutton -sphinx-rtd-dark-mode diff --git a/docs/index_main.rst b/docs/index_main.rst deleted file mode 100644 index 65634f32e8..0000000000 --- a/docs/index_main.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. PythonRobotics documentation master file, created by - sphinx-quickstart on Sat Sep 15 13:15:55 2018. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to PythonRobotics's documentation! -========================================== - -"PythonRobotics" is a Python code collections and textbook -(This document) for robotics algorithm, which is developed on `GitHub`_. - -See this section (:ref:`What is PythonRobotics?`) for more details of this project. - -This project is `the one of the most popular open-source software (OSS) in -the field of robotics on GitHub`_. -This is `user comments about this project`_, and -this graph shows GitHub star history of this project: - -.. image:: https://api.star-history.com/svg?repos=AtsushiSakai/PythonRobotics&type=Date - :alt: Star History - :width: 80% - :align: center - - -.. _GitHub: https://github.com/AtsushiSakai/PythonRobotics -.. _`user comments about this project`: https://github.com/AtsushiSakai/PythonRobotics/blob/master/users_comments.md -.. _`MIT license`: https://github.com/AtsushiSakai/PythonRobotics/blob/master/LICENSE -.. _`the one of the most popular open-source software (OSS) in the field of robotics on GitHub`: https://github.com/topics/robotics - ----------------------------------- - -.. toctree:: - :maxdepth: 2 - :caption: Table of Contents - - modules/0_getting_started/0_getting_started - modules/1_introduction/introduction - modules/2_localization/localization - modules/3_mapping/mapping - modules/4_slam/slam - modules/5_path_planning/path_planning - modules/6_path_tracking/path_tracking - modules/7_arm_navigation/arm_navigation - modules/8_aerial_navigation/aerial_navigation - modules/9_bipedal/bipedal - modules/10_inverted_pendulum/inverted_pendulum - modules/13_mission_planning/mission_planning - modules/11_utils/utils - modules/12_appendix/appendix - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 07dcebea41..0000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=PythonRobotics - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/modules/0_getting_started/0_getting_started_main.rst b/docs/modules/0_getting_started/0_getting_started_main.rst deleted file mode 100644 index cb2cba4784..0000000000 --- a/docs/modules/0_getting_started/0_getting_started_main.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _`getting started`: - -Getting Started -=============== - -.. toctree:: - :maxdepth: 2 - :caption: Contents - - 1_what_is_python_robotics - 2_how_to_run_sample_codes - 3_how_to_contribute - 4_how_to_read_textbook diff --git a/docs/modules/0_getting_started/1_what_is_python_robotics_main.rst b/docs/modules/0_getting_started/1_what_is_python_robotics_main.rst deleted file mode 100644 index 2a7bd574f0..0000000000 --- a/docs/modules/0_getting_started/1_what_is_python_robotics_main.rst +++ /dev/null @@ -1,130 +0,0 @@ -.. _`What is PythonRobotics?`: - -What is PythonRobotics? ------------------------- - -This is an Open Source Software (OSS) project: PythonRobotics, which is a Python code collection of robotics algorithms. -These codes are developed under `MIT license`_ and on `GitHub`_. - -.. _GitHub: https://github.com/AtsushiSakai/PythonRobotics -.. _`MIT license`: https://github.com/AtsushiSakai/PythonRobotics/blob/master/LICENSE - -This project has three main philosophies below: - -Philosophy 1. Easy to understand each algorithm's basic idea. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The goal is for beginners in robotics to understand the basic ideas behind each algorithm. - -`Python`_ programming language is adopted in this project to achieve the goal. -Python is a high-level programming language that is easy to read and write. -If the code is not easy to read, it would be difficult to achieve our goal of -allowing beginners to understand the algorithms. -Python has great libraries for matrix operation, mathematical and scientific operation, -and visualization, which makes code more readable because such operations -don’t need to be re-implemented. -Having the core Python packages allows the user to focus on the algorithms, -rather than the implementations. - -PythonRobotics provides not only the code but also intuitive animations that -visually demonstrate the process and behavior of each algorithm over time. -This is an example of an animation gif file that shows the process of the -path planning in a highway scenario for autonomous vehicle. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/high_speed_and_velocity_keeping_frenet_path.gif - -This animation is a gif file and is created using the `Matplotlib`_ library. -All animation gif files are stored in the `PythonRoboticsGifs`_ repository and -all animation movies are also uploaded to this `YouTube channel`_. - -PythonRobotics also provides a textbook that explains the basic ideas of each algorithm. -The PythonRobotics textbook allows you to learn fundamental algorithms used in -robotics with minimal mathematical formulas and textual explanations, -based on PythonRobotics’ sample codes and animations. -The contents of this document, like the code, are managed in the PythonRobotics -`GitHub`_ repository and converted into HTML-based online documents using `Sphinx`_, -which is a Python-based documentation builder. -Please refer to this section ":ref:`How to read this textbook`" for information on -how to read this document for learning. - - -.. _`Python`: https://www.python.org/ -.. _`PythonRoboticsGifs`: https://github.com/AtsushiSakai/PythonRoboticsGifs -.. _`YouTube channel`: https://youtube.com/playlist?list=PL12URV8HFpCozuz0SDxke6b2ae5UZvIwa&si=AH2fNPPYufPtK20S - - -Philosophy 2. Widely used and practical algorithms are selected. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The second philosophy is that implemented algorithms have to be practical -and widely used in both academia and industry. -We believe learning these algorithms will be useful in many applications. -For example, Kalman filters and particle filter for localization, -grid mapping for mapping, -dynamic programming based approaches and sampling based approaches for path planning, -and optimal control based approach for path tracking. -These algorithms are implemented and explained in the textbook in this project. - -Philosophy 3. Minimum dependency. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each sample code of PythonRobotics is written in Python3 and only depends on -some standard modules for readability and ease to setup and use. - - -.. _`Requirements`: - -Requirements -============ - -PythonRobotics depends only on the following five libraries, -including Python itself, to run each sample code. - -- `Python 3.12.x`_ (for Python runtime) -- `NumPy`_ (for matrix operation) -- `SciPy`_ (for scientific operation) -- `cvxpy`_ (for convex optimization) -- `Matplotlib`_ (for visualization) - -If you only need to run the code, the five libraries mentioned above are sufficient. -However, for code development or creating documentation for the textbook, -the following additional libraries are required. - -- `pytest`_ (for unit tests) -- `pytest-xdist`_ (for parallel unit tests) -- `mypy`_ (for type check) -- `Sphinx`_ (for document generation) -- `ruff`_ (for code style check) - -.. _`Python 3.12.x`: https://www.python.org/ -.. _`NumPy`: https://numpy.org/ -.. _`SciPy`: https://scipy.org/ -.. _`Matplotlib`: https://matplotlib.org/ -.. _`cvxpy`: https://www.cvxpy.org/ -.. _`pytest`: https://docs.pytest.org/en/latest/ -.. _`pytest-xdist`: https://github.com/pytest-dev/pytest-xdist -.. _`mypy`: https://mypy-lang.org/ -.. _`Sphinx`: https://www.sphinx-doc.org/en/master/index.html -.. _`ruff`: https://github.com/astral-sh/ruff - -For instructions on installing the above libraries, please refer to -this section ":ref:`How to run sample codes`". - -Audio overview of this project -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For an audio overview of this project, please refer to this `YouTube video`_. - -.. _`YouTube video`: https://www.youtube.com/watch?v=uMeRnNoJAfU - -Arxiv paper -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We have published a paper on this project on Arxiv in 2018. - -See this paper for more details about this Project: - -- `PythonRobotics: a Python code collection of robotics algorithms`_ (`BibTeX`_) - -.. _`PythonRobotics: a Python code collection of robotics algorithms`: https://arxiv.org/abs/1808.10703 -.. _`BibTeX`: https://github.com/AtsushiSakai/PythonRoboticsPaper/blob/master/python_robotics.bib - diff --git a/docs/modules/0_getting_started/2_how_to_run_sample_codes_main.rst b/docs/modules/0_getting_started/2_how_to_run_sample_codes_main.rst deleted file mode 100644 index b92fc9bde0..0000000000 --- a/docs/modules/0_getting_started/2_how_to_run_sample_codes_main.rst +++ /dev/null @@ -1,118 +0,0 @@ -.. _`How to run sample codes`: - -How to run sample codes -------------------------- - -In this chapter, we will explain the setup process for running each sample code -in PythonRobotics and describe the contents of each directory. - -Steps to setup and run sample codes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Install `Python 3.12.x`_ - -Note that older versions of Python3 might work, but we recommend using -the specified version, because the sample codes are only tested with it. - -2. If you prefer to use conda for package management, install -Anaconda or Miniconda to use `conda`_ command. - -3. Clone this repo and go into dir. - -.. code-block:: - - >$ git clone https://github.com/AtsushiSakai/PythonRobotics.git - - >$ cd PythonRobotics - - -4. Install the required libraries. - -We have prepared requirements management files for `conda`_ and `pip`_ under -the requirements directory. Using these files makes it easy to install the necessary libraries. - -using conda : - -.. code-block:: - - >$ conda env create -f requirements/environment.yml - -using pip : - -.. code-block:: - - >$ pip install -r requirements/requirements.txt - - -5. Execute python script in each directory. - -For example, to run the sample code of `Extented Kalman Filter` in the -`localization` directory, execute the following command: - -.. code-block:: - - >$ cd localization/extended_kalman_filter - - >$ python extended_kalman_filter.py - -Then, you can see this animation of the EKF algorithm based localization: - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/extended_kalman_filter/animation.gif - -Please refer to the `Directory structure`_ section for more details on the contents of each directory. - -6. Add star to this repo if you like it 😃. - -.. _`Python 3.12.x`: https://www.python.org/ -.. _`conda`: https://docs.conda.io/projects/conda/en/stable/user-guide/install/index.html -.. _`pip`: https://pip.pypa.io/en/stable/ -.. _`the requirements directory`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/requirements - -.. _`Directory structure`: - -Directory structure -~~~~~~~~~~~~~~~~~~~~ - -The top-level directory structure of PythonRobotics is as follows: - -Sample codes directories: - -- `AerialNavigation`_ : the sample codes of aerial navigation algorithms for drones and rocket landing. -- `ArmNavigation`_ : the sample codes of arm navigation algorithms for robotic arms. -- `Localization`_ : the sample codes of localization algorithms. -- `Bipedal`_ : the sample codes of bipedal walking algorithms for legged robots. -- `Control`_ : the sample codes of control algorithms for robotic systems. -- `Mapping`_ : the sample codes of mapping or obstacle shape recognition algorithms. -- `PathPlanning`_ : the sample codes of path planning algorithms. -- `PathTracking`_ : the sample codes of path tracking algorithms for car like robots. -- `SLAM`_ : the sample codes of SLAM algorithms. - -Other directories: - -- `docs`_ : This directory contains the documentation of PythonRobotics. -- `requirements`_ : This directory contains the requirements management files. -- `tests`_ : This directory contains the unit test files. -- `utils`_ : This directory contains utility functions used in some sample codes in common. -- `.github`_ : This directory contains the GitHub Actions configuration files. -- `.circleci`_ : This directory contains the CircleCI configuration files. - -The structure of this document is the same as that of the sample code -directories mentioned above. -For more details, please refer to the :ref:`How to read this textbook` section. - - -.. _`AerialNavigation`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/AerialNavigation -.. _`ArmNavigation`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/ArmNavigation -.. _`Localization`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/Localization -.. _`Bipedal`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/Bipedal -.. _`Control`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/Control -.. _`Mapping`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/Mapping -.. _`PathPlanning`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/PathPlanning -.. _`PathTracking`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/PathTracking -.. _`SLAM`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/SLAM -.. _`docs`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/docs -.. _`requirements`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/requirements -.. _`tests`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/tests -.. _`utils`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/utils -.. _`.github`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/.github -.. _`.circleci`: https://github.com/AtsushiSakai/PythonRobotics/tree/master/.circleci diff --git a/docs/modules/0_getting_started/4_how_to_read_textbook_main.rst b/docs/modules/0_getting_started/4_how_to_read_textbook_main.rst deleted file mode 100644 index 1625c838af..0000000000 --- a/docs/modules/0_getting_started/4_how_to_read_textbook_main.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _`How to read this textbook`: - -How to read this textbook --------------------------- - -This document is structured to help you learn the fundamental concepts -behind each sample code in PythonRobotics. - -If you already have some knowledge of robotics technologies, you can start -by reading any document that interests you. - -However, if you have no prior knowledge of robotics technologies, it is -recommended that you first read the :ref:`Introduction` section and then proceed -to the documents related to the technical fields that interest you. \ No newline at end of file diff --git a/docs/modules/12_appendix/external_sensors_main.rst b/docs/modules/12_appendix/external_sensors_main.rst deleted file mode 100644 index b7caaf2c3a..0000000000 --- a/docs/modules/12_appendix/external_sensors_main.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _`External Sensors for Robots`: - -External Sensors for Robots -============================ - -This project, `PythonRobotics`, focuses on algorithms, so hardware is not included. -However, having basic knowledge of hardware in robotics is also important for understanding algorithms. -Therefore, we will provide an overview. - -Introduction ------------- - -In recent years, the application of robotic technology has advanced, particularly in areas such as autonomous vehicles and disaster response robots. A crucial element in these technologies is external recognition—the robot's ability to understand its surrounding environment, identify safe zones, and detect moving objects using onboard sensors. Achieving effective external recognition involves various techniques, but equally important is the selection of appropriate sensors. Robots, like the sensors they employ, come in many forms, but external recognition sensors can be broadly categorized into three types. Developing an advanced external recognition system requires a thorough understanding of each sensor's principles and characteristics to determine their optimal application. This article summarizes the principles and features of these sensors for personal study purposes. - -Laser Sensors -------------- - -Laser sensors measure distances by utilizing light, commonly referred to as Light Detection and Ranging (LIDAR). They operate by emitting light towards an object and calculating the distance based on the time it takes for the reflected light to return, using the speed of light as a constant. - -Radar Sensors -------------- - -Radar measures distances using radio waves, commonly referred to as Radio Detection and Ranging (RADAR). It operates by transmitting radio signals towards an object and calculating the distance based on the time it takes for the reflected waves to return, using the speed of radio waves as a constant. - - -Monocular Cameras ------------------ - -Monocular cameras utilize a single camera to recognize the external environment. Compared to other sensors, they can detect color and brightness information, making them primarily useful for object recognition. However, they face challenges in independently measuring distances to surrounding objects and may struggle in low-light or dark conditions. - -Requirements for Cameras and Image Processing in Robotics -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -While camera sensors are widely used in applications like surveillance, deploying them in robotics necessitates meeting specific requirements: - -1. High dynamic range to adapt to various lighting conditions -2. Wide measurement range -3. Capability for three-dimensional measurement through techniques like motion stereo -4. Real-time processing with high frame rates -5. Cost-effectiveness - -Stereo Cameras --------------- - -Stereo cameras employ multiple cameras to measure distances to surrounding objects. By knowing the positions and orientations of each camera and analyzing the disparity in the images (parallax), the distance to a specific point (the object represented by a particular pixel) can be calculated. - -Characteristics of Stereo Cameras -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Advantages of stereo cameras include the ability to obtain high-precision and high-density distance information at close range, depending on factors like camera resolution and the distance between cameras (baseline). This makes them suitable for indoor robots that require precise shape recognition of nearby objects. Additionally, stereo cameras are relatively cost-effective compared to other sensors, leading to their use in consumer products like Subaru's EyeSight system. However, stereo cameras are less effective for long-distance measurements due to a decrease in accuracy proportional to the square of the distance. They are also susceptible to environmental factors such as lighting conditions. - -Ultrasonic Sensors ------------------- - -Ultrasonic sensors are commonly used in indoor robots and some automotive autonomous driving systems. Their features include affordability compared to laser or radar sensors, the ability to detect very close objects, and the capability to sense materials like glass, which may be challenging for lasers or cameras. However, they have limitations such as shorter maximum measurement distances and lower resolution and accuracy. - -References ----------- - -- Wikipedia articles: - - - `Lidar Sensors `_ - - `Radar Sensors `_ - - `Stereo Cameras `_ - - `Ultrasonic Sensors `_ \ No newline at end of file diff --git a/docs/modules/12_appendix/internal_sensors_main.rst b/docs/modules/12_appendix/internal_sensors_main.rst deleted file mode 100644 index fa2594a2bf..0000000000 --- a/docs/modules/12_appendix/internal_sensors_main.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _`Internal Sensors for Robots`: - -Internal Sensors for Robots -============================ - -This project, `PythonRobotics`, focuses on algorithms, so hardware is not included. However, having basic knowledge of hardware in robotics is also important for understanding algorithms. Therefore, we will provide an overview. - -Introduction -------------- - -In robotic systems, internal sensors play a crucial role in monitoring the robot’s internal state, such as orientation, acceleration, angular velocity, altitude, and temperature. These sensors provide essential feedback that supports control, localization, and safety mechanisms. While external sensors perceive the environment, internal sensors give the robot self-awareness of its own motion and condition. Understanding the principles and characteristics of these sensors is vital to fully utilize their information within algorithms and decision-making systems. This section outlines the main internal sensors used in robotics. - -Global Navigation Satellite System (GNSS) ------------------------------------------ - -GNSS is a satellite-based navigation system that provides global positioning and time information by analyzing signals from multiple satellites. It is commonly used in outdoor environments for absolute positioning. Although GNSS offers global coverage without infrastructure dependency, its performance is limited indoors or in obstructed areas, and it suffers from low update rates and susceptibility to signal noise. It is widely used in outdoor navigation for drones, vehicles, and delivery robots. - -Gyroscope ----------- - -A gyroscope measures angular velocity around the robot’s axes, enabling orientation and attitude estimation through detection of the Coriolis effect. Gyroscopes are compact, cost-effective, and provide high update rates, but they are prone to drift and require calibration or sensor fusion for long-term accuracy. These sensors are essential in drones, balancing robots, and IMU-based systems for motion tracking. - -Accelerometer ---------------- - -An accelerometer measures linear acceleration along one or more axes, typically by detecting mass displacement due to motion. It is small, affordable, and useful for detecting movement, tilt, or vibration. However, accelerometers are limited by noisy output and cannot independently determine position without integration and fusion. They are commonly applied in wearable robotics, step counters, and vibration sensing. - -Magnetometer --------------- - -A magnetometer measures the direction and intensity of magnetic fields, enabling heading estimation based on Earth’s magnetic field. It is lightweight and useful for orientation, especially in GPS-denied environments, though it is sensitive to interference from electronics and requires calibration. Magnetometers are often used in conjunction with IMUs for navigation and directional awareness. - -Inertial Measurement Unit (IMU) --------------------------------- - -An IMU integrates a gyroscope, accelerometer, and sometimes a magnetometer to estimate a robot's motion and orientation through sensor fusion techniques such as Kalman filters. IMUs are compact and provide high-frequency data, which is essential for localization and navigation in GPS-denied areas. Nonetheless, they accumulate drift over time and require complex filtering to maintain accuracy. IMUs are found in drones, mobile robots, and motion tracking systems. - -Pressure Sensor ----------------- - -Pressure sensors detect atmospheric or fluid pressure by measuring the force exerted on a diaphragm. They are effective for estimating altitude and monitoring environmental conditions, especially in drones and underwater robots. Although cost-effective and efficient, their accuracy may degrade due to temperature variation and limitations in low-altitude resolution. - -Temperature Sensor --------------------- - -Temperature sensors monitor environmental or internal component temperatures, using changes in resistance or voltage depending on sensor type (e.g., RTD or thermocouple). They are simple and reliable for safety and thermal regulation, though they may respond slowly and be affected by nearby electronics. Common applications include battery and motor monitoring, safety systems, and ambient sensing. - -References ----------- - -- *Introduction to Autonomous Mobile Robots*, Roland Siegwart, Illah Nourbakhsh, Davide Scaramuzza -- *Probabilistic Robotics*, Sebastian Thrun, Wolfram Burgard, Dieter Fox -- Wikipedia articles: - - - `Inertial Measurement Unit (IMU) `_ - - `Accelerometer `_ - - `Gyroscope `_ - - `Magnetometer `_ - - `Pressure sensor `_ - - `Temperature sensor `_ -- `Adafruit Sensor Guides `_ \ No newline at end of file diff --git a/docs/modules/12_appendix/steering_motion_model/steering_model.png b/docs/modules/12_appendix/steering_motion_model/steering_model.png deleted file mode 100644 index c66dded87a..0000000000 Binary files a/docs/modules/12_appendix/steering_motion_model/steering_model.png and /dev/null differ diff --git a/docs/modules/12_appendix/steering_motion_model/turning_radius_calc1.png b/docs/modules/12_appendix/steering_motion_model/turning_radius_calc1.png deleted file mode 100644 index 3de7ed8797..0000000000 Binary files a/docs/modules/12_appendix/steering_motion_model/turning_radius_calc1.png and /dev/null differ diff --git a/docs/modules/12_appendix/steering_motion_model/turning_radius_calc2.png b/docs/modules/12_appendix/steering_motion_model/turning_radius_calc2.png deleted file mode 100644 index f7e776bf40..0000000000 Binary files a/docs/modules/12_appendix/steering_motion_model/turning_radius_calc2.png and /dev/null differ diff --git a/docs/modules/12_appendix/steering_motion_model_main.rst b/docs/modules/12_appendix/steering_motion_model_main.rst deleted file mode 100644 index c697123fa2..0000000000 --- a/docs/modules/12_appendix/steering_motion_model_main.rst +++ /dev/null @@ -1,97 +0,0 @@ - -Steering Motion Model ------------------------ - -Turning radius calculation by steering motion model -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The turning Radius represents the radius of the circle when the robot turns, as shown in the diagram below. - -.. image:: steering_motion_model/steering_model.png - -When the steering angle is tilted by :math:`\delta`, -the turning radius :math:`R` can be calculated using the following equation, -based on the geometric relationship between the wheelbase (WB), -which is the distance between the rear wheel center and the front wheel center, -and the assumption that the turning radius circle passes through the center of -the rear wheels in the diagram above. - -:math:`R = \frac{WB}{tan\delta}` - -The curvature :math:`\kappa` is the reciprocal of the turning radius: - -:math:`\kappa = \frac{tan\delta}{WB}` - -In the diagram above, the angular difference :math:`\Delta \theta` in the vehicle’s heading between two points on the turning radius :math:`R` -is the same as the angle of the vector connecting the two points from the center of the turn. - -From the formula for the length of an arc and the radius, - -:math:`\Delta \theta = \frac{s}{R}` - -Here, :math:`s` is the distance between two points on the turning radius. - -So, yaw rate :math:`\omega` can be calculated as follows. - -:math:`\omega = \frac{v}{R}` - -and - -:math:`\omega = v\kappa` - -here, :math:`v` is the velocity of the vehicle. - - -Turning radius calculation by 2 consecutive positions of the robot trajectory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In this section, we will derive the formula for the turning radius from 2 consecutive positions of the robot trajectory. - -.. image:: steering_motion_model/turning_radius_calc1.png - -As shown in the upper diagram above, the robot moves from a point at time :math:`t` to a point at time :math:`t+1`. -Each point is represented by a 2D position :math:`(x_t, y_t)` and an orientation :math:`\theta_t`. - -The distance between the two points is :math:`d = \sqrt{(x_{t+1} - x_t)^2 + (y_{t+1} - y_t)^2}`. - -The angle between the two vectors from the turning center to the two points is :math:`\theta = \theta_{t+1} - \theta_t`. -Here, by drawing a perpendicular line from the center of the turning radius -to a straight line of length :math:`d` connecting two points, -the following equation can be derived from the resulting right triangle. - -:math:`sin\frac{\theta}{2} = \frac{d}{2R}` - -So, the turning radius :math:`R` can be calculated as follows. - -:math:`R = \frac{d}{2sin\frac{\theta}{2}}` - -The curvature :math:`\kappa` is the reciprocal of the turning radius. -So, the curvature can be calculated as follows. - -:math:`\kappa = \frac{2sin\frac{\theta}{2}}{d}` - -Target speed by maximum steering speed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the maximum steering speed is given as :math:`\dot{\delta}_{max}`, -the maximum curvature change rate :math:`\dot{\kappa}_{max}` can be calculated as follows: - -:math:`\dot{\kappa}_{max} = \frac{tan\dot{\delta}_{max}}{WB}` - -From the curvature calculation by 2 consecutive positions of the robot trajectory, - -the maximum curvature change rate :math:`\dot{\kappa}_{max}` can be calculated as follows: - -:math:`\dot{\kappa}_{max} = \frac{\kappa_{t+1}-\kappa_{t}}{\Delta t}` - -If we can assume that the vehicle will not exceed the maximum curvature change rate, - -the target minimum velocity :math:`v_{min}` can be calculated as follows: - -:math:`v_{min} = \frac{d_{t+1}+d_{t}}{\Delta t} = \frac{d_{t+1}+d_{t}}{(\kappa_{t+1}-\kappa_{t})}\frac{tan\dot{\delta}_{max}}{WB}` - - -Reference -~~~~~~~~~~~ - -- `Vehicle Dynamics and Control `_ diff --git a/docs/modules/13_mission_planning/behavior_tree/behavior_tree_main.rst b/docs/modules/13_mission_planning/behavior_tree/behavior_tree_main.rst deleted file mode 100644 index 22849f7c54..0000000000 --- a/docs/modules/13_mission_planning/behavior_tree/behavior_tree_main.rst +++ /dev/null @@ -1,104 +0,0 @@ -Behavior Tree -------------- - -Behavior Tree is a modular, hierarchical decision model that is widely used in robot control, and game development. -It present some similarities to hierarchical state machines with the key difference that the main building block of a behavior is a task rather than a state. -Behavior Tree have been shown to generalize several other control architectures (https://ieeexplore.ieee.org/document/7790863) - -Code Link -~~~~~~~~~~~~~ - -Control Node -++++++++++++ - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.ControlNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.SequenceNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.SelectorNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.WhileDoElseNode - -Action Node -++++++++++++ - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.ActionNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.EchoNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.SleepNode - -Decorator Node -++++++++++++++ - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.DecoratorNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.InverterNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.TimeoutNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.DelayNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.ForceSuccessNode - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.ForceFailureNode - -Behavior Tree Factory -+++++++++++++++++++++ - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.BehaviorTreeFactory - :members: - -Behavior Tree -+++++++++++++ - -.. autoclass:: MissionPlanning.BehaviorTree.behavior_tree.BehaviorTree - :members: - -Example -~~~~~~~ - -Visualize the behavior tree by `xml-tree-visual `_. - -.. image:: ./robot_behavior_case.svg - -Print the behavior tree - -.. code-block:: text - - Behavior Tree - [Robot Main Controller] - [Battery Management] - (Low Battery Detection) - - - - [Patrol Task] - - [Move to Position A] - - [Obstacle Handling A] - [Obstacle Present] - - - - - [Move to Position B] - (Short Wait) - - - (Limited Time Obstacle Handling) - [Obstacle Present] - - - - [Conditional Move to C] - - [Perform Position C Task] - - (Ensure Completion) - - - - - Behavior Tree diff --git a/docs/modules/13_mission_planning/behavior_tree/robot_behavior_case.svg b/docs/modules/13_mission_planning/behavior_tree/robot_behavior_case.svg deleted file mode 100644 index a3d43aed52..0000000000 --- a/docs/modules/13_mission_planning/behavior_tree/robot_behavior_case.svg +++ /dev/null @@ -1,22 +0,0 @@ -Selectorname: Robot Main ControllerSequencename: Battery ManagementSequencename: Patrol TaskInvertername: Low Battery DetectionEchoname: Low Battery Warningmessage: Battery level low!Charging neededChargeBatteryname: Charge Batterycharge_rate: 20Echoname: Start Taskmessage: Starting patrol taskSequencename: Move to Position ASequencename: Move to Position BWhileDoElsename: Conditional Move to CEchoname: Complete Patrolmessage: Patrol taskcompleted, returning tocharging stationMoveToPositionname: Return to ChargingStationposition: Charging Stationmove_duration: 4CheckBatteryname: Check Batterythreshold: 30MoveToPositionname: Move to Aposition: Amove_duration: 2Selectorname: Obstacle Handling APerformTaskname: Position A Tasktask_name: Check Device Statustask_duration: 2Delayname: Short Waitsec: 1MoveToPositionname: Move to Bposition: Bmove_duration: 3Timeoutname: Limited Time ObstacleHandlingsec: 2PerformTaskname: Position B Tasktask_name: Data Collectiontask_duration: 2.5CheckBatteryname: Check Sufficient Batterythreshold: 50Sequencename: Perform Position C TaskEchoname: Skip Position Cmessage: Insufficient power,skipping position C taskSequencename: Obstacle PresentEchoname: No Obstaclemessage: Path clearEchoname: Prepare Movementmessage: Preparing to move tonext positionSequencename: Obstacle PresentMoveToPositionname: Move to Cposition: Cmove_duration: 2.5ForceSuccessname: Ensure CompletionDetectObstaclename: Detect Obstacleobstacle_probability: 0.3AvoidObstaclename: Avoid Obstacleavoid_duration: 1.5DetectObstaclename: Detect Obstacleobstacle_probability: 0.4AvoidObstaclename: Avoid Obstacleavoid_duration: 1.8PerformTaskname: Position C Tasktask_name: EnvironmentMonitoringtask_duration: 2 \ No newline at end of file diff --git a/docs/modules/13_mission_planning/mission_planning_main.rst b/docs/modules/13_mission_planning/mission_planning_main.rst deleted file mode 100644 index c35eacd8d5..0000000000 --- a/docs/modules/13_mission_planning/mission_planning_main.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _`Mission Planning`: - -Mission Planning -================ - -Mission planning includes tools such as finite state machines and behavior trees used to describe robot behavior and high level task planning. - -.. toctree:: - :maxdepth: 2 - :caption: Contents - - state_machine/state_machine - behavior_tree/behavior_tree diff --git a/docs/modules/13_mission_planning/state_machine/robot_behavior_case.png b/docs/modules/13_mission_planning/state_machine/robot_behavior_case.png deleted file mode 100644 index fbc1369cbc..0000000000 Binary files a/docs/modules/13_mission_planning/state_machine/robot_behavior_case.png and /dev/null differ diff --git a/docs/modules/13_mission_planning/state_machine/state_machine_main.rst b/docs/modules/13_mission_planning/state_machine/state_machine_main.rst deleted file mode 100644 index 3f516d46a9..0000000000 --- a/docs/modules/13_mission_planning/state_machine/state_machine_main.rst +++ /dev/null @@ -1,74 +0,0 @@ -State Machine -------------- - -A state machine is a model used to describe the transitions of an object between different states. It clearly shows how an object changes state based on events and may trigger corresponding actions. - -Core Concepts -~~~~~~~~~~~~~ - -- **State**: A distinct mode or condition of the system (e.g. "Idle", "Running"). Managed by State class with optional on_enter/on_exit callbacks -- **Event**: A trigger signal that may cause state transitions (e.g. "start", "stop") -- **Transition**: A state change path from source to destination state triggered by an event -- **Action**: An operation executed during transition (before entering new state) -- **Guard**: A precondition that must be satisfied to allow transition - -Code Link -~~~~~~~~~~~ - -.. autoclass:: MissionPlanning.StateMachine.state_machine.StateMachine - :members: add_transition, process, register_state - :special-members: __init__ - -PlantUML Support -~~~~~~~~~~~~~~~~ - -The ``generate_plantuml()`` method creates diagrams showing: - -- Current state (marked with [*] arrow) -- All possible transitions -- Guard conditions in [brackets] -- Actions prefixed with / - -Example -~~~~~~~ - -state machine diagram: -+++++++++++++++++++++++ -.. image:: robot_behavior_case.png - -state transition table: -+++++++++++++++++++++++ -.. list-table:: State Transitions - :header-rows: 1 - :widths: 20 15 20 20 20 - - * - Source State - - Event - - Target State - - Guard - - Action - * - patrolling - - detect_task - - executing_task - - - - - * - executing_task - - task_complete - - patrolling - - - - reset_task - * - executing_task - - low_battery - - returning_to_base - - is_battery_low - - - * - returning_to_base - - reach_base - - charging - - - - - * - charging - - charge_complete - - patrolling - - - - \ No newline at end of file diff --git a/docs/modules/1_introduction/1_definition_of_robotics/definition_of_robotics_main.rst b/docs/modules/1_introduction/1_definition_of_robotics/definition_of_robotics_main.rst deleted file mode 100644 index ca595301a6..0000000000 --- a/docs/modules/1_introduction/1_definition_of_robotics/definition_of_robotics_main.rst +++ /dev/null @@ -1,107 +0,0 @@ -Definition of Robotics ----------------------- - -This section explains the definition, history, key components, and applications of robotics. - -What is Robotics? -^^^^^^^^^^^^^^^^^^ - -Robot is a machine that can perform tasks automatically or semi-autonomously. -Robotics is the study of robots. - -The word “robot” comes from the Czech word “robota,” which means “forced labor” or “drudgery.” -It was first used in the 1920 science fiction play `R.U.R.`_ (Rossum’s Universal Robots) -by the Czech writer `Karel Čapek`_. -In the play, robots were artificial workers created to serve humans, but they eventually rebelled. - -Over time, “robot” came to refer to machines or automated systems that can perform tasks, -often with some level of intelligence or autonomy. - -Currently, 2 millions robots are working in the world, and the number is increasing every year. -In South Korea, where the adoption of robots is particularly rapid, -50 robots are operating per 1,000 people. - -.. _`R.U.R.`: https://thereader.mitpress.mit.edu/origin-word-robot-rur/ -.. _`Karel Čapek`: https://en.wikipedia.org/wiki/Karel_%C4%8Capek - -The History of Robots -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This timeline highlights key milestones in the history of robotics: - -Ancient and Early Concepts (Before 1500s) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The idea of **automated machines** has existed for thousands of years. -Ancient civilizations imagined mechanical beings: - -- **Ancient Greece (4th Century BC)** – Greek engineer `Hero of Alexandria`_ designed early **automata** (self-operating machines) powered by water or air. -- **Chinese and Arabic Automata (9th–13th Century)** – Inventors like `Ismail Al-Jazari`_ created intricate mechanical devices, including water clocks and automated moving peacocks driven by hydropower. - -.. _`Hero of Alexandria`: https://en.wikipedia.org/wiki/Hero_of_Alexandria -.. _`Ismail Al-Jazari`: https://en.wikipedia.org/wiki/Ismail_al-Jazari - -The Birth of Modern Robotics (1500s–1800s) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- `Leonardo da Vinci’s Robot`_ (1495) – Designed a humanoid knight with mechanical movement. -- `Jacques de Vaucanson’s Digesting Duck`_ (1738) – Created robotic figures like a mechanical duck that could "eat" and "digest." -- `Industrial Revolution`_ (18th–19th Century) – Machines began replacing human labor in factories, setting the foundation for automation. - -.. _`Leonardo da Vinci’s Robot`: https://en.wikipedia.org/wiki/Leonardo%27s_robot -.. _`Jacques de Vaucanson’s Digesting Duck`: https://en.wikipedia.org/wiki/Jacques_de_Vaucanson -.. _`Industrial Revolution`: https://en.wikipedia.org/wiki/Industrial_Revolution - -The Rise of Industrial Robots (1900s–1950s) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- **The Term “Robot” (1921)** – Czech writer `Karel Čapek`_ introduced the word *“robot”* in his play `R.U.R.`_ (Rossum’s Universal Robots). -- **Early Cybernetics (1940s–1950s)** – Scientists like `Norbert Wiener`_ developed theories of self-regulating machines, influencing modern robotics (Cybernetics). - -.. _`Norbert Wiener`: https://en.wikipedia.org/wiki/Norbert_Wiener - -The Birth of Modern Robotics (1950s–1980s) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- **First Industrial Robot (1961)** – `Unimate`_, created by `George Devol`_ and `Joseph Engelberger`_, was the first programmable robot used in a factory. -- **Rise of AI & Autonomous Robots (1970s–1980s)** – Researchers developed mobile robots like `Shakey`_ (Stanford, 1966) and AI-based control systems. - -.. _`Unimate`: https://en.wikipedia.org/wiki/Unimate -.. _`George Devol`: https://en.wikipedia.org/wiki/George_Devol -.. _`Joseph Engelberger`: https://en.wikipedia.org/wiki/Joseph_Engelberger -.. _`Shakey`: https://en.wikipedia.org/wiki/Shakey_the_robot - -Advanced Robotics and AI Integration (1990s–Present) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- **Industrial Robots** – Advanced robots like `Baxter`_ and `Amazon Robotics`_ revolutionized manufacturing and logistics. -- **Autonomous Vehicles** – Self-driving cars for robo taxi like `Waymo`_ and autonomous haulage system in mining like `AHS`_ became more advanced and bisiness-ready. -- **Exploration Robots** – Used for planetary exploration (e.g., NASA’s `Mars rovers`_). -- **Medical Robotics** – Robots like `da Vinci Surgical System`_ revolutionized healthcare. -- **Personal Robots** – Devices like `Roomba`_ (vacuum robot) and `Sophia`_ (AI humanoid) became popular. -- **Service Robots** - Assistive robots like serving robots in restaurants and hotels like `Bellabot`_. -- **Collaborative Robots (Drones)** – Collaborative robots like UAV (Unmanned Aerial Vehicle) in drone shows and delivery services. - -.. _`Baxter`: https://en.wikipedia.org/wiki/Baxter_(robot) -.. _`Amazon Robotics`: https://en.wikipedia.org/wiki/Amazon_Robotics -.. _`Mars rovers`: https://en.wikipedia.org/wiki/Mars_rover -.. _`Waymo`: https://waymo.com/ -.. _`AHS`: https://www.futurebridge.com/industry/perspectives-industrial-manufacturing/autonomous-haulage-systems-the-future-of-mining-operations/ -.. _`da Vinci Surgical System`: https://en.wikipedia.org/wiki/Da_Vinci_Surgical_System -.. _`Roomba`: https://en.wikipedia.org/wiki/Roomba -.. _`Sophia`: https://en.wikipedia.org/wiki/Sophia_(robot) -.. _`Bellabot`: https://www.pudurobotics.com/en - -Key Components of Robotics -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Robotics consists of several essential components: - -#. Sensors – Gather information from the environment (e.g., Cameras, LiDAR, GNSS, Gyro, Accelerometer, Wheel encoders). -#. Actuators – Enable movement and interaction with the world (e.g., Motors, Hydraulic systems). -#. Computers – Process sensor data and make decisions (e.g., Micro-controllers, CPUs, GPUs). -#. Power Supply – Provides energy to run the robot (e.g., Batteries, Solar power). -#. Software & Algorithms – Allow the robot to function and make intelligent decisions (e.g., ROS, Machine learning models, Localization, Mapping, Path planning, Control). - -This project, `PythonRobotics`, focuses on the software and algorithms part of robotics. -If you are interested in `Sensors` hardware, you can check :ref:`Internal Sensors for Robots` or :ref:`External Sensors for Robots`. diff --git a/docs/modules/1_introduction/2_python_for_robotics/python_for_robotics_main.rst b/docs/modules/1_introduction/2_python_for_robotics/python_for_robotics_main.rst deleted file mode 100644 index c47c122853..0000000000 --- a/docs/modules/1_introduction/2_python_for_robotics/python_for_robotics_main.rst +++ /dev/null @@ -1,135 +0,0 @@ -Python for Robotics ----------------------- - -A programing language, Python is used for this `PythonRobotics` project -to achieve the purposes of this project described in the :ref:`What is PythonRobotics?`. - -This section explains the Python itself and features for science computing and robotics. - -Python for general-purpose programming -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -`Python `_ is an general-purpose programming language developed by -`Guido van Rossum `_ from the late 1980s. - -It features as follows: - -#. High-level -#. Interpreted -#. Dynamic type system (also type annotation is supported) -#. Emphasizes code readability -#. Rapid prototyping -#. Batteries included -#. Interoperability for C and Fortran - -Due to these features, Python is one of the most popular programming language -for educational purposes for programming beginners. - -Python for Scientific Computing -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Python itself was not designed for scientific computing. -However, scientists quickly recognized its strengths. -For example, - -#. High-level and interpreted features enable scientists to focus on their problems without dealing with low-level programming tasks like memory management. -#. Code readability, rapid prototyping, and batteries included features enables scientists who are not professional programmers, to solve their problems easily. -#. The interoperability to wrap C and Fortran libraries enables scientists to access already existed powerful and optimized scientific computing libraries. - -To address the more needs of scientific computing, many fundamental scientific computation libraries have been developed based on the upper features. - -- `NumPy `_ is the fundamental package for scientific computing with Python. -- `SciPy `_ is a library that builds on NumPy and provides a large number of functions that operate on NumPy arrays and are useful for different types of scientific and engineering applications. -- `Matplotlib `_ is a plotting library for the Python programming language and its numerical mathematics extension NumPy. -- `Pandas `_ is a fast, powerful, flexible, and easy-to-use open-source data analysis and data manipulation library built on top of NumPy. -- `SymPy `_ is a Python library for symbolic mathematics. -- `CVXPy `_ is a Python-embedded modeling language for convex optimization problems. - -Also, more domain-specific libraries have been developed based on these fundamental libraries: - -- `Scikit-learn `_ is a free software machine learning library for the Python programming language. -- `Scikit-image `_ is a collection of algorithms for image processing. -- `Networkx `_ is a package for the creation, manipulation for complex networks. -- `SunPy `_ is a community-developed free and open-source software package for solar physics. -- `Astropy `_ is a community-developed free and open-source software package for astronomy. - -Currently, Python is one of the most popular programming languages for scientific computing. - -Python for Robotics -^^^^^^^^^^^^^^^^^^^^ - -Python has become an increasingly popular language in robotics. - -These are advantages of Python for Robotics: - -Simplicity and Readability -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Python's syntax is clear and concise, making it easier to learn and write code. -This is crucial in robotics where complex algorithms and control logic are involved. - - -Extensive libraries for scientific computation. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Scientific computation routine are fundamental for robotics. -For example: - -- Matrix operation is needed for rigid body transformation, state estimation, and model based control. -- Optimization is needed for optimization based SLAM, optimal path planning, and optimal control. -- Visualization is needed for robot teleoperation, debugging, and simulation. - -ROS supports Python -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`ROS`_ (Robot Operating System) is an open-source and widely used framework for robotics development. -It is designed to help developping complicated robotic applications. -ROS provides essential tools, libraries, and drivers to simplify robot programming and integration. - -Key Features of ROS: - -- Modular Architecture – Uses a node-based system where different components (nodes) communicate via messages. -- Hardware Abstraction – Supports various robots, sensors, and actuators, making development more flexible. -- Powerful Communication System – Uses topics, services, and actions for efficient data exchange between components. -- Rich Ecosystem – Offers many pre-built packages for navigation, perception, and manipulation. -- Multi-language Support – Primarily uses Python and C++, but also supports other languages. -- Simulation & Visualization – Tools like Gazebo (for simulation) and RViz (for visualization) aid in development and testing. -- Scalability & Community Support – Widely used in academia and industry, with a large open-source community. - -ROS has strong Python support (`rospy`_ for ROS1 and `rclpy`_ for ROS2). -This allows developers to easily create nodes, manage communication between -different parts of a robot system, and utilize various ROS tools. - -.. _`ROS`: https://www.ros.org/ -.. _`rospy`: http://wiki.ros.org/rospy -.. _`rclpy`: https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html - -Cross-Platform Compatibility -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Python code can run on various operating systems (Windows, macOS, Linux), providing flexibility in choosing hardware platforms for robotics projects. - -Large Community and Support -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Python has a vast and active community, offering ample resources, tutorials, and support for developers. This is invaluable when tackling challenges in robotics development. - -Situations which Python is NOT suitable for Robotics -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -We explained the advantages of Python for robotics. -However, Python is not always the best choice for robotics development. - -These are situations where Python is NOT suitable for robotics: - -High-speed real-time control -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Python is an interpreted language, which means it is slower than compiled languages like C++. -This can be a disadvantage when real-time control is required, -such as in high-speed motion control or safety-critical systems. - -So, for these applications, we recommend to understand the each algorithm you -needed using this project and implement it in other suitable languages like C++. - -Resource-constrained systems -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Python is a high-level language that requires more memory and processing power -compared to low-level languages. -So, it is difficult to run Python on resource-constrained systems like -microcontrollers or embedded devices. -In such cases, C or C++ is more suitable for these applications. diff --git a/docs/modules/1_introduction/3_technologies_for_robotics/technologies_for_robotics_main.rst b/docs/modules/1_introduction/3_technologies_for_robotics/technologies_for_robotics_main.rst deleted file mode 100644 index 0ed51e961b..0000000000 --- a/docs/modules/1_introduction/3_technologies_for_robotics/technologies_for_robotics_main.rst +++ /dev/null @@ -1,68 +0,0 @@ -Technologies for Robotics -------------------------- - -The field of robotics needs wide areas of technologies such as mechanical engineering, -electrical engineering, computer science, and artificial intelligence (AI). -This project, `PythonRobotics`, only focus on computer science and artificial intelligence. - -The technologies for robotics are categorized as following 3 categories: - -#. `Autonomous Navigation`_ -#. `Manipulation`_ -#. `Robot type specific technologies`_ - -.. _`Autonomous Navigation`: - -Autonomous Navigation -^^^^^^^^^^^^^^^^^^^^^^^^ -Autonomous navigation is a capability that can move to a goal over long -periods of time without any external control by an operator. - -To achieve autonomous navigation, the robot needs to have the following technologies: -- It needs to know where it is (localization) -- Where it is safe (mapping) -- Where is is safe and where the robot is in the map (Simultaneous Localization and Mapping (SLAM)) -- Where and how to move (path planning) -- How to control its motion (path following). - -The autonomous system would not work correctly if any of these technologies is missing. - -In recent years, autonomous navigation technologies have received huge -attention in many fields. -For example, self-driving cars, drones, and autonomous mobile robots in indoor and outdoor environments. - -In this project, we provide many algorithms, sample codes, -and documentations for autonomous navigation. - -#. :ref:`Localization` -#. :ref:`Mapping` -#. :ref:`SLAM` -#. :ref:`Path planning` -#. :ref:`Path tracking` - - - -.. _`Manipulation`: - -Manipulation -^^^^^^^^^^^^^^^^^^^^^^^^ - -#. :ref:`Arm Navigation` - -.. _`Robot type specific technologies`: - -Robot type specific technologies -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -#. :ref:`Aerial Navigation` -#. :ref:`Bipedal` -#. :ref:`Inverted Pendulum` - - -Additional Information -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -#. :ref:`utils` -#. :ref:`Appendix` - - diff --git a/docs/modules/1_introduction/introduction_main.rst b/docs/modules/1_introduction/introduction_main.rst deleted file mode 100644 index 1871dfc3b1..0000000000 --- a/docs/modules/1_introduction/introduction_main.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _Introduction: - -Introduction -============ - -PythonRobotics is composed of two words: "Python" and "Robotics". -Therefore, I will first explain these two topics, Robotics and Python. -After that, I will provide an overview of the robotics technologies -covered in PythonRobotics. - -.. toctree:: - :maxdepth: 2 - :caption: Table of Contents - - 1_definition_of_robotics/definition_of_robotics - 2_python_for_robotics/python_for_robotics - 3_technologies_for_robotics/technologies_for_robotics - diff --git a/docs/modules/2_localization/extended_kalman_filter_localization_files/ekf_with_velocity_correction_1_0.png b/docs/modules/2_localization/extended_kalman_filter_localization_files/ekf_with_velocity_correction_1_0.png deleted file mode 100644 index 595b651bd2..0000000000 Binary files a/docs/modules/2_localization/extended_kalman_filter_localization_files/ekf_with_velocity_correction_1_0.png and /dev/null differ diff --git a/docs/modules/3_mapping/distance_map/distance_map.png b/docs/modules/3_mapping/distance_map/distance_map.png deleted file mode 100644 index 2d89252a70..0000000000 Binary files a/docs/modules/3_mapping/distance_map/distance_map.png and /dev/null differ diff --git a/docs/modules/3_mapping/distance_map/distance_map_main.rst b/docs/modules/3_mapping/distance_map/distance_map_main.rst deleted file mode 100644 index ec60e752c9..0000000000 --- a/docs/modules/3_mapping/distance_map/distance_map_main.rst +++ /dev/null @@ -1,27 +0,0 @@ -Distance Map ------------- - -This is an implementation of the Distance Map algorithm for path planning. - -The Distance Map algorithm computes the unsigned distance field (UDF) and signed distance field (SDF) from a boolean field representing obstacles. - -The UDF gives the distance from each point to the nearest obstacle. The SDF gives positive distances for points outside obstacles and negative distances for points inside obstacles. - -Example -~~~~~~~ - -The algorithm is demonstrated on a simple 2D grid with obstacles: - -.. image:: distance_map.png - -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.DistanceMap.distance_map.compute_sdf - -.. autofunction:: Mapping.DistanceMap.distance_map.compute_udf - -References -~~~~~~~~~~ - -- `Distance Transforms of Sampled Functions `_ paper by Pedro F. Felzenszwalb and Daniel P. Huttenlocher. \ No newline at end of file diff --git a/docs/modules/3_mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst b/docs/modules/3_mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst deleted file mode 100644 index bee2f64193..0000000000 --- a/docs/modules/3_mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst +++ /dev/null @@ -1,11 +0,0 @@ -Ray casting grid map --------------------- - -This is a 2D ray casting grid mapping example. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/raycasting_grid_map/animation.gif - -Code Link -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autofunction:: Mapping.ray_casting_grid_map.ray_casting_grid_map.generate_ray_casting_grid_map diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example.rst b/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example.rst deleted file mode 100644 index 15963aff79..0000000000 --- a/docs/modules/4_slam/graph_slam/graphSLAM_SE2_example.rst +++ /dev/null @@ -1,208 +0,0 @@ -Graph SLAM for a real-world SE(2) dataset -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: ipython3 - - from graphslam.graph import Graph - from graphslam.load import load_g2o_se2 - -Introduction -^^^^^^^^^^^^ - -For a complete derivation of the Graph SLAM algorithm, please see -`Graph SLAM Formulation`_. - -This notebook illustrates the iterative optimization of a real-world -:math:`SE(2)` dataset. The code can be found in the ``graphslam`` -folder. For simplicity, numerical differentiation is used in lieu of -analytic Jacobians. This code originated from the -`python-graphslam `__ -repo, which is a full-featured Graph SLAM solver. The dataset in this -example is used with permission from Luca Carlone and was downloaded -from his `website `__. - -The Dataset -^^^^^^^^^^^^ - -.. code:: ipython3 - - g = load_g2o_se2("data/input_INTEL.g2o") - - print("Number of edges: {}".format(len(g._edges))) - print("Number of vertices: {}".format(len(g._vertices))) - - -.. parsed-literal:: - - Number of edges: 1483 - Number of vertices: 1228 - - -.. code:: ipython3 - - g.plot(title=r"Original ($\chi^2 = {:.0f}$)".format(g.calc_chi2())) - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_4_0.png - - -Each edge in this dataset is a constraint that compares the measured -:math:`SE(2)` transformation between two poses to the expected -:math:`SE(2)` transformation between them, as computed using the current -pose estimates. These edges can be classified into two categories: - -1. Odometry edges constrain two consecutive vertices, and the - measurement for the :math:`SE(2)` transformation comes directly from - odometry data. -2. Scan-matching edges constrain two non-consecutive vertices. These - scan matches can be computed using, for example, 2-D LiDAR data or - landmarks; the details of how these constraints are determined is - beyond the scope of this example. This is often referred to as *loop - closure* in the Graph SLAM literature. - -We can easily parse out the two different types of edges present in this -dataset and plot them. - -.. code:: ipython3 - - def parse_edges(g): - """Split the graph `g` into two graphs, one with only odometry edges and the other with only scan-matching edges. - - Parameters - ---------- - g : graphslam.graph.Graph - The input graph - - Returns - ------- - g_odom : graphslam.graph.Graph - A graph consisting of the vertices and odometry edges from `g` - g_scan : graphslam.graph.Graph - A graph consisting of the vertices and scan-matching edges from `g` - - """ - edges_odom = [] - edges_scan = [] - - for e in g._edges: - if abs(e.vertex_ids[1] - e.vertex_ids[0]) == 1: - edges_odom.append(e) - else: - edges_scan.append(e) - - g_odom = Graph(edges_odom, g._vertices) - g_scan = Graph(edges_scan, g._vertices) - - return g_odom, g_scan - -.. code:: ipython3 - - g_odom, g_scan = parse_edges(g) - - print("Number of odometry edges: {:4d}".format(len(g_odom._edges))) - print("Number of scan-matching edges: {:4d}".format(len(g_scan._edges))) - - print("\nχ^2 error from odometry edges: {:11.3f}".format(g_odom.calc_chi2())) - print("χ^2 error from scan-matching edges: {:11.3f}".format(g_scan.calc_chi2())) - - -.. parsed-literal:: - - Number of odometry edges: 1227 - Number of scan-matching edges: 256 - - χ^2 error from odometry edges: 0.232 - χ^2 error from scan-matching edges: 7191686.151 - - -.. code:: ipython3 - - g_odom.plot(title="Odometry edges") - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_8_0.png - - -.. code:: ipython3 - - g_scan.plot(title="Scan-matching edges") - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_9_0.png - - -Optimization -^^^^^^^^^^^^ - -Initially, the pose estimates are consistent with the collected odometry -measurements, and the odometry edges contribute almost zero towards the -:math:`\chi^2` error. However, there are large discrepancies between the -scan-matching constraints and the initial pose estimates. This is not -surprising, since small errors in odometry readings that are propagated -over time can lead to large errors in the robot’s trajectory. What makes -Graph SLAM effective is that it allows incorporation of multiple -different data sources into a single optimization problem. - -.. code:: ipython3 - - g.optimize() - - -.. parsed-literal:: - - - Iteration chi^2 rel. change - --------- ----- ----------- - 0 7191686.3825 - 1 320031728.8624 43.500234 - 2 125083004.3299 -0.609154 - 3 338155.9074 -0.997297 - 4 735.1344 -0.997826 - 5 215.8405 -0.706393 - 6 215.8405 -0.000000 - - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/GraphBasedSLAM/Graph_SLAM_optimization.gif - -.. code:: ipython3 - - g.plot(title="Optimized") - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_13_0.png - - -.. code:: ipython3 - - print("\nχ^2 error from odometry edges: {:7.3f}".format(g_odom.calc_chi2())) - print("χ^2 error from scan-matching edges: {:7.3f}".format(g_scan.calc_chi2())) - - -.. parsed-literal:: - - - χ^2 error from odometry edges: 142.189 - χ^2 error from scan-matching edges: 73.652 - - -.. code:: ipython3 - - g_odom.plot(title="Odometry edges") - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_15_0.png - - -.. code:: ipython3 - - g_scan.plot(title="Scan-matching edges") - - - -.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_16_0.png - diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_doc.rst b/docs/modules/4_slam/graph_slam/graphSLAM_doc.rst deleted file mode 100644 index 5297604809..0000000000 --- a/docs/modules/4_slam/graph_slam/graphSLAM_doc.rst +++ /dev/null @@ -1,554 +0,0 @@ - -Graph SLAM -~~~~~~~~~~~~ - -.. code:: ipython3 - - import copy - import math - import itertools - import numpy as np - import matplotlib.pyplot as plt - from graph_based_slam import calc_rotational_matrix, calc_jacobian, cal_observation_sigma, \ - calc_input, observation, motion_model, Edge, pi_2_pi - - %matplotlib inline - np.set_printoptions(precision=3, suppress=True) - np.random.seed(0) - -Introduction -^^^^^^^^^^^^ - -In contrast to the probabilistic approaches for solving SLAM, such as -EKF, UKF, particle filters, and so on, the graph technique formulates -the SLAM as an optimization problem. It is mostly used to solve the full -SLAM problem in an offline fashion, i.e. optimize all the poses of the -robot after the path has been traversed. However, some variants are -available that uses graph-based approaches to perform online estimation -or to solve for a subset of the poses. - -GraphSLAM uses the motion information as well as the observations of the -environment to create least square problem that can be solved using -standard optimization techniques. - -Minimal Example -^^^^^^^^^^^^^^^ - -The following example illustrates the main idea behind graphSLAM. A -simple case of a 1D robot is considered that can only move in 1 -direction. The robot is commanded to move forward with a control input -:math:`u_t=1`, however, the motion is not perfect and the measured -odometry will deviate from the true path. At each time step the robot can -observe its environment, for this simple case as well, there is only a -single landmark at coordinates :math:`x=3`. The measured observations -are the range between the robot and landmark. These measurements are -also subjected to noise. No bearing information is required since this -is a 1D problem. - -To solve this, graphSLAM creates what is called as the system -information matrix :math:`\Omega` also referred to as :math:`H` and the -information vector :math:`\xi` also known as :math:`b`. The entries are -created based on the information of the motion and the observation. - -.. code:: ipython3 - - R = 0.2 - Q = 0.2 - N = 3 - graphics_radius = 0.1 - - odom = np.empty((N,1)) - obs = np.empty((N,1)) - x_true = np.empty((N,1)) - - landmark = 3 - # Simulated readings of odometry and observations - x_true[0], odom[0], obs[0] = 0.0, 0.0, 2.9 - x_true[1], odom[1], obs[1] = 1.0, 1.5, 2.0 - x_true[2], odom[2], obs[2] = 2.0, 2.4, 1.0 - - hxDR = copy.deepcopy(odom) - # Visualization - plt.plot(landmark,0, '*k', markersize=30) - for i in range(N): - plt.plot(odom[i], 0, '.', markersize=50, alpha=0.8, color='steelblue') - plt.plot([odom[i], odom[i] + graphics_radius], - [0,0], 'r') - plt.text(odom[i], 0.02, "X_{}".format(i), fontsize=12) - plt.plot(obs[i]+odom[i],0,'.', markersize=25, color='brown') - plt.plot(x_true[i],0,'.g', markersize=20) - plt.grid() - plt.show() - - - # Defined as a function to facilitate iteration - def get_H_b(odom, obs): - """ - Create the information matrix and information vector. This implementation is - based on the concept of virtual measurement i.e. the observations of the - landmarks are converted into constraints (edges) between the nodes that - have observed this landmark. - """ - measure_constraints = {} - omegas = {} - zids = list(itertools.combinations(range(N),2)) - H = np.zeros((N,N)) - b = np.zeros((N,1)) - for (t1, t2) in zids: - x1 = odom[t1] - x2 = odom[t2] - z1 = obs[t1] - z2 = obs[t2] - - # Adding virtual measurement constraint - measure_constraints[(t1,t2)] = (x2-x1-z1+z2) - omegas[(t1,t2)] = (1 / (2*Q)) - - # populate system's information matrix and vector - H[t1,t1] += omegas[(t1,t2)] - H[t2,t2] += omegas[(t1,t2)] - H[t2,t1] -= omegas[(t1,t2)] - H[t1,t2] -= omegas[(t1,t2)] - - b[t1] += omegas[(t1,t2)] * measure_constraints[(t1,t2)] - b[t2] -= omegas[(t1,t2)] * measure_constraints[(t1,t2)] - - return H, b - - - H, b = get_H_b(odom, obs) - print("The determinant of H: ", np.linalg.det(H)) - H[0,0] += 1 # np.inf ? - print("The determinant of H after anchoring constraint: ", np.linalg.det(H)) - - for i in range(5): - H, b = get_H_b(odom, obs) - H[(0,0)] += 1 - - # Recover the posterior over the path - dx = np.linalg.inv(H) @ b - odom += dx - # repeat till convergence - print("Running graphSLAM ...") - print("Odometry values after optimzation: \n", odom) - - plt.figure() - plt.plot(x_true, np.zeros(x_true.shape), '.', markersize=20, label='Ground truth') - plt.plot(odom, np.zeros(x_true.shape), '.', markersize=20, label='Estimation') - plt.plot(hxDR, np.zeros(x_true.shape), '.', markersize=20, label='Odom') - plt.legend() - plt.grid() - plt.show() - - - -.. image:: graphSLAM_doc_files/graphSLAM_doc_2_0.png - - -.. parsed-literal:: - - The determinant of H: 0.0 - The determinant of H after anchoring constraint: 18.750000000000007 - Running graphSLAM ... - Odometry values after optimzation: - [[-0. ] - [ 0.9] - [ 1.9]] - - - -.. image:: graphSLAM_doc_files/graphSLAM_doc_2_2.png - - -In particular, the tasks are split into 2 parts, graph construction, and -graph optimization. ### Graph Construction - -Firstly the nodes are defined -:math:`\boldsymbol{x} = \boldsymbol{x}_{1:n}` such that each node is the -pose of the robot at time :math:`t_i` Secondly, the edges i.e. the -constraints, are constructed according to the following conditions: - -- robot moves from :math:`\boldsymbol{x}_i` to - :math:`\boldsymbol{x}_j`. This edge corresponds to the odometry - measurement. Relative motion constraints (Not included in the - previous minimal example). -- Measurement constraints, this can be done in two ways: - - - The information matrix is set in such a way that it includes the - landmarks in the map as well. Then the constraints can be entered - in a straightforward fashion between a node - :math:`\boldsymbol{x}_i` and some landmark :math:`m_k` - - Through creating a virtual measurement among all the node that - have observed the same landmark. More concretely, robot observes - the same landmark from :math:`\boldsymbol{x}_i` and - :math:`\boldsymbol{x}_j`. Relative measurement constraint. The - “virtual measurement” :math:`\boldsymbol{z}_{ij}`, which is the - estimated pose of :math:`\boldsymbol{x}_j` as seen from the node - :math:`\boldsymbol{x}_i`. The virtual measurement can then be - entered in the information matrix and vector in a similar fashion - to the motion constraints. - -An edge is fully characterized by the values of the error (entry to -information vector) and the local information matrix (entry to the -system’s information vector). The larger the local information matrix -(lower :math:`Q` or :math:`R`) the values that this edge will contribute -with. - -Important Notes: - -- The addition to the information matrix and vector are added to the - earlier values. -- In the case of 2D robot, the constraints will be non-linear, - therefore, a Jacobian of the error w.r.t the states is needed when - updated :math:`H` and :math:`b`. -- The anchoring constraint is needed in order to avoid having a - singular information matri. - -Graph Optimization -^^^^^^^^^^^^^^^^^^ - -The result from this formulation yields an overdetermined system of -equations. The goal after constructing the graph is to find: -:math:`x^*=\underset{x}{\mathrm{argmin}}~\underset{ij}\Sigma~f(e_{ij})`, -where :math:`f` is some error function that depends on the edges between -to related nodes :math:`i` and :math:`j`. The derivation in the references -arrive at the solution for :math:`x^* = H^{-1}b` - -Planar Example: -^^^^^^^^^^^^^^^ - -Now we will go through an example with a more realistic case of a 2D -robot with 3DoF, namely, :math:`[x, y, \theta]^T` - -.. code:: ipython3 - - # Simulation parameter - Qsim = np.diag([0.01, np.deg2rad(0.010)])**2 # error added to range and bearing - Rsim = np.diag([0.1, np.deg2rad(1.0)])**2 # error added to [v, w] - - DT = 2.0 # time tick [s] - SIM_TIME = 100.0 # simulation time [s] - MAX_RANGE = 30.0 # maximum observation range - STATE_SIZE = 3 # State size [x,y,yaw] - - # TODO: Why not use Qsim ? - # Covariance parameter of Graph Based SLAM - C_SIGMA1 = 0.1 - C_SIGMA2 = 0.1 - C_SIGMA3 = np.deg2rad(1.0) - - MAX_ITR = 20 # Maximum iteration during optimization - timesteps = 1 - - # consider only 2 landmarks for simplicity - # RFID positions [x, y, yaw] - RFID = np.array([[10.0, -2.0, 0.0], - # [15.0, 10.0, 0.0], - # [3.0, 15.0, 0.0], - # [-5.0, 20.0, 0.0], - # [-5.0, 5.0, 0.0] - ]) - - # State Vector [x y yaw v]' - xTrue = np.zeros((STATE_SIZE, 1)) - xDR = np.zeros((STATE_SIZE, 1)) # Dead reckoning - xTrue[2] = np.deg2rad(45) - xDR[2] = np.deg2rad(45) - # history initial values - hxTrue = xTrue - hxDR = xTrue - _, z, _, _ = observation(xTrue, xDR, np.array([[0,0]]).T, RFID) - hz = [z] - - for i in range(timesteps): - u = calc_input() - xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) - hxDR = np.hstack((hxDR, xDR)) - hxTrue = np.hstack((hxTrue, xTrue)) - hz.append(z) - - # visualize - graphics_radius = 0.3 - plt.plot(RFID[:, 0], RFID[:, 1], "*k", markersize=20) - plt.plot(hxDR[0, :], hxDR[1, :], '.', markersize=50, alpha=0.8, label='Odom') - plt.plot(hxTrue[0, :], hxTrue[1, :], '.', markersize=20, alpha=0.6, label='X_true') - - for i in range(hxDR.shape[1]): - x = hxDR[0, i] - y = hxDR[1, i] - yaw = hxDR[2, i] - plt.plot([x, x + graphics_radius * np.cos(yaw)], - [y, y + graphics_radius * np.sin(yaw)], 'r') - d = hz[i][:, 0] - angle = hz[i][:, 1] - plt.plot([x + d * np.cos(angle + yaw)], [y + d * np.sin(angle + yaw)], '.', - markersize=20, alpha=0.7) - plt.legend() - plt.grid() - plt.show() - - - -.. image:: graphSLAM_doc_files/graphSLAM_doc_4_0.png - - -.. code:: ipython3 - - # Copy the data to have a consistent naming with the .py file - zlist = copy.deepcopy(hz) - x_opt = copy.deepcopy(hxDR) - xlist = copy.deepcopy(hxDR) - number_of_nodes = x_opt.shape[1] - n = number_of_nodes * STATE_SIZE - -After the data has been saved, the graph will be constructed by looking -at each pair for nodes. The virtual measurement is only created if two -nodes have observed the same landmark at different points in time. The -next cells are a walk through for a single iteration of graph -construction -> optimization -> estimate update. - -.. code:: ipython3 - - # get all the possible combination of the different node - zids = list(itertools.combinations(range(len(zlist)), 2)) - print("Node combinations: \n", zids) - - for i in range(xlist.shape[1]): - print("Node {} observed landmark with ID {}".format(i, zlist[i][0, 3])) - - -.. parsed-literal:: - - Node combinations: - [(0, 1)] - Node 0 observed landmark with ID 0.0 - Node 1 observed landmark with ID 0.0 - - -In the following code snippet the error based on the virtual measurement -between node 0 and 1 will be created. The equations for the error is as follows: -:math:`e_{ij}^x = x_j + d_j cos(\psi_j +\theta_j) - x_i - d_i cos(\psi_i + \theta_i)` - -:math:`e_{ij}^y = y_j + d_j sin(\psi_j + \theta_j) - y_i - d_i sin(\psi_i + \theta_i)` - -:math:`e_{ij}^x = \psi_j + \theta_j - \psi_i - \theta_i` - -Where :math:`[x_i, y_i, \psi_i]` is the pose for node :math:`i` and -similarly for node :math:`j`, :math:`d` is the measured distance at -nodes :math:`i` and :math:`j`, and :math:`\theta` is the measured -bearing to the landmark. The difference is visualized with the figure in -the next cell. - -In case of perfect motion and perfect measurement the error shall be -zero since :math:`x_j + d_j cos(\psi_j + \theta_j)` should equal -:math:`x_i + d_i cos(\psi_i + \theta_i)` - -.. code:: ipython3 - - # Initialize edges list - edges = [] - - # Go through all the different combinations - for (t1, t2) in zids: - x1, y1, yaw1 = xlist[0, t1], xlist[1, t1], xlist[2, t1] - x2, y2, yaw2 = xlist[0, t2], xlist[1, t2], xlist[2, t2] - - # All nodes have valid observation with ID=0, therefore, no data association condition - iz1 = 0 - iz2 = 0 - - d1 = zlist[t1][iz1, 0] - angle1, phi1 = zlist[t1][iz1, 1], zlist[t1][iz1, 2] - d2 = zlist[t2][iz2, 0] - angle2, phi2 = zlist[t2][iz2, 1], zlist[t2][iz2, 2] - - # find angle between observation and horizontal - tangle1 = pi_2_pi(yaw1 + angle1) - tangle2 = pi_2_pi(yaw2 + angle2) - - # project the observations - tmp1 = d1 * math.cos(tangle1) - tmp2 = d2 * math.cos(tangle2) - tmp3 = d1 * math.sin(tangle1) - tmp4 = d2 * math.sin(tangle2) - - edge = Edge() - print(y1,y2, tmp3, tmp4) - # calculate the error of the virtual measurement - # node 1 as seen from node 2 throught the observations 1,2 - edge.e[0, 0] = x2 - x1 - tmp1 + tmp2 - edge.e[1, 0] = y2 - y1 - tmp3 + tmp4 - edge.e[2, 0] = pi_2_pi(yaw2 - yaw1 - tangle1 + tangle2) - - edge.d1, edge.d2 = d1, d2 - edge.yaw1, edge.yaw2 = yaw1, yaw2 - edge.angle1, edge.angle2 = angle1, angle2 - edge.id1, edge.id2 = t1, t2 - - edges.append(edge) - - print("For nodes",(t1,t2)) - print("Added edge with errors: \n", edge.e) - - # Visualize measurement projections - plt.plot(RFID[0, 0], RFID[0, 1], "*k", markersize=20) - plt.plot([x1,x2],[y1,y2], '.', markersize=50, alpha=0.8) - plt.plot([x1, x1 + graphics_radius * np.cos(yaw1)], - [y1, y1 + graphics_radius * np.sin(yaw1)], 'r') - plt.plot([x2, x2 + graphics_radius * np.cos(yaw2)], - [y2, y2 + graphics_radius * np.sin(yaw2)], 'r') - - plt.plot([x1,x1+tmp1], [y1,y1], label="obs 1 x") - plt.plot([x2,x2+tmp2], [y2,y2], label="obs 2 x") - plt.plot([x1,x1], [y1,y1+tmp3], label="obs 1 y") - plt.plot([x2,x2], [y2,y2+tmp4], label="obs 2 y") - plt.plot(x1+tmp1, y1+tmp3, 'o') - plt.plot(x2+tmp2, y2+tmp4, 'o') - plt.legend() - plt.grid() - plt.show() - - -.. parsed-literal:: - - 0.0 1.427649841628278 -2.0126109674819155 -3.524048014922737 - For nodes (0, 1) - Added edge with errors: - [[-0.02 ] - [-0.084] - [ 0. ]] - - - -.. image:: graphSLAM_doc_files/graphSLAM_doc_9_1.png - - -Since the constraints equations derived before are non-linear, -linearization is needed before we can insert them into the information -matrix and information vector. Two jacobians - -:math:`A = \frac{\partial e_{ij}}{\partial \boldsymbol{x}_i}` as -:math:`\boldsymbol{x}_i` holds the three variabls x, y, and theta. -Similarly, :math:`B = \frac{\partial e_{ij}}{\partial \boldsymbol{x}_j}` - -.. code:: ipython3 - - # Initialize the system information matrix and information vector - H = np.zeros((n, n)) - b = np.zeros((n, 1)) - x_opt = copy.deepcopy(hxDR) - - for edge in edges: - id1 = edge.id1 * STATE_SIZE - id2 = edge.id2 * STATE_SIZE - - t1 = edge.yaw1 + edge.angle1 - A = np.array([[-1.0, 0, edge.d1 * math.sin(t1)], - [0, -1.0, -edge.d1 * math.cos(t1)], - [0, 0, -1.0]]) - - t2 = edge.yaw2 + edge.angle2 - B = np.array([[1.0, 0, -edge.d2 * math.sin(t2)], - [0, 1.0, edge.d2 * math.cos(t2)], - [0, 0, 1.0]]) - - # TODO: use Qsim instead of sigma - sigma = np.diag([C_SIGMA1, C_SIGMA2, C_SIGMA3]) - Rt1 = calc_rotational_matrix(tangle1) - Rt2 = calc_rotational_matrix(tangle2) - edge.omega = np.linalg.inv(Rt1 @ sigma @ Rt1.T + Rt2 @ sigma @ Rt2.T) - - # Fill in entries in H and b - H[id1:id1 + STATE_SIZE, id1:id1 + STATE_SIZE] += A.T @ edge.omega @ A - H[id1:id1 + STATE_SIZE, id2:id2 + STATE_SIZE] += A.T @ edge.omega @ B - H[id2:id2 + STATE_SIZE, id1:id1 + STATE_SIZE] += B.T @ edge.omega @ A - H[id2:id2 + STATE_SIZE, id2:id2 + STATE_SIZE] += B.T @ edge.omega @ B - - b[id1:id1 + STATE_SIZE] += (A.T @ edge.omega @ edge.e) - b[id2:id2 + STATE_SIZE] += (B.T @ edge.omega @ edge.e) - - - print("The determinant of H: ", np.linalg.det(H)) - plt.figure() - plt.subplot(1,2,1) - plt.imshow(H, extent=[0, n, 0, n]) - plt.subplot(1,2,2) - plt.imshow(b, extent=[0, 1, 0, n]) - plt.show() - - # Fix the origin, multiply by large number gives same results but better visualization - H[0:STATE_SIZE, 0:STATE_SIZE] += np.identity(STATE_SIZE) - print("The determinant of H after origin constraint: ", np.linalg.det(H)) - plt.figure() - plt.subplot(1,2,1) - plt.imshow(H, extent=[0, n, 0, n]) - plt.subplot(1,2,2) - plt.imshow(b, extent=[0, 1, 0, n]) - plt.show() - - -.. parsed-literal:: - - The determinant of H: 0.0 - The determinant of H after origin constraint: 716.1972439134893 - - - -.. image:: graphSLAM_doc_files/graphSLAM_doc_11_1.png - -.. image:: graphSLAM_doc_files/graphSLAM_doc_11_2.png - -.. code:: ipython3 - - # Find the solution (first iteration) - dx = - np.linalg.inv(H) @ b - for i in range(number_of_nodes): - x_opt[0:3, i] += dx[i * 3:i * 3 + 3, 0] - print("dx: \n",dx) - print("ground truth: \n ",hxTrue) - print("Odom: \n", hxDR) - print("SLAM: \n", x_opt) - - # performance will improve with more iterations, nodes and landmarks. - print("\ngraphSLAM localization error: ", np.sum((x_opt - hxTrue) ** 2)) - print("Odom localization error: ", np.sum((hxDR - hxTrue) ** 2)) - - -.. parsed-literal:: - - dx: - [[-0. ] - [-0. ] - [ 0. ] - [ 0.02 ] - [ 0.084] - [-0. ]] - ground truth: - [[0. 1.414] - [0. 1.414] - [0.785 0.985]] - Odom: - [[0. 1.428] - [0. 1.428] - [0.785 0.976]] - SLAM: - [[-0. 1.448] - [-0. 1.512] - [ 0.785 0.976]] - - graphSLAM localization error: 0.010729072751057656 - Odom localization error: 0.0004460978857535104 - - -The references: -^^^^^^^^^^^^^^^ - -- `The GraphSLAM Algorithm with Applications to Large-Scale Mapping of Urban Structures `_ - -- `Introduction to Mobile Robotics Graph-Based SLAM `_ - -- `A Tutorial on Graph-Based SLAM `_ - -N.B. An additional step is required that uses the estimated path to -update the belief regarding the map. - diff --git a/docs/modules/4_slam/graph_slam/graphSLAM_formulation.rst b/docs/modules/4_slam/graph_slam/graphSLAM_formulation.rst deleted file mode 100644 index 4978596c10..0000000000 --- a/docs/modules/4_slam/graph_slam/graphSLAM_formulation.rst +++ /dev/null @@ -1,218 +0,0 @@ -.. _Graph SLAM Formulation: - -Graph SLAM Formulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Author Jeff Irion - -Problem Formulation -^^^^^^^^^^^^^^^^^^^ - -Let a robot’s trajectory through its environment be represented by a -sequence of :math:`N` poses: -:math:`\mathbf{p}_1, \mathbf{p}_2, \ldots, \mathbf{p}_N`. Each pose lies -on a manifold: :math:`\mathbf{p}_i \in \mathcal{M}`. Simple examples of -manifolds used in Graph SLAM include 1-D, 2-D, and 3-D space, i.e., -:math:`\mathbb{R}`, :math:`\mathbb{R}^2`, and :math:`\mathbb{R}^3`. -These environments are *rectilinear*, meaning that there is no concept -of orientation. By contrast, in :math:`SE(2)` problem settings a robot’s -pose consists of its location in :math:`\mathbb{R}^2` and its -orientation :math:`\theta`. Similarly, in :math:`SE(3)` a robot’s pose -consists of its location in :math:`\mathbb{R}^3` and its orientation, -which can be represented via Euler angles, quaternions, or :math:`SO(3)` -rotation matrices. - -As the robot explores its environment, it collects a set of :math:`M` -measurements :math:`\mathcal{Z} = \{\mathbf{z}_j\}`. Examples of such -measurements include odometry, GPS, and IMU data. Given a set of poses -:math:`\mathbf{p}_1, \ldots, \mathbf{p}_N`, we can compute the estimated -measurement -:math:`\hat{\mathbf{z}}_j(\mathbf{p}_1, \ldots, \mathbf{p}_N)`. We can -then compute the *residual* -:math:`\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j)` for measurement -:math:`j`. The formula for the residual depends on the type of -measurement. As an example, let :math:`\mathbf{z}_1` be an odometry -measurement that was collected when the robot traveled from -:math:`\mathbf{p}_1` to :math:`\mathbf{p}_2`. The expected measurement -and the residual are computed as - -.. math:: - - \begin{aligned} - \hat{\mathbf{z}}_1(\mathbf{p}_1, \mathbf{p}_2) &= \mathbf{p}_2 \ominus \mathbf{p}_1 \\ - \mathbf{e}_1(\mathbf{z}_1, \hat{\mathbf{z}}_1) &= \mathbf{z}_1 \ominus \hat{\mathbf{z}}_1 = \mathbf{z}_1 \ominus (\mathbf{p}_2 \ominus \mathbf{p}_1),\end{aligned} - -where the :math:`\ominus` operator indicates inverse pose composition. -We model measurement :math:`\mathbf{z}_j` as having independent Gaussian -noise with zero mean and covariance matrix :math:`\Omega_j^{-1}`; we -refer to :math:`\Omega_j` as the *information matrix* for measurement -:math:`j`. That is, - -.. math:: - p(\mathbf{z}_j \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) = \eta_j \exp (-\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\mathsf{T}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j) - :label: infomat - -where :math:`\eta_j` is the normalization constant. - -The objective of Graph SLAM is to find the maximum likelihood set of -poses given the measurements :math:`\mathcal{Z} = \{\mathbf{z}_j\}`; in -other words, we want to find - -.. math:: \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z}) - -Using Bayes’ rule, we can write this probability as - -.. math:: - \begin{aligned} - p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z}) &= \frac{p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) p(\mathbf{p}_1, \ldots, \mathbf{p}_N) }{ p(\mathcal{Z}) } \notag \\ - &\propto p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) - \end{aligned} - :label: bayes - -since :math:`p(\mathcal{Z})` is a constant (albeit, an unknown constant) -and we assume that :math:`p(\mathbf{p}_1, \ldots, \mathbf{p}_N)` is -uniformly distributed. Therefore, we -can use Eq. :eq:`infomat` and and Eq. :eq:`bayes` to simplify the Graph SLAM -optimization as follows: - -.. math:: - - \begin{aligned} - \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z}) &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) \\ - &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \prod_{j=1}^M p(\mathbf{z}_j \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) \\ - &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \prod_{j=1}^M \exp \left( -(\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j) \right) \\ - &= \mathop{\mathrm{arg\,min}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \sum_{j=1}^M (\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j).\end{aligned} - -We define - -.. math:: \chi^2 := \sum_{j=1}^M (\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j), - -and this is what we seek to minimize. - -Dimensionality and Pose Representation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Before proceeding further, it is helpful to discuss the dimensionality -of the problem. We have: - -- A set of :math:`N` poses - :math:`\mathbf{p}_1, \mathbf{p}_2, \ldots, \mathbf{p}_N`, where each - pose lies on the manifold :math:`\mathcal{M}` - - - Each pose :math:`\mathbf{p}_i` is represented as a vector in (a - subset of) :math:`\mathbb{R}^d`. For example: - - - An :math:`SE(2)` pose is typically represented as - :math:`(x, y, \theta)`, and thus :math:`d = 3`. - - - An :math:`SE(3)` pose is typically represented as - :math:`(x, y, z, q_x, q_y, q_z, q_w)`, where :math:`(x, y, z)` - is a point in :math:`\mathbb{R}^3` and - :math:`(q_x, q_y, q_z, q_w)` is a *quaternion*, and so - :math:`d = 7`. For more information about :math:`SE(3)` - parameterization and pose transformations, see - [blanco2010tutorial]_. - - - We also need to be able to represent each pose compactly as a - vector in (a subset of) :math:`\mathbb{R}^c`. - - - Since an :math:`SE(2)` pose has three degrees of freedom, the - :math:`(x, y, \theta)` representation is again sufficient and - :math:`c=3`. - - - An :math:`SE(3)` pose only has six degrees of freedom, and we - can represent it compactly as :math:`(x, y, z, q_x, q_y, q_z)`, - and thus :math:`c=6`. - - - We use the :math:`\boxplus` operator to indicate pose composition - when one or both of the poses are represented compactly. The - output can be a pose in :math:`\mathcal{M}` or a vector in - :math:`\mathbb{R}^c`, as required by context. - -- A set of :math:`M` measurements - :math:`\mathcal{Z} = \{\mathbf{z}_1, \mathbf{z}_2, \ldots, \mathbf{z}_M\}` - - - Each measurement’s dimensionality can be unique, and we will use - :math:`\bullet` to denote a “wildcard” variable. - - - Measurement :math:`\mathbf{z}_j \in \mathbb{R}^\bullet` has an - associated information matrix - :math:`\Omega_j \in \mathbb{R}^{\bullet \times \bullet}` and - residual function - :math:`\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j) = \mathbf{e}_j(\mathbf{z}_j, \mathbf{p}_1, \ldots, \mathbf{p}_N) \in \mathbb{R}^\bullet`. - - - A measurement could, in theory, constrain anywhere from 1 pose to - all :math:`N` poses. In practice, each measurement usually - constrains only 1 or 2 poses. - -Graph SLAM Algorithm -^^^^^^^^^^^^^^^^^^^^ - -The “Graph” in Graph SLAM refers to the fact that we view the problem as -a graph. The graph has a set :math:`\mathcal{V}` of :math:`N` vertices, -where each vertex :math:`v_i` has an associated pose -:math:`\mathbf{p}_i`. Similarly, the graph has a set :math:`\mathcal{E}` -of :math:`M` edges, where each edge :math:`e_j` has an associated -measurement :math:`\mathbf{z}_j`. In practice, the edges in this graph -are either unary (i.e., a loop) or binary. (Note: :math:`e_j` refers to -the edge in the graph associated with measurement :math:`\mathbf{z}_j`, -whereas :math:`\mathbf{e}_j` refers to the residual function associated -with :math:`\mathbf{z}_j`.) For more information about the Graph SLAM -algorithm, see [grisetti2010tutorial]_. - -We want to optimize - -.. math:: \chi^2 = \sum_{e_j \in \mathcal{E}} \mathbf{e}_j^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j. - -Let :math:`\mathbf{x}_i \in \mathbb{R}^c` be the compact representation -of pose :math:`\mathbf{p}_i \in \mathcal{M}`, and let - -.. math:: \mathbf{x} := \begin{bmatrix} \mathbf{x}_1 \\ \mathbf{x}_2 \\ \vdots \\ \mathbf{x}_N \end{bmatrix} \in \mathbb{R}^{cN} - -We will solve this optimization problem iteratively. Let - -.. math:: \mathbf{x}^{k+1} := \mathbf{x}^k \boxplus \Delta \mathbf{x}^k = \begin{bmatrix} \mathbf{x}_1 \boxplus \Delta \mathbf{x}_1 \\ \mathbf{x}_2 \boxplus \Delta \mathbf{x}_2 \\ \vdots \\ \mathbf{x}_N \boxplus \Delta \mathbf{x}_2 \end{bmatrix} - :label: update - -The :math:`\chi^2` error at iteration :math:`k+1` is - -.. math:: \chi_{k+1}^2 = \sum_{e_j \in \mathcal{E}} \underbrace{\left[ \mathbf{e}_j(\mathbf{x}^{k+1}) \right]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\mathbf{e}_j(\mathbf{x}^{k+1})}_{\bullet \times 1}. - :label: chisq_at_kplusone - -We will linearize the residuals as: - -.. math:: - \begin{aligned} - \mathbf{e}_j(\mathbf{x}^{k+1}) &= \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \\ - &\approx \mathbf{e}_j(\mathbf{x}^{k}) + \frac{\partial}{\partial \Delta \mathbf{x}^k} \left[ \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \right] \Delta \mathbf{x}^k \\ - &= \mathbf{e}_j(\mathbf{x}^{k}) + \left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right) \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \Delta \mathbf{x}^k. - \end{aligned} - :label: linearization - -Plugging :eq:`linearization` into :eq:`chisq_at_kplusone`, we get: - -.. math:: - - \begin{aligned} - \chi_{k+1}^2 &\approx \ \ \ \ \ \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x}^k)]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\mathbf{e}_j(\mathbf{x}^k)}_{\bullet \times 1} \notag \\ - &\hphantom{\approx} \ \ \ + \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x^k}) ]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \underbrace{\Delta \mathbf{x}^k}_{cN \times 1} \notag \\ - &\hphantom{\approx} \ \ \ + \sum_{e_j \in \mathcal{E}} \underbrace{(\Delta \mathbf{x}^k)^{\scriptstyle{\mathsf{T}}}}_{1 \times cN} \underbrace{ \left( \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \right)^{\scriptstyle{\mathsf{T}}}}_{cN \times dN} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)^{\scriptstyle{\mathsf{T}}}}_{dN \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \underbrace{\Delta \mathbf{x}^k}_{cN \times 1} \notag \\ - &= \chi_k^2 + 2 \mathbf{b}^{\scriptstyle{\mathsf{T}}}\Delta \mathbf{x}^k + (\Delta \mathbf{x}^k)^{\scriptstyle{\mathsf{T}}}H \Delta \mathbf{x}^k, \notag\end{aligned} - -where - -.. math:: - - \begin{aligned} - \mathbf{b}^{\scriptstyle{\mathsf{T}}}&= \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x^k}) ]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \\ - H &= \sum_{e_j \in \mathcal{E}} \underbrace{ \left( \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \right)^{\scriptstyle{\mathsf{T}}}}_{cN \times dN} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)^{\scriptstyle{\mathsf{T}}}}_{dN \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN}.\end{aligned} - -Using this notation, we obtain the optimal update as - -.. math:: \Delta \mathbf{x}^k = -H^{-1} \mathbf{b}. \label{eq:deltax} - -We apply this update to the poses via :eq:`update` and repeat until convergence. - - -.. [blanco2010tutorial] Blanco, J.-L.A tutorial onSE(3) transformation parameterization and on-manifold optimization.University of Malaga, Tech. Rep 3(2010) -.. [grisetti2010tutorial] Grisetti, G., Kummerle, R., Stachniss, C., and Burgard, W.A tutorial on graph-based SLAM.IEEE Intelligent Transportation Systems Magazine 2, 4 (2010), 31–43. - diff --git a/docs/modules/4_slam/slam_main.rst b/docs/modules/4_slam/slam_main.rst deleted file mode 100644 index 98211986c2..0000000000 --- a/docs/modules/4_slam/slam_main.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _`SLAM`: - -SLAM -==== - -Simultaneous Localization and Mapping(SLAM) examples -Simultaneous Localization and Mapping (SLAM) is an ability to estimate the pose of a robot and the map of the environment at the same time. The SLAM problem is hard to -solve, because a map is needed for localization and localization is needed for mapping. In this way, SLAM is often said to be similar to a ‘chicken-and-egg’ problem. Popular SLAM solution methods include the extended Kalman filter, particle filter, and Fast SLAM algorithm[31]. Fig.4 shows SLAM simulation results using extended Kalman filter and results using FastSLAM2.0[31]. - -.. toctree:: - :maxdepth: 2 - :caption: Contents - - iterative_closest_point_matching/iterative_closest_point_matching - ekf_slam/ekf_slam - FastSLAM1/FastSLAM1 - FastSLAM2/FastSLAM2 - graph_slam/graph_slam diff --git a/docs/modules/5_path_planning/bugplanner/bugplanner_main.rst b/docs/modules/5_path_planning/bugplanner/bugplanner_main.rst deleted file mode 100644 index e1cd2fe353..0000000000 --- a/docs/modules/5_path_planning/bugplanner/bugplanner_main.rst +++ /dev/null @@ -1,16 +0,0 @@ -Bug planner ------------ - -This is a 2D planning with Bug algorithm. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/BugPlanner/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.BugPlanning.bug.main - -Reference -~~~~~~~~~~~~ - -- `ECE452 Bug Algorithms `_ diff --git a/docs/modules/5_path_planning/catmull_rom_spline/blending_functions.png b/docs/modules/5_path_planning/catmull_rom_spline/blending_functions.png deleted file mode 100644 index 928946df46..0000000000 Binary files a/docs/modules/5_path_planning/catmull_rom_spline/blending_functions.png and /dev/null differ diff --git a/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_path_planning.png b/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_path_planning.png deleted file mode 100644 index 1d0ff001e6..0000000000 Binary files a/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_path_planning.png and /dev/null differ diff --git a/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_spline_main.rst b/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_spline_main.rst deleted file mode 100644 index fa2a2ff72b..0000000000 --- a/docs/modules/5_path_planning/catmull_rom_spline/catmull_rom_spline_main.rst +++ /dev/null @@ -1,103 +0,0 @@ -Catmull-Rom Spline Planning ----------------------------- - -.. image:: catmull_rom_path_planning.png - -This is a Catmull-Rom spline path planning routine. - -If you provide waypoints, the Catmull-Rom spline generates a smooth path that always passes through the control points, -exhibits local control, and maintains 𝐶1 continuity. - - -Catmull-Rom Spline Fundamentals -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Catmull-Rom splines are a type of cubic spline that passes through a given set of points, known as control points. - -They are defined by the following equation for calculating a point on the spline: - -:math:`P(t) = 0.5 \times \left( 2P_1 + (-P_0 + P_2)t + (2P_0 - 5P_1 + 4P_2 - P_3)t^2 + (-P_0 + 3P_1 - 3P_2 + P_3)t^3 \right)` - -Where: - -* :math:`P(t)` is the point on the spline at parameter :math:`t`. -* :math:`P_0, P_1, P_2, P_3` are the control points surrounding the parameter :math:`t`. - -Types of Catmull-Rom Splines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are different types of Catmull-Rom splines based on the choice of the **tau** parameter, which influences how the curve -behaves in relation to the control points: - -1. **Uniform Catmull-Rom Spline**: - The standard implementation where the parameterization is uniform. Each segment of the spline is treated equally, - regardless of the distances between control points. - - -2. **Chordal Catmull-Rom Spline**: - This spline type takes into account the distance between control points. The parameterization is based on the actual distance - along the spline, ensuring smoother transitions. The equation can be modified to include the chord length :math:`L_i` between - points :math:`P_i` and :math:`P_{i+1}`: - - .. math:: - \tau_i = \sqrt{(x_{i+1} - x_i)^2 + (y_{i+1} - y_i)^2} - -3. **Centripetal Catmull-Rom Spline**: - This variation improves upon the chordal spline by using the square root of the distance to determine the parameterization, - which avoids oscillations and creates a more natural curve. The parameter :math:`t_i` is adjusted using the following relation: - - .. math:: - t_i = \sqrt{(x_{i+1} - x_i)^2 + (y_{i+1} - y_i)^2} - - -Blending Functions -~~~~~~~~~~~~~~~~~~~~~ - -In Catmull-Rom spline interpolation, blending functions are used to calculate the influence of each control point on the spline at a -given parameter :math:`t`. The blending functions ensure that the spline is smooth and passes through the control points while -maintaining continuity. The four blending functions used in Catmull-Rom splines are defined as follows: - -1. **Blending Function 1**: - - .. math:: - b_1(t) = -t + 2t^2 - t^3 - -2. **Blending Function 2**: - - .. math:: - b_2(t) = 2 - 5t^2 + 3t^3 - -3. **Blending Function 3**: - - .. math:: - b_3(t) = t + 4t^2 - 3t^3 - -4. **Blending Function 4**: - - .. math:: - b_4(t) = -t^2 + t^3 - -The blending functions are combined in the spline equation to create a smooth curve that reflects the influence of each control point. - -The following figure illustrates the blending functions over the interval :math:`[0, 1]`: - -.. image:: blending_functions.png - -Catmull-Rom Spline API -~~~~~~~~~~~~~~~~~~~~~~~ - -This section provides an overview of the functions used for Catmull-Rom spline path planning. - -Code Link -++++++++++ - -.. autofunction:: PathPlanning.Catmull_RomSplinePath.catmull_rom_spline_path.catmull_rom_point - -.. autofunction:: PathPlanning.Catmull_RomSplinePath.catmull_rom_spline_path.catmull_rom_spline - - -References -~~~~~~~~~~~~~~ - -- `Catmull-Rom Spline - Wikipedia `__ -- `Catmull-Rom Splines `__ \ No newline at end of file diff --git a/docs/modules/5_path_planning/elastic_bands/elastic_bands_main.rst b/docs/modules/5_path_planning/elastic_bands/elastic_bands_main.rst deleted file mode 100644 index d0109d4ec3..0000000000 --- a/docs/modules/5_path_planning/elastic_bands/elastic_bands_main.rst +++ /dev/null @@ -1,79 +0,0 @@ -Elastic Bands -------------- - -This is a path planning with Elastic Bands. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ElasticBands/animation.gif - -Code Link -+++++++++++++ - -.. autoclass:: PathPlanning.ElasticBands.elastic_bands.ElasticBands - - -Core Concept -~~~~~~~~~~~~ -- **Elastic Band**: A dynamically deformable collision-free path initialized by a global planner. -- **Objective**: - - * Shorten and smooth the path. - * Maximize obstacle clearance. - * Maintain global path connectivity. - -Bubble Representation -~~~~~~~~~~~~~~~~~~~~~~~~ -- **Definition**: A local free-space region around configuration :math:`b`: - - .. math:: - B(b) = \{ q: \|q - b\| < \rho(b) \}, - - where :math:`\rho(b)` is the radius of the bubble. - - -Force-Based Deformation -~~~~~~~~~~~~~~~~~~~~~~~ -The elastic band deforms under artificial forces: - -Internal Contraction Force -++++++++++++++++++++++++++ -- **Purpose**: Reduces path slack and length. -- **Formula**: For node :math:`b_i`: - - .. math:: - f_c(b_i) = k_c \left( \frac{b_{i-1} - b_i}{\|b_{i-1} - b_i\|} + \frac{b_{i+1} - b_i}{\|b_{i+1} - b_i\|} \right) - - where :math:`k_c` is the contraction gain. - -External Repulsion Force -+++++++++++++++++++++++++ -- **Purpose**: Pushes the path away from obstacles. -- **Formula**: For node :math:`b_i`: - - .. math:: - f_r(b_i) = \begin{cases} - k_r (\rho_0 - \rho(b_i)) \nabla \rho(b_i) & \text{if } \rho(b_i) < \rho_0, \\ - 0 & \text{otherwise}. - \end{cases} - - where :math:`k_r` is the repulsion gain, :math:`\rho_0` is the maximum distance for applying repulsion force, and :math:`\nabla \rho(b_i)` is approximated via finite differences: - - .. math:: - \frac{\partial \rho}{\partial x} \approx \frac{\rho(b_i + h) - \rho(b_i - h)}{2h}. - -Dynamic Path Maintenance -~~~~~~~~~~~~~~~~~~~~~~~~~~ -1. **Node Update**: - - .. math:: - b_i^{\text{new}} = b_i^{\text{old}} + \alpha (f_c + f_r), - - where :math:`\alpha` is a step-size parameter, which often proportional to :math:`\rho(b_i^{\text{old}})` - -2. **Overlap Enforcement**: -- Insert new nodes if adjacent nodes are too far apart -- Remove redundant nodes if adjacent nodes are too close - -References -+++++++++++++ - -- `Elastic Bands: Connecting Path Planning and Control `__ diff --git a/docs/modules/5_path_planning/frenet_frame_path/frenet_frame_path_main.rst b/docs/modules/5_path_planning/frenet_frame_path/frenet_frame_path_main.rst deleted file mode 100644 index 0f84d381ea..0000000000 --- a/docs/modules/5_path_planning/frenet_frame_path/frenet_frame_path_main.rst +++ /dev/null @@ -1,52 +0,0 @@ -Optimal Trajectory in a Frenet Frame ------------------------------------- - -This is optimal trajectory generation in a Frenet Frame. - -The cyan line is the target course and black crosses are obstacles. - -The red line is predicted path. - -Code Link -~~~~~~~~~~~~~~ - -.. autofunction:: PathPlanning.FrenetOptimalTrajectory.frenet_optimal_trajectory.main - - -High Speed and Velocity Keeping Scenario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/high_speed_and_velocity_keeping_frenet_path.gif - -This scenario shows how the trajectory is maintained at high speeds while keeping a consistent velocity. - -High Speed and Merging and Stopping Scenario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/high_speed_and_merging_and_stopping_frenet_path.gif - -This scenario demonstrates the trajectory planning at high speeds with merging and stopping behaviors. - -Low Speed and Velocity Keeping Scenario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/low_speed_and_velocity_keeping_frenet_path.gif - -This scenario demonstrates how the trajectory is managed at low speeds while maintaining a steady velocity. - -Low Speed and Merging and Stopping Scenario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/low_speed_and_merging_and_stopping_frenet_path.gif - -This scenario illustrates the trajectory planning at low speeds with merging and stopping behaviors. - -Reference - -- `Optimal Trajectory Generation for Dynamic Street Scenarios in a - Frenet - Frame `__ - -- `Optimal trajectory generation for dynamic street scenarios in a - Frenet Frame `__ - diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LR_L.png b/docs/modules/5_path_planning/reeds_shepp_path/LR_L.png deleted file mode 100644 index 1e64da57f2..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LR_L.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LR_LR.png b/docs/modules/5_path_planning/reeds_shepp_path/LR_LR.png deleted file mode 100644 index e2c9883d8e..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LR_LR.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LSL.png b/docs/modules/5_path_planning/reeds_shepp_path/LSL.png deleted file mode 100644 index 6785ad3f8c..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LSL.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LSL90xR.png b/docs/modules/5_path_planning/reeds_shepp_path/LSL90xR.png deleted file mode 100644 index 54e892ba46..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LSL90xR.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LSR.png b/docs/modules/5_path_planning/reeds_shepp_path/LSR.png deleted file mode 100644 index 8acc0de69f..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LSR.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/LSR90_L.png b/docs/modules/5_path_planning/reeds_shepp_path/LSR90_L.png deleted file mode 100644 index 58d381010d..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/LSR90_L.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL.png b/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL.png deleted file mode 100644 index c305c0be6e..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL90_R.png b/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL90_R.png deleted file mode 100644 index f2b38329da..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SL90_R.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SR.png b/docs/modules/5_path_planning/reeds_shepp_path/L_R90SR.png deleted file mode 100644 index 4323a9fe3b..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_R90SR.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_RL.png b/docs/modules/5_path_planning/reeds_shepp_path/L_RL.png deleted file mode 100644 index ad58b8ffea..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_RL.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_RL_R.png b/docs/modules/5_path_planning/reeds_shepp_path/L_RL_R.png deleted file mode 100644 index db4aaf6af3..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_RL_R.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/L_R_L.png b/docs/modules/5_path_planning/reeds_shepp_path/L_R_L.png deleted file mode 100644 index 0d3082aeaf..0000000000 Binary files a/docs/modules/5_path_planning/reeds_shepp_path/L_R_L.png and /dev/null differ diff --git a/docs/modules/5_path_planning/reeds_shepp_path/reeds_shepp_path_main.rst b/docs/modules/5_path_planning/reeds_shepp_path/reeds_shepp_path_main.rst deleted file mode 100644 index 4dd54d7c97..0000000000 --- a/docs/modules/5_path_planning/reeds_shepp_path/reeds_shepp_path_main.rst +++ /dev/null @@ -1,399 +0,0 @@ -Reeds Shepp planning --------------------- - -A sample code with Reeds Shepp path planning. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ReedsSheppPath/animation.gif?raw=true - -Code Link -============== - -.. autofunction:: PathPlanning.ReedsSheppPath.reeds_shepp_path_planning.reeds_shepp_path_planning - - -Mathematical Description of Individual Path Types -================================================= -Here is an overview of mathematical derivations of formulae for individual path types. - -In all the derivations below, radius of curvature of the vehicle is assumed to be of unit length and start pose is considered to be at origin. (*In code we are removing the offset due to start position and normalising the lengths before passing the values to these functions.*) - -Also, (t, u, v) respresent the measure of each motion requried. Thus, in case of a turning maneuver, they represent the angle inscribed at the centre of turning circle and in case of straight maneuver, they represent the distance to be travelled. - -1. **Left-Straight-Left** - -.. image:: LSL.png - -We can deduce the following facts using geometry. - -- AGHC is a rectangle. -- :math:`∠LAC = ∠BAG = t` -- :math:`t + v = φ` -- :math:`C(x - sin(φ), y + cos(φ))` -- :math:`A(0, 1)` -- :math:`u, t = polar(vector)` - -Hence, we have: - -- :math:`u, t = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`v = φ - t` - - -2. **Left-Straight-Right** - -.. image:: LSR.png - -With followng notations: - -- :math:`∠MBD = t1` -- :math:`∠BDF = θ` -- :math:`BC = u1` - -We can deduce the following facts using geometry. - -- D is mid-point of BC and FG. -- :math:`t - v = φ` -- :math:`C(x + sin(φ), y - cos(φ))` -- :math:`A(0, 1)` -- :math:`u1, t1 = polar(vector)` -- :math:`\frac{u1^2}{4} = 1 + \frac{u^2}{4}` -- :math:`BF = 1` [Radius Of Curvature] -- :math:`FD = \frac{u}{2}` -- :math:`θ = arctan(\frac{BF}{FD})` -- :math:`t1 + θ = t` - -Hence, we have: - -- :math:`u1, t1 = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = \sqrt{u1^2 - 4}` -- :math:`θ = arctan(\frac{2}{u})` -- :math:`t = t1 + θ` -- :math:`v = t - φ` - -3. **LeftxRightxLeft** - -.. image:: L_R_L.png - -With followng notations: - -- :math:`∠CBD = ∠CDB = A` [BCD is an isoceles triangle] -- :math:`∠DBK = θ` -- :math:`BD = u1` - -We can deduce the following facts using geometry. - -- :math:`t + u + v = φ` -- :math:`D(x - sin(φ), y + cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`A = arccos(\frac{BD/2}{CD})` -- :math:`u = (π - 2*A)` -- :math:`∠ABK = \frac{π}{2}` -- :math:`∠KBD = θ` -- :math:`t = ∠ABK + ∠KBD + ∠DBC` - -Hence, we have: - -- :math:`u1, θ = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`A = arccos(\frac{u1/2}{2})` -- :math:`t = \frac{π}{2} + θ + A` -- :math:`u = (π - 2*A)` -- :math:`v = (φ - t - u)` - -4. **LeftxRight-Left** - -.. image:: L_RL.png - -With followng notations: - -- :math:`∠CBD = ∠CDB = A` [BCD is an isoceles triangle] -- :math:`∠DBK = θ` -- :math:`BD = u1` - -We can deduce the following facts using geometry. - -- :math:`t + u - v = φ` -- :math:`D(x - sin(φ), y + cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`A = arccos(\frac{BD/2}{CD})` -- :math:`u = (π - 2*A)` -- :math:`∠ABK = \frac{π}{2}` -- :math:`∠KBD = θ` -- :math:`t = ∠ABK + ∠KBD + ∠DBC` - -Hence, we have: - -- :math:`u1, θ = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`A = arccos(\frac{u1/2}{2})` -- :math:`t = \frac{π}{2} + θ + A` -- :math:`u = (π - 2*A)` -- :math:`v = (-φ + t + u)` - -5. **Left-RightxLeft** - -.. image:: LR_L.png - -With followng notations: - -- :math:`∠CBD = ∠CDB = A` [BCD is an isoceles triangle] -- :math:`∠DBK = θ` -- :math:`BD = u1` - -We can deduce the following facts using geometry. - -- :math:`t - u - v = φ` -- :math:`D(x - sin(φ), y + cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`BC = CD = 2` [2 * radius of curvature] -- :math:`cos(2π - u) = \frac{BC^2 + CD^2 - BD^2}{2 * BC * CD}` [Cosine Rule] -- :math:`\frac{sin(A)}{BC} = \frac{sin(u)}{u1}` [Sine Rule] -- :math:`∠ABK = \frac{π}{2}` -- :math:`∠KBD = θ` -- :math:`t = ∠ABK + ∠KBD - ∠DBC` - -Hence, we have: - -- :math:`u1, θ = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`u = arccos(1 - \frac{u1^2}{8})` -- :math:`A = arcsin(\frac{sin(u)}{u1}*2)` -- :math:`t = \frac{π}{2} + θ - A` -- :math:`v = (t - u - φ)` - -6. **Left-RightxLeft-Right** - -.. image:: LR_LR.png - -With followng notations: - -- :math:`∠CLG = ∠BCL = ∠CBG = ∠LGB = A = u` [BGCL is an isoceles trapezium] -- :math:`∠KBG = θ` -- :math:`BG = u1` - -We can deduce the following facts using geometry. - -- :math:`t - 2u + v = φ` -- :math:`G(x + sin(φ), y - cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`BC = CL = LG = 2` [2 * radius of curvature] -- :math:`CG^2 = CL^2 + LG^2 - 2*CL*LG*cos(A)` [Cosine rule in LGC] -- :math:`CG^2 = CL^2 + LG^2 - 2*CL*LG*cos(A)` [Cosine rule in LGC] -- From the previous two equations: :math:`A = arccos(\frac{u1 + 2}{4})` -- :math:`∠ABK = \frac{π}{2}` -- :math:`t = ∠ABK + ∠KBG + ∠GBC` - -Hence, we have: - -- :math:`u1, θ = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = arccos(\frac{u1 + 2}{4})` -- :math:`t = \frac{π}{2} + θ + u` -- :math:`v = (φ - t + 2u)` - -7. **LeftxRight-LeftxRight** - -.. image:: L_RL_R.png - -With followng notations: - -- :math:`∠GBC = A` [BGCL is an isoceles trapezium] -- :math:`∠KBG = θ` -- :math:`BG = u1` - -We can deduce the following facts using geometry. - -- :math:`t - v = φ` -- :math:`G(x + sin(φ), y - cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`BC = CL = LG = 2` [2 * radius of curvature] -- :math:`CD = 1` [radius of curvature] -- D is midpoint of BG -- :math:`BD = \frac{u1}{2}` -- :math:`cos(u) = \frac{BC^2 + CD^2 - BD^2}{2*BC*CD}` [Cosine rule in BCD] -- :math:`sin(A) = CD*\frac{sin(u)}{BD}` [Sine rule in BCD] -- :math:`∠ABK = \frac{π}{2}` -- :math:`t = ∠ABK + ∠KBG + ∠GBC` - -Hence, we have: - -- :math:`u1, θ = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = arccos(\frac{20 - u1^2}{16})` -- :math:`A = arcsin(2*\frac{sin(u)}{u1})` -- :math:`t = \frac{π}{2} + θ + A` -- :math:`v = (t - φ)` - - -8. **LeftxRight90-Straight-Left** - -.. image:: L_R90SL.png - -With followng notations: - -- :math:`∠FBM = A` [BGCL is an isoceles trapezium] -- :math:`∠KBF = θ` -- :math:`BF = u1` - -We can deduce the following facts using geometry. - -- :math:`t + \frac{π}{2} - v = φ` -- :math:`F(x - sin(φ), y + cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`BM = CB = 2` [2 * radius of curvature] -- :math:`MD = CD = 1` [CGDM is a rectangle] -- :math:`MC = GD = u` [CGDM is a rectangle] -- :math:`MF = MD + DF = 2` -- :math:`BM = \sqrt{BF^2 - MF^2}` [Pythagoras theorem on BFM] -- :math:`tan(A) = \frac{MF}{BM}` -- :math:`u = MC = BM - CB` -- :math:`t = ∠ABK + ∠KBF + ∠FBC` - -Hence, we have: - -- :math:`u1, θ = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`u = arccos(\sqrt{u1^2 - 4} - 2)` -- :math:`A = arctan(\frac{2}{\sqrt{u1^2 - 4}})` -- :math:`t = \frac{π}{2} + θ + A` -- :math:`v = (t - φ + \frac{π}{2})` - - -9. **Left-Straight-Right90xLeft** - -.. image:: LSR90_L.png - -With followng notations: - -- :math:`∠MBH = A` [BGCL is an isoceles trapezium] -- :math:`∠KBH = θ` -- :math:`BH = u1` - -We can deduce the following facts using geometry. - -- :math:`t - \frac{π}{2} - v = φ` -- :math:`H(x - sin(φ), y + cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`GH = 2` [2 * radius of curvature] -- :math:`CM = DG = 1` [CGDM is a rectangle] -- :math:`CD = MG = u` [CGDM is a rectangle] -- :math:`BM = BC + CM = 2` -- :math:`MH = \sqrt{BH^2 - BM^2}` [Pythagoras theorem on BHM] -- :math:`tan(A) = \frac{HM}{BM}` -- :math:`u = MC = BM - CB` -- :math:`t = ∠ABK + ∠KBH - ∠HBC` - -Hence, we have: - -- :math:`u1, θ = polar(x - sin(φ), y + cos(φ) - 1)` -- :math:`u = arccos(\sqrt{u1^2 - 4} - 2)` -- :math:`A = arctan(\frac{2}{\sqrt{u1^2 - 4}})` -- :math:`t = \frac{π}{2} + θ - A` -- :math:`v = (t - φ - \frac{π}{2})` - - -10. **LeftxRight90-Straight-Right** - -.. image:: L_R90SR.png - -With followng notations: - -- :math:`∠KBG = θ` -- :math:`BG = u1` - -We can deduce the following facts using geometry. - -- :math:`t - \frac{π}{2} - v = φ` -- :math:`G(x + sin(φ), y - cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`BD = 2` [2 * radius of curvature] -- :math:`DG = EF = u` [DGFE is a rectangle] -- :math:`DG = BG - BD = 2` -- :math:`∠ABK = \frac{π}{2}` -- :math:`t = ∠ABK + ∠KBG` - -Hence, we have: - -- :math:`u1, θ = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = u1 - 2` -- :math:`t = \frac{π}{2} + θ` -- :math:`v = (t - φ - \frac{π}{2})` - - -11. **Left-Straight-Left90xRight** - -.. image:: LSL90xR.png - -With followng notations: - -- :math:`∠KBH = θ` -- :math:`BH = u1` - -We can deduce the following facts using geometry. - -- :math:`t + \frac{π}{2} + v = φ` -- :math:`H(x + sin(φ), y - cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`GH = 2` [2 * radius of curvature] -- :math:`DC = BG = u` [DGBC is a rectangle] -- :math:`BG = BH - GH` -- :math:`∠ABC= ∠KBH` - -Hence, we have: - -- :math:`u1, θ = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = u1 - 2` -- :math:`t = θ` -- :math:`v = (φ - t - \frac{π}{2})` - - -12. **LeftxRight90-Straight-Left90xRight** - -.. image:: L_R90SL90_R.png - -With followng notations: - -- :math:`∠KBH = θ` -- :math:`∠HBM = A` -- :math:`BH = u1` - -We can deduce the following facts using geometry. - -- :math:`t - v = φ` -- :math:`H(x + sin(φ), y - cos(φ))` -- :math:`B(0, 1)` -- :math:`u1, θ = polar(vector)` -- :math:`GF = ED = 1` [radius of curvature] -- :math:`BD = GH = 2` [2 * radius of curvature] -- :math:`FN = GH = 2` [ENMD is a rectangle] -- :math:`NH = GF = 1` [FNHG is a rectangle] -- :math:`MN = ED = 1` [ENMD is a rectangle] -- :math:`DO = EF = u` [DOFE is a rectangle] -- :math:`MH = MN + NH = 2` -- :math:`BM = \sqrt{BH^2 - MH^2}` [Pythagoras theorem on BHM] -- :math:`DO = BM - BD - OM` -- :math:`tan(A) = \frac{MH}{BM}` -- :math:`∠ABC = ∠ABK + ∠KBH + ∠HBM` - -Hence, we have: - -- :math:`u1, θ = polar(x + sin(φ), y - cos(φ) - 1)` -- :math:`u = /sqrt{u1^2 - 4} - 4` -- :math:`A = arctan(\frac{2}{u1^2 - 4})` -- :math:`t = \frac{π}{2} + θ + A` -- :math:`v = (t - φ)` - - -Reference -============= - -- `15.3.2 Reeds-Shepp - Curves `__ - -- `optimal paths for a car that goes both forwards and - backwards `__ - -- `ghliu/pyReedsShepp: Implementation of Reeds Shepp - curve. `__ diff --git a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_1.png b/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_1.png deleted file mode 100644 index 9b0334a665..0000000000 Binary files a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_1.png and /dev/null differ diff --git a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_3.png b/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_3.png deleted file mode 100644 index 8b746e00a0..0000000000 Binary files a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_3.png and /dev/null differ diff --git a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_4.png b/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_4.png deleted file mode 100644 index 2c9d829227..0000000000 Binary files a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_4.png and /dev/null differ diff --git a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_5.png b/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_5.png deleted file mode 100644 index eb2021e40c..0000000000 Binary files a/docs/modules/5_path_planning/rrt/closed_loop_rrt_star_car/Figure_5.png and /dev/null differ diff --git a/docs/modules/5_path_planning/rrt/figure_1.png b/docs/modules/5_path_planning/rrt/figure_1.png deleted file mode 100644 index 959a28b482..0000000000 Binary files a/docs/modules/5_path_planning/rrt/figure_1.png and /dev/null differ diff --git a/docs/modules/5_path_planning/rrt/rrt_star.rst b/docs/modules/5_path_planning/rrt/rrt_star.rst deleted file mode 100644 index 960b9976d5..0000000000 --- a/docs/modules/5_path_planning/rrt/rrt_star.rst +++ /dev/null @@ -1,27 +0,0 @@ -RRT\* -~~~~~ - -.. figure:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTstar/animation.gif - -This is a path planning code with RRT\* - -Black circles are obstacles, green line is a searched tree, red crosses are start and goal positions. - -Code Link -^^^^^^^^^^ - -.. autoclass:: PathPlanning.RRTStar.rrt_star.RRTStar - - -Simulation -^^^^^^^^^^ - -.. image:: rrt_star/rrt_star_1_0.png - :width: 600px - - -Ref -^^^ -- `Sampling-based Algorithms for Optimal Motion Planning `__ -- `Incremental Sampling-based Algorithms for Optimal Motion Planning `__ - diff --git a/docs/modules/5_path_planning/rrt/rrt_star_reeds_shepp/figure_1.png b/docs/modules/5_path_planning/rrt/rrt_star_reeds_shepp/figure_1.png deleted file mode 100644 index 2dcbe258c5..0000000000 Binary files a/docs/modules/5_path_planning/rrt/rrt_star_reeds_shepp/figure_1.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_1.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_1.png deleted file mode 100644 index 259b2dfa41..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_1.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_2.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_2.png deleted file mode 100644 index 6e9b4758cd..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_2.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_3.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_3.png deleted file mode 100644 index 09a4909dd1..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_3.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_4.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_4.png deleted file mode 100644 index 3c468efd57..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_4.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_5.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_5.png deleted file mode 100644 index 0f45d93081..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_5.png and /dev/null differ diff --git a/docs/modules/5_path_planning/state_lattice_planner/Figure_6.png b/docs/modules/5_path_planning/state_lattice_planner/Figure_6.png deleted file mode 100644 index d1a3c9945d..0000000000 Binary files a/docs/modules/5_path_planning/state_lattice_planner/Figure_6.png and /dev/null differ diff --git a/docs/modules/5_path_planning/time_based_grid_search/time_based_grid_search_main.rst b/docs/modules/5_path_planning/time_based_grid_search/time_based_grid_search_main.rst deleted file mode 100644 index a44dd20a97..0000000000 --- a/docs/modules/5_path_planning/time_based_grid_search/time_based_grid_search_main.rst +++ /dev/null @@ -1,108 +0,0 @@ -Time based grid search ----------------------- - -Space-time astar -~~~~~~~~~~~~~~~~~~~~~~ - -This is an extension of A* algorithm that supports planning around dynamic obstacles. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/TimeBasedPathPlanning/SpaceTimeAStar/path_animation.gif - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/TimeBasedPathPlanning/SpaceTimeAStar/path_animation2.gif - -The key difference of this algorithm compared to vanilla A* is that the cost and heuristic are now time-based instead of distance-based. -Using a time-based cost and heuristic ensures the path found is optimal in terms of time to reach the goal. - -The cost is the amount of time it takes to reach a given node, and the heuristic is the minimum amount of time it could take to reach the goal from that node, disregarding all obstacles. -For a simple scenario where the robot can move 1 cell per time step and stop and go as it pleases, the heuristic for time is equivalent to the heuristic for distance. - -One optimization that was added in `this PR `__ was to add an expanded set to the algorithm. The algorithm will not expand nodes that are already in that set. This greatly reduces the number of node expansions needed to find a path, since no duplicates are expanded. It also helps to reduce the amount of memory the algorithm uses. - -Before:: - - Found path to goal after 204490 expansions - Planning took: 1.72464 seconds - Memory usage (RSS): 68.19 MB - - -After:: - - Found path to goal after 2348 expansions - Planning took: 0.01550 seconds - Memory usage (RSS): 64.85 MB - -When starting at (1, 11) in the structured obstacle arrangement (second of the two gifs above). - -Code Link -^^^^^^^^^^^^^ -.. autoclass:: PathPlanning.TimeBasedPathPlanning.SpaceTimeAStar.SpaceTimeAStar - - -Safe Interval Path Planning -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The safe interval path planning algorithm is described in this paper: - -`SIPP: Safe Interval Path Planning for Dynamic Environments `__ - -It is faster than space-time A* because it pre-computes the intervals of time that are unoccupied in each cell. This allows it to reduce the number of successor node it generates by avoiding nodes within the same interval. - -**Comparison with SpaceTime A*:** - -Arrangement 1 starting at (1, 18) - -SafeInterval planner:: - - Found path to goal after 322 expansions - Planning took: 0.00730 seconds - -SpaceTime A*:: - - Found path to goal after 2717154 expansions - Planning took: 20.51330 seconds - -**Benchmarking the Safe Interval Path Planner:** - -250 random obstacles:: - - Found path to goal after 764 expansions - Planning took: 0.60596 seconds - -.. image:: https://raw.githubusercontent.com/AtsushiSakai/PythonRoboticsGifs/refs/heads/master/PathPlanning/TimeBasedPathPlanning/SafeIntervalPathPlanner/path_animation.gif - -Arrangement 1 starting at (1, 18):: - - Found path to goal after 322 expansions - Planning took: 0.00730 seconds - -.. image:: https://raw.githubusercontent.com/AtsushiSakai/PythonRoboticsGifs/refs/heads/master/PathPlanning/TimeBasedPathPlanning/SafeIntervalPathPlanner/path_animation2.gif - -Code Link -^^^^^^^^^^^^^ -.. autoclass:: PathPlanning.TimeBasedPathPlanning.SafeInterval.SafeIntervalPathPlanner - -Multi-Agent Path Planning -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Priority Based Planning -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The planner generates an order to plan in, and generates plans for the robots in that order. Each planned path is reserved in the grid, and all future plans must avoid that path. The robots are planned for in descending order of distance from start to goal. - -This is a sub-optimal algorithm because no replanning happens once paths are found. It does not guarantee the shortest path is found for any particular robot, nor that the final set of paths found contains the lowest possible amount of time or movement. - -Static Obstacles: - -.. image:: https://raw.githubusercontent.com/AtsushiSakai/PythonRoboticsGifs/refs/heads/master/PathPlanning/TimeBasedPathPlanning/PriorityBasedPlanner/priority_planner2.gif - -Dynamic Obstacles: - -.. image:: https://raw.githubusercontent.com/AtsushiSakai/PythonRoboticsGifs/refs/heads/master/PathPlanning/TimeBasedPathPlanning/PriorityBasedPlanner/priority_planner.gif - - -References -~~~~~~~~~~~ - -- `Cooperative Pathfinding `__ -- `SIPP: Safe Interval Path Planning for Dynamic Environments `__ -- `Prioritized Planning Algorithms for Trajectory Coordination of Multiple Mobile Robots `__ \ No newline at end of file diff --git a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst b/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst deleted file mode 100644 index b0ba9e0d45..0000000000 --- a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst +++ /dev/null @@ -1,145 +0,0 @@ -.. _linearquadratic-regulator-(lqr)-speed-and-steering-control: - -Linear–quadratic regulator (LQR) speed and steering control ------------------------------------------------------------ - -Path tracking simulation with LQR speed and steering control. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_speed_steer_control/animation.gif - - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.lqr_speed_steer_control.lqr_speed_steer_control.lqr_speed_steering_control - - -Overview -~~~~~~~~ - -The LQR (Linear Quadratic Regulator) speed and steering control model implemented in `lqr_speed_steer_control.py` provides a simulation -for an autonomous vehicle to track: - -1. A desired speed by adjusting acceleration based on feedback from the current state and the desired speed. - -2. A desired trajectory by adjusting steering angle based on feedback from the current state and the desired trajectory. - -by only using one LQT controller. - -Vehicle motion Model -~~~~~~~~~~~~~~~~~~~~~ - -The below figure shows the geometric model of the vehicle used in this simulation: - -.. image:: lqr_steering_control_model.jpg - :width: 600px - -The `e`, :math:`{\theta}`, and :math:`\upsilon` represent the lateral error, orientation error, and velocity error, respectively, with respect to the desired trajectory and speed. -And :math:`\dot{e}` and :math:`\dot{\theta}` represent the rates of change of these errors. - -The :math:`e_t` and :math:`\theta_t`, and :math:`\upsilon` are the updated values of `e`, :math:`\theta`, :math:`\upsilon` and at time `t`, respectively, and can be calculated using the following kinematic equations: - -.. math:: e_t = e_{t-1} + \dot{e}_{t-1} dt - -.. math:: \theta_t = \theta_{t-1} + \dot{\theta}_{t-1} dt - -.. math:: \upsilon_t = \upsilon_{t-1} + a_{t-1} dt - -Where `dt` is the time difference and :math:`a_t` is the acceleration at the time `t`. - -The change rate of the `e` can be calculated as: - -.. math:: \dot{e}_t = V \sin(\theta_{t-1}) - -Where `V` is the current vehicle speed. - -If the :math:`\theta` is small, - -.. math:: \theta \approx 0 - -the :math:`\sin(\theta)` can be approximated as :math:`\theta`: - -.. math:: \sin(\theta) = \theta - -So, the change rate of the `e` can be approximated as: - -.. math:: \dot{e}_t = V \theta_{t-1} - -The change rate of the :math:`\theta` can be calculated as: - -.. math:: \dot{\theta}_t = \frac{V}{L} \tan(\delta) - -where `L` is the wheelbase of the vehicle and :math:`\delta` is the steering angle. - -If the :math:`\delta` is small, - -.. math:: \delta \approx 0 - -the :math:`\tan(\delta)` can be approximated as :math:`\delta`: - -.. math:: \tan(\delta) = \delta - -So, the change rate of the :math:`\theta` can be approximated as: - -.. math:: \dot{\theta}_t = \frac{V}{L} \delta - -The above equations can be used to update the state of the vehicle at each time step. - -Control Model -~~~~~~~~~~~~~~ - -To formulate the state-space representation of the vehicle dynamics as a linear model, -the state vector `x` and control input vector `u` are defined as follows: - -.. math:: x_t = [e_t, \dot{e}_t, \theta_t, \dot{\theta}_t, \upsilon_t]^T - -.. math:: u_t = [\delta_t, a_t]^T - -The linear state transition equation can be represented as: - -.. math:: x_{t+1} = A x_t + B u_t - -where: - -:math:`\begin{equation*} A = \begin{bmatrix} 1 & dt & 0 & 0 & 0\\ 0 & 0 & v & 0 & 0\\ 0 & 0 & 1 & dt & 0\\ 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1\\\end{bmatrix} \end{equation*}` - -:math:`\begin{equation*} B = \begin{bmatrix} 0 & 0\\ 0 & 0\\ 0 & 0\\ \frac{v}{L} & 0\\ 0 & dt \\ \end{bmatrix} \end{equation*}` - -LQR controller -~~~~~~~~~~~~~~~ - -The Linear Quadratic Regulator (LQR) controller is used to calculate the optimal control input `u` that minimizes the quadratic cost function: - -:math:`J = \sum_{t=0}^{N} (x_t^T Q x_t + u_t^T R u_t)` - -where `Q` and `R` are the weighting matrices for the state and control input, respectively. - -for the linear model: - -:math:`x_{t+1} = A x_t + B u_t` - -The optimal control input `u` can be calculated as: - -:math:`u_t = -K x_t` - -where `K` is the feedback gain matrix obtained by solving the Riccati equation. - -Simulation results -~~~~~~~~~~~~~~~~~~~ - - -.. image:: x-y.png - :width: 600px - -.. image:: yaw.png - :width: 600px - -.. image:: speed.png - :width: 600px - - - -Reference -~~~~~~~~~~~ - -- `Towards fully autonomous driving: Systems and algorithms `__ diff --git a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_steering_control_model.jpg b/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_steering_control_model.jpg deleted file mode 100644 index 83754d5bb0..0000000000 Binary files a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/lqr_steering_control_model.jpg and /dev/null differ diff --git a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/speed.png b/docs/modules/6_path_tracking/lqr_speed_and_steering_control/speed.png deleted file mode 100644 index ae99eb7ea3..0000000000 Binary files a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/speed.png and /dev/null differ diff --git a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/x-y.png b/docs/modules/6_path_tracking/lqr_speed_and_steering_control/x-y.png deleted file mode 100644 index bff3f1a786..0000000000 Binary files a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/x-y.png and /dev/null differ diff --git a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/yaw.png b/docs/modules/6_path_tracking/lqr_speed_and_steering_control/yaw.png deleted file mode 100644 index 7f3d9c1d10..0000000000 Binary files a/docs/modules/6_path_tracking/lqr_speed_and_steering_control/yaw.png and /dev/null differ diff --git a/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_main.rst b/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_main.rst deleted file mode 100644 index fc8f2a33aa..0000000000 --- a/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_main.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. _linearquadratic-regulator-(lqr)-steering-control: - -Linear–quadratic regulator (LQR) steering control -------------------------------------------------- - -Path tracking simulation with LQR steering control and PID speed -control. - -.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_steer_control/animation.gif - -Code Link -~~~~~~~~~~~~~~~ - -.. autofunction:: PathTracking.lqr_steer_control.lqr_steer_control.lqr_steering_control - - -Overview -~~~~~~~~ - -The LQR (Linear Quadratic Regulator) steering control model implemented in lqr_steer_control.py provides a simulation -for an autonomous vehicle to track a desired trajectory by adjusting steering angle based on feedback from the current state and the desired trajectory. -This model utilizes a combination of PID speed control and LQR steering control to achieve smooth and accurate trajectory tracking. - -Vehicle motion Model -~~~~~~~~~~~~~~~~~~~~~ - -The below figure shows the geometric model of the vehicle used in this simulation: - -.. image:: lqr_steering_control_model.jpg - :width: 600px - -The `e` and :math:`\theta` represent the lateral error and orientation error, respectively, with respect to the desired trajectory. -And :math:`\dot{e}` and :math:`\dot{\theta}` represent the rates of change of these errors. - -The :math:`e_t` and :math:`\theta_t` are the updated values of `e` and :math:`\theta` at time `t`, respectively, and can be calculated using the following kinematic equations: - -.. math:: e_t = e_{t-1} + \dot{e}_{t-1} dt - -.. math:: \theta_t = \theta_{t-1} + \dot{\theta}_{t-1} dt - -Where `dt` is the time difference. - -The change rate of the `e` can be calculated as: - -.. math:: \dot{e}_t = V \sin(\theta_{t-1}) - -Where `V` is the current vehicle speed. - -If the :math:`\theta` is small, - -.. math:: \theta \approx 0 - -the :math:`\sin(\theta)` can be approximated as :math:`\theta`: - -.. math:: \sin(\theta) = \theta - -So, the change rate of the `e` can be approximated as: - -.. math:: \dot{e}_t = V \theta_{t-1} - -The change rate of the :math:`\theta` can be calculated as: - -.. math:: \dot{\theta}_t = \frac{V}{L} \tan(\delta) - -where `L` is the wheelbase of the vehicle and :math:`\delta` is the steering angle. - -If the :math:`\delta` is small, - -.. math:: \delta \approx 0 - -the :math:`\tan(\delta)` can be approximated as :math:`\delta`: - -.. math:: \tan(\delta) = \delta - -So, the change rate of the :math:`\theta` can be approximated as: - -.. math:: \dot{\theta}_t = \frac{V}{L} \delta - -The above equations can be used to update the state of the vehicle at each time step. - -Control Model -~~~~~~~~~~~~~~ - -To formulate the state-space representation of the vehicle dynamics as a linear model, -the state vector `x` and control input vector `u` are defined as follows: - -.. math:: x_t = [e_t, \dot{e}_t, \theta_t, \dot{\theta}_t]^T - -.. math:: u_t = \delta_t - -The linear state transition equation can be represented as: - -.. math:: x_{t+1} = A x_t + B u_t - -where: - -:math:`\begin{equation*} A = \begin{bmatrix} 1 & dt & 0 & 0\\ 0 & 0 & v & 0\\ 0 & 0 & 1 & dt\\ 0 & 0 & 0 & 0 \\ \end{bmatrix} \end{equation*}` - -:math:`\begin{equation*} B = \begin{bmatrix} 0\\ 0\\ 0\\ \frac{v}{L} \\ \end{bmatrix} \end{equation*}` - -LQR controller -~~~~~~~~~~~~~~~ - -The Linear Quadratic Regulator (LQR) controller is used to calculate the optimal control input `u` that minimizes the quadratic cost function: - -:math:`J = \sum_{t=0}^{N} (x_t^T Q x_t + u_t^T R u_t)` - -where `Q` and `R` are the weighting matrices for the state and control input, respectively. - -for the linear model: - -:math:`x_{t+1} = A x_t + B u_t` - -The optimal control input `u` can be calculated as: - -:math:`u_t = -K x_t` - -where `K` is the feedback gain matrix obtained by solving the Riccati equation. - -Reference -~~~~~~~~~~~ -- `ApolloAuto/apollo: An open autonomous driving platform `_ - -- `Linear Quadratic Regulator (LQR) `_ - diff --git a/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_model.jpg b/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_model.jpg deleted file mode 100644 index 83754d5bb0..0000000000 Binary files a/docs/modules/6_path_tracking/lqr_steering_control/lqr_steering_control_model.jpg and /dev/null differ diff --git a/docs/modules/6_path_tracking/path_tracking_main.rst b/docs/modules/6_path_tracking/path_tracking_main.rst deleted file mode 100644 index 742cc807e6..0000000000 --- a/docs/modules/6_path_tracking/path_tracking_main.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _`Path Tracking`: - -Path Tracking -============= - -Path tracking is the ability of a robot to follow the reference path generated by a path planner while simultaneously stabilizing the robot. The path tracking controller may need to account for modeling error and other forms of uncertainty. In path tracking, feedback control techniques and optimization based control techniques are widely used[22]. Fig.6 shows simulations using rear wheel feedback steering control and PID speed control, and iterative linear model predictive path tracking control[27]. - -.. toctree:: - :maxdepth: 2 - :caption: Contents - - pure_pursuit_tracking/pure_pursuit_tracking - stanley_control/stanley_control - rear_wheel_feedback_control/rear_wheel_feedback_control - lqr_steering_control/lqr_steering_control - lqr_speed_and_steering_control/lqr_speed_and_steering_control - model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control - cgmres_nmpc/cgmres_nmpc - move_to_a_pose/move_to_a_pose diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000000..93263535e6 --- /dev/null +++ b/genindex.html @@ -0,0 +1,323 @@ + + + + + + Index — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + +

Index

+ +
+ A + | C + | D + | E + | F + | G + | I + | L + | M + | N + | P + | R + | V + +
+

A

+ + +
+ +

C

+ + + +
+ +

D

+ + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + +
+ +

I

+ + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

V

+ + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/getting_started.html b/getting_started.html new file mode 100644 index 0000000000..3a84fcce96 --- /dev/null +++ b/getting_started.html @@ -0,0 +1,202 @@ + + + + + + + Getting Started — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting Started

+
+

What is this?

+

This is an Open Source Software (OSS) project: PythonRobotics, which is a Python code collection of robotics algorithms.

+

The focus of the project is on autonomous navigation, and the goal is for beginners in robotics to understand the basic ideas behind each algorithm.

+

In this project, the algorithms which are practical and widely used in both academia and industry are selected.

+

Each sample code is written in Python3 and only depends on some standard modules for readability and ease of use.

+

It includes intuitive animations to understand the behavior of the simulation.

+

See this paper for more details:

+ +
+
+

Requirements

+ +

For development:

+ +
+
+

How to use

+
    +
  1. Clone this repo and go into dir.

  2. +
+
>$ git clone https://github.com/AtsushiSakai/PythonRobotics.git
+
+>$ cd PythonRobotics
+
+
+
    +
  1. Install the required libraries.

  2. +
+

using conda :

+
>$ conda env create -f requirements/environment.yml
+
+
+

using pip :

+
>$ pip install -r requirements/requirements.txt
+
+
+
    +
  1. Execute python script in each directory.

  2. +
  3. Add star to this repo if you like it 😃.

  4. +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/how_to_contribute.html b/how_to_contribute.html new file mode 100644 index 0000000000..6d75579903 --- /dev/null +++ b/how_to_contribute.html @@ -0,0 +1,252 @@ + + + + + + + How To Contribute — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

How To Contribute

+

This document describes how to contribute this project.

+
+

Adding a new algorithm example

+

This is a step by step manual to add a new algorithm example.

+
+

Step 1: Choose an algorithm to implement

+

Before choosing an algorithm, please check the Getting Started doc to +understand this project’s philosophy and setup your development environment.

+

If an algorithm is widely used and successful, let’s create an issue to +propose it for our community.

+

If some people agree by thumbs up or posting positive comments, let go to next step.

+

It is OK to just create an issue to propose adding an algorithm, someone might implement it.

+

In that case, please share any papers or documentations to implement it.

+
+
+

Step 2: Implement the algorithm with matplotlib based animation

+

When you implement an algorithm, please keep the following items in mind.

+
    +
  1. Use only Python. Other language code is not acceptable.

  2. +
  3. This project only accept codes for python 3.9 or higher.

  4. +
  5. Use matplotlib based animation to show how the algorithm works.

  6. +
  7. Only use current Requirements libraries, not adding new dependencies.

  8. +
  9. Keep simple your code. The main goal is to make it easy for users to understand the algorithm, not for practical usage.

  10. +
+
+
+

Step 3: Add a unittest

+

If you add a new algorithm sample code, please add a unit test file under tests dir.

+

This sample test code might help you : test_a_star.py.

+

At the least, try to run the example code without animation in the unit test.

+

If you want to run the test suites locally, you can use the runtests.sh script by just executing it.

+

The test_codestyle.py check code style for your PR’s codes.

+
+
+

Step 4: Write a document about the algorithm

+

Please add a document to describe the algorithm details, mathematical backgrounds and show graphs and animation gif.

+

This project is using Sphinx as a document builder, all documentations are written by reStructuredText.

+

You can add a new rst file under the subdirectory in doc modules dir and the top rst file can include it.

+

Please check other documents for details.

+

You can build the doc locally based on doc README.

+

Note that the reStructuredText based doc should only focus on the mathematics and the algorithm of the example.

+

Documentations related codes should be in the python script as the header comments of the script or docstrings of each function.

+
+
+

Step 5: Submit a pull request and fix codes based on review

+

Let’s submit a pull request when your code, test, and doc are ready.

+

At first, please fix all CI errors before code review.

+

You can check your PR doc from the CI panel.

+

After the “ci/circleci: build_doc” CI is succeeded, +you can access you PR doc with clicking the [Details] of the “ci/circleci: build_doc artifact” CI.

+_images/doc_ci.png +

After that, I will start the review.

+

Note that this is my hobby project; I appreciate your patience during the review process.

+
+
+
+

Reporting and fixing a defect

+

Reporting and fixing a defect is also great contribution.

+

When you report an issue, please provide these information:

+
    +
  • A clear and concise description of what the bug is.

  • +
  • A clear and concise description of what you expected to happen.

  • +
  • Screenshots to help explain your problem if applicable.

  • +
  • OS version

  • +
  • Python version

  • +
  • Each library versions

  • +
+

If you want to fix any bug, you can find reported issues in bug labeled issues.

+

If you fix a bug of existing codes, please add a test function +in the test code to show the issue was solved.

+

This doc submit a pull request can be helpful to submit a pull request.

+
+
+

Adding missed documentations for existing examples

+

Adding the missed documentations for existing examples is also great contribution.

+

If you check the Python Robotics Docs, you can notice that some of the examples +only have a simulation gif or short overview descriptions, +but no detailed algorithm or mathematical description.

+

This doc how to write doc can be helpful to write documents.

+
+
+

Supporting this project

+

Supporting this project financially is also a great contribution!!.

+

If you or your company would like to support this project, please consider:

+ +

If you would like to support us in some other way, please contact with creating an issue.

+
+

Sponsors

+
    +
  1. JetBrains : They are providing a free license of their IDEs for this OSS development.

  2. +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000..0cbb9763bd --- /dev/null +++ b/index.html @@ -0,0 +1,279 @@ + + + + + + + Welcome to PythonRobotics’s documentation! — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Welcome to PythonRobotics’s documentation!
  • +
  • + Edit on GitHub +
  • +
+
+
+
+
+ +
+

Welcome to PythonRobotics’s documentation!

+

Python codes for robotics algorithm. The project is on GitHub.

+

This is a Python code collection of robotics algorithms.

+

Features:

+
    +
  1. Easy to read for understanding each algorithm’s basic idea.

  2. +
  3. Widely used and practical algorithms are selected.

  4. +
  5. Minimum dependency.

  6. +
+

See this paper for more details:

+ +
+

Contents

+ +
+
+
+

Indices and tables

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/aerial_navigation/aerial_navigation.html b/modules/aerial_navigation/aerial_navigation.html new file mode 100644 index 0000000000..6580428524 --- /dev/null +++ b/modules/aerial_navigation/aerial_navigation.html @@ -0,0 +1,157 @@ + + + + + + + Aerial Navigation — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Aerial Navigation

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following.html b/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following.html new file mode 100644 index 0000000000..906060e8b8 --- /dev/null +++ b/modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following.html @@ -0,0 +1,148 @@ + + + + + + + Drone 3d trajectory following — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Drone 3d trajectory following

+

This is a 3d trajectory following simulation for a quadrotor.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/AerialNavigation/drone_3d_trajectory_following/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing.html b/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing.html new file mode 100644 index 0000000000..9988b3515b --- /dev/null +++ b/modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing.html @@ -0,0 +1,272 @@ + + + + + + + Rocket powered landing — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Rocket powered landing

+
+

Simulation

+
from IPython.display import Image
+Image(filename="figure.png",width=600)
+from IPython.display import display, HTML
+
+display(HTML(data="""
+<style>
+    div#notebook-container    { width: 95%; }
+    div#menubar-container     { width: 65%; }
+    div#maintoolbar-container { width: 99%; }
+</style>
+"""))
+
+
+
+gif +
+
+
+

Equation generation

+
import sympy as sp
+import numpy as np
+from IPython.display import display
+sp.init_printing(use_latex='mathjax')
+
+
+
# parameters
+# Angular moment of inertia
+J_B = 1e-2 * np.diag([1., 1., 1.])
+
+# Gravity
+g_I = np.array((-1, 0., 0.))
+
+# Fuel consumption
+alpha_m = 0.01
+
+# Vector from thrust point to CoM
+r_T_B = np.array([-1e-2, 0., 0.])
+
+
+def dir_cosine(q):
+        return np.matrix([
+            [1 - 2 * (q[2] ** 2 + q[3] ** 2), 2 * (q[1] * q[2] +
+                                                   q[0] * q[3]), 2 * (q[1] * q[3] - q[0] * q[2])],
+            [2 * (q[1] * q[2] - q[0] * q[3]), 1 - 2 *
+             (q[1] ** 2 + q[3] ** 2), 2 * (q[2] * q[3] + q[0] * q[1])],
+            [2 * (q[1] * q[3] + q[0] * q[2]), 2 * (q[2] * q[3] -
+                                                   q[0] * q[1]), 1 - 2 * (q[1] ** 2 + q[2] ** 2)]
+        ])
+
+def omega(w):
+        return np.matrix([
+            [0, -w[0], -w[1], -w[2]],
+            [w[0], 0, w[2], -w[1]],
+            [w[1], -w[2], 0, w[0]],
+            [w[2], w[1], -w[0], 0],
+        ])
+
+def skew(v):
+    return np.matrix([
+            [0, -v[2], v[1]],
+            [v[2], 0, -v[0]],
+            [-v[1], v[0], 0]
+        ])
+
+
+
f = sp.zeros(14, 1)
+
+x = sp.Matrix(sp.symbols(
+    'm rx ry rz vx vy vz q0 q1 q2 q3 wx wy wz', real=True))
+u = sp.Matrix(sp.symbols('ux uy uz', real=True))
+
+g_I = sp.Matrix(g_I)
+r_T_B = sp.Matrix(r_T_B)
+J_B = sp.Matrix(J_B)
+
+C_B_I = dir_cosine(x[7:11, 0])
+C_I_B = C_B_I.transpose()
+
+f[0, 0] = - alpha_m * u.norm()
+f[1:4, 0] = x[4:7, 0]
+f[4:7, 0] = 1 / x[0, 0] * C_I_B * u + g_I
+f[7:11, 0] = 1 / 2 * omega(x[11:14, 0]) * x[7: 11, 0]
+f[11:14, 0] = J_B ** -1 * \
+    (skew(r_T_B) * u - skew(x[11:14, 0]) * J_B * x[11:14, 0])
+
+
+
display(sp.simplify(f)) # f
+
+
+
+\[\begin{split}\left[\begin{matrix}- 0.01 \sqrt{ux^{2} + uy^{2} + uz^{2}}\\vx\\vy\\vz\\\frac{- 1.0 m - ux \left(2 q_{2}^{2} + 2 q_{3}^{2} - 1\right) - 2 uy \left(q_{0} q_{3} - q_{1} q_{2}\right) + 2 uz \left(q_{0} q_{2} + q_{1} q_{3}\right)}{m}\\\frac{2 ux \left(q_{0} q_{3} + q_{1} q_{2}\right) - uy \left(2 q_{1}^{2} + 2 q_{3}^{2} - 1\right) - 2 uz \left(q_{0} q_{1} - q_{2} q_{3}\right)}{m}\\\frac{- 2 ux \left(q_{0} q_{2} - q_{1} q_{3}\right) + 2 uy \left(q_{0} q_{1} + q_{2} q_{3}\right) - uz \left(2 q_{1}^{2} + 2 q_{2}^{2} - 1\right)}{m}\\- 0.5 q_{1} wx - 0.5 q_{2} wy - 0.5 q_{3} wz\\0.5 q_{0} wx + 0.5 q_{2} wz - 0.5 q_{3} wy\\0.5 q_{0} wy - 0.5 q_{1} wz + 0.5 q_{3} wx\\0.5 q_{0} wz + 0.5 q_{1} wy - 0.5 q_{2} wx\\0\\1.0 uz\\- 1.0 uy\end{matrix}\right]\end{split}\]
+
display(sp.simplify(f.jacobian(x)))# A
+
+
+
+\[\begin{split}\left[\begin{array}{cccccccccccccc}0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\frac{ux \left(2 q_{2}^{2} + 2 q_{3}^{2} - 1\right) + 2 uy \left(q_{0} q_{3} - q_{1} q_{2}\right) - 2 uz \left(q_{0} q_{2} + q_{1} q_{3}\right)}{m^{2}} & 0 & 0 & 0 & 0 & 0 & 0 & \frac{2 \left(q_{2} uz - q_{3} uy\right)}{m} & \frac{2 \left(q_{2} uy + q_{3} uz\right)}{m} & \frac{2 \left(q_{0} uz + q_{1} uy - 2 q_{2} ux\right)}{m} & \frac{2 \left(- q_{0} uy + q_{1} uz - 2 q_{3} ux\right)}{m} & 0 & 0 & 0\\\frac{- 2 ux \left(q_{0} q_{3} + q_{1} q_{2}\right) + uy \left(2 q_{1}^{2} + 2 q_{3}^{2} - 1\right) + 2 uz \left(q_{0} q_{1} - q_{2} q_{3}\right)}{m^{2}} & 0 & 0 & 0 & 0 & 0 & 0 & \frac{2 \left(- q_{1} uz + q_{3} ux\right)}{m} & \frac{2 \left(- q_{0} uz - 2 q_{1} uy + q_{2} ux\right)}{m} & \frac{2 \left(q_{1} ux + q_{3} uz\right)}{m} & \frac{2 \left(q_{0} ux + q_{2} uz - 2 q_{3} uy\right)}{m} & 0 & 0 & 0\\\frac{2 ux \left(q_{0} q_{2} - q_{1} q_{3}\right) - 2 uy \left(q_{0} q_{1} + q_{2} q_{3}\right) + uz \left(2 q_{1}^{2} + 2 q_{2}^{2} - 1\right)}{m^{2}} & 0 & 0 & 0 & 0 & 0 & 0 & \frac{2 \left(q_{1} uy - q_{2} ux\right)}{m} & \frac{2 \left(q_{0} uy - 2 q_{1} uz + q_{3} ux\right)}{m} & \frac{2 \left(- q_{0} ux - 2 q_{2} uz + q_{3} uy\right)}{m} & \frac{2 \left(q_{1} ux + q_{2} uy\right)}{m} & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & - 0.5 wx & - 0.5 wy & - 0.5 wz & - 0.5 q_{1} & - 0.5 q_{2} & - 0.5 q_{3}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0.5 wx & 0 & 0.5 wz & - 0.5 wy & 0.5 q_{0} & - 0.5 q_{3} & 0.5 q_{2}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0.5 wy & - 0.5 wz & 0 & 0.5 wx & 0.5 q_{3} & 0.5 q_{0} & - 0.5 q_{1}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0.5 wz & 0.5 wy & - 0.5 wx & 0 & - 0.5 q_{2} & 0.5 q_{1} & 0.5 q_{0}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\end{array}\right]\end{split}\]
+
sp.simplify(f.jacobian(u)) # B
+
+
+
+\[\begin{split}\left[\begin{matrix}- \frac{0.01 ux}{\sqrt{ux^{2} + uy^{2} + uz^{2}}} & - \frac{0.01 uy}{\sqrt{ux^{2} + uy^{2} + uz^{2}}} & - \frac{0.01 uz}{\sqrt{ux^{2} + uy^{2} + uz^{2}}}\\0 & 0 & 0\\0 & 0 & 0\\0 & 0 & 0\\\frac{- 2 q_{2}^{2} - 2 q_{3}^{2} + 1}{m} & \frac{2 \left(- q_{0} q_{3} + q_{1} q_{2}\right)}{m} & \frac{2 \left(q_{0} q_{2} + q_{1} q_{3}\right)}{m}\\\frac{2 \left(q_{0} q_{3} + q_{1} q_{2}\right)}{m} & \frac{- 2 q_{1}^{2} - 2 q_{3}^{2} + 1}{m} & \frac{2 \left(- q_{0} q_{1} + q_{2} q_{3}\right)}{m}\\\frac{2 \left(- q_{0} q_{2} + q_{1} q_{3}\right)}{m} & \frac{2 \left(q_{0} q_{1} + q_{2} q_{3}\right)}{m} & \frac{- 2 q_{1}^{2} - 2 q_{2}^{2} + 1}{m}\\0 & 0 & 0\\0 & 0 & 0\\0 & 0 & 0\\0 & 0 & 0\\0 & 0 & 0\\0 & 0 & 1.0\\0 & -1.0 & 0\end{matrix}\right]\end{split}\]
+
+
+

References

+
    +
  • Python implementation of ‘Successive Convexification for 6-DoF Mars +Rocket Powered Landing with Free-Final-Time’ paper by Michael Szmuk +and Behçet Açıkmeşe.

  • +
  • inspired by EmbersArc/SuccessiveConvexificationFreeFinalTime: +Implementation of “Successive Convexification for 6-DoF Mars Rocket +Powered Landing with Free-Final-Time” +https://github.com/EmbersArc/SuccessiveConvexificationFreeFinalTime

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/appendix/Kalmanfilter_basics.html b/modules/appendix/Kalmanfilter_basics.html new file mode 100644 index 0000000000..4801fbde54 --- /dev/null +++ b/modules/appendix/Kalmanfilter_basics.html @@ -0,0 +1,576 @@ + + + + + + + KF Basics - Part I — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

KF Basics - Part I

+
+

Introduction

+
+

What is the need to describe belief in terms of PDF’s?

+

This is because robot environments are stochastic. A robot environment +may have cows with Tesla by side. That is a robot and it’s environment +cannot be deterministically modelled(e.g as a function of something like +time t). In the real world sensors are also error prone, and hence +there’ll be a set of values with a mean and variance that it can take. +Hence, we always have to model around some mean and variances +associated.

+
+
+

What is Expectation of a Random Variables?

+

Expectation is nothing but an average of the probabilites

+
+\[\mathbb E[X] = \sum_{i=1}^n p_ix_i\]
+

In the continous form,

+
+\[\mathbb E[X] = \int_{-\infty}^\infty x\, f(x) \,dx\]
+
import numpy as np
+import random
+x=[3,1,2]
+p=[0.1,0.3,0.4]
+E_x=np.sum(np.multiply(x,p))
+print(E_x)
+
+
+
1.4000000000000001
+
+
+
+
+

What is the advantage of representing the belief as a unimodal as opposed to multimodal?

+

Obviously, it makes sense because we can’t multiple probabilities to a +car moving for two locations. This would be too confusing and the +information will not be useful.

+
+
+
+

Variance, Covariance and Correlation

+
+

Variance

+

Variance is the spread of the data. The mean does’nt tell much about +the data. Therefore the variance tells us about the story about the +data meaning the spread of the data.

+
+\[\mathit{VAR}(X) = \frac{1}{n}\sum_{i=1}^n (x_i - \mu)^2\]
+
x=np.random.randn(10)
+np.var(x)
+
+
+
1.0224618077401504
+
+
+
+
+

Covariance

+

This is for a multivariate distribution. For example, a robot in 2-D +space can take values in both x and y. To describe them, a normal +distribution with mean in both x and y is needed.

+

For a multivariate distribution, mean \(\mu\) can be represented as +a matrix,

+
+\[\begin{split}\mu = \begin{bmatrix}\mu_1\\\mu_2\\ \vdots \\\mu_n\end{bmatrix}\end{split}\]
+

Similarly, variance can also be represented.

+

But an important concept is that in the same way as every variable or +dimension has a variation in its values, it is also possible that there +will be values on how they together vary. This is also a measure of +how two datasets are related to each other or correlation.

+

For example, as height increases weight also generally increases. These +variables are correlated. They are positively correlated because as one +variable gets larger so does the other.

+

We use a covariance matrix to denote covariances of a multivariate +normal distribution:

+
+\[\begin{split}\Sigma = \begin{bmatrix} + \sigma_1^2 & \sigma_{12} & \cdots & \sigma_{1n} \\ + \sigma_{21} &\sigma_2^2 & \cdots & \sigma_{2n} \\ + \vdots & \vdots & \ddots & \vdots \\ + \sigma_{n1} & \sigma_{n2} & \cdots & \sigma_n^2 + \end{bmatrix}\end{split}\]
+

Diagonal - Variance of each variable associated.

+

Off-Diagonal - covariance between ith and jth variables.

+
+\[\begin{split}\begin{aligned}VAR(X) = \sigma_x^2 &= \frac{1}{n}\sum_{i=1}^n(X - \mu)^2\\ +COV(X, Y) = \sigma_{xy} &= \frac{1}{n}\sum_{i=1}^n[(X-\mu_x)(Y-\mu_y)\big]\end{aligned}\end{split}\]
+
x=np.random.random((3,3))
+np.cov(x)
+
+
+
array([[0.08868895, 0.05064471, 0.08855629],
+       [0.05064471, 0.06219243, 0.11555291],
+       [0.08855629, 0.11555291, 0.21534324]])
+
+
+

Covariance taking the data as sample with \(\frac{1}{N-1}\)

+
x_cor=np.random.rand(1,10)
+y_cor=np.random.rand(1,10)
+np.cov(x_cor,y_cor)
+
+
+
array([[ 0.1571437 , -0.00766623],
+       [-0.00766623,  0.13957621]])
+
+
+

Covariance taking the data as population with \(\frac{1}{N}\)

+
np.cov(x_cor,y_cor,bias=1)
+
+
+
array([[ 0.14142933, -0.0068996 ],
+       [-0.0068996 ,  0.12561859]])
+
+
+
+
+
+

Gaussians

+
+

Central Limit Theorem

+

According to this theorem, the average of n samples of random and +independent variables tends to follow a normal distribution as we +increase the sample size.(Generally, for n>=30)

+
import matplotlib.pyplot as plt
+import random
+a=np.zeros((100,))
+for i in range(100):
+    x=[random.uniform(1,10) for _ in range(1000)]
+    a[i]=np.sum(x,axis=0)/1000
+plt.hist(a)
+
+
+
(array([ 1.,  4.,  9., 12., 12., 20., 16., 16.,  4.,  6.]),
+ array([5.30943011, 5.34638597, 5.38334183, 5.42029769, 5.45725355,
+        5.49420941, 5.53116527, 5.56812114, 5.605077  , 5.64203286,
+        5.67898872]),
+ <a list of 10 Patch objects>)
+
+
+../../_images/Kalmanfilter_basics_14_1.png +
+
+

Gaussian Distribution

+

A Gaussian is a continuous probability distribution that is completely +described with two parameters, the mean (\(\mu\)) and the variance +(\(\sigma^2\)). It is defined as:

+
+\[f(x, \mu, \sigma) = \frac{1}{\sigma\sqrt{2\pi}} \exp\big [{-\frac{(x-\mu)^2}{2\sigma^2} }\big ]\]
+

Range is \([-\inf,\inf]\)

+

This is just a function of mean(\(\mu\)) and standard deviation +(\(\sigma\)) and what gives the normal distribution the +charecteristic bell curve.

+
import matplotlib.mlab as mlab
+import math
+import scipy.stats
+
+mu = 0
+variance = 5
+sigma = math.sqrt(variance)
+x = np.linspace(mu - 5*sigma, mu + 5*sigma, 100)
+plt.plot(x,scipy.stats.norm.pdf(x, mu, sigma))
+plt.show()
+
+
+../../_images/Kalmanfilter_basics_16_0.png +
+
+

Why do we need Gaussian distributions?

+

Since it becomes really difficult in the real world to deal with +multimodal distribution as we cannot put the belief in two seperate +location of the robots. This becomes really confusing and in practice +impossible to comprehend. Gaussian probability distribution allows us to +drive the robots using only one mode with peak at the mean with some +variance.

+
+
+
+

Gaussian Properties

+

Multiplication

+

For the measurement update in a Bayes Filter, the algorithm tells us to +multiply the Prior P(X_t) and measurement P(Z_t|X_t) to calculate the +posterior:

+
+\[P(X \mid Z) = \frac{P(Z \mid X)P(X)}{P(Z)}\]
+

Here for the numerator, \(P(Z \mid X),P(X)\) both are gaussian.

+

\(N(\bar\mu, \bar\sigma^1)\) and \(N(\bar\mu, \bar\sigma^2)\) +are their mean and variances.

+

New mean is

+
+\[\mu_\mathtt{new} = \frac{\sigma_z^2\bar\mu + \bar\sigma^2z}{\bar\sigma^2+\sigma_z^2}\]
+

New variance is

+
+\[\sigma_\mathtt{new} = \frac{\sigma_z^2\bar\sigma^2}{\bar\sigma^2+\sigma_z^2}\]
+
import matplotlib.mlab as mlab
+import math
+mu1 = 0
+variance1 = 2
+sigma = math.sqrt(variance1)
+x1 = np.linspace(mu1 - 3*sigma, mu1 + 3*sigma, 100)
+plt.plot(x1,scipy.stats.norm.pdf(x1, mu1, sigma),label='prior')
+
+mu2 = 10
+variance2 = 2
+sigma = math.sqrt(variance2)
+x2 = np.linspace(mu2 - 3*sigma, mu2 + 3*sigma, 100)
+plt.plot(x2,scipy.stats.norm.pdf(x2, mu2, sigma),"g-",label='measurement')
+
+
+mu_new=(mu1*variance2+mu2*variance1)/(variance1+variance2)
+print("New mean is at: ",mu_new)
+var_new=(variance1*variance2)/(variance1+variance2)
+print("New variance is: ",var_new)
+sigma = math.sqrt(var_new)
+x3 = np.linspace(mu_new - 3*sigma, mu_new + 3*sigma, 100)
+plt.plot(x3,scipy.stats.norm.pdf(x3, mu_new, var_new),label="posterior")
+plt.legend(loc='upper left')
+plt.xlim(-10,20)
+plt.show()
+
+
+
New mean is at:  5.0
+New variance is:  1.0
+
+
+../../_images/Kalmanfilter_basics_19_1.png +

Addition

+

The motion step involves a case of adding up probability (Since it has +to abide the Law of Total Probability). This means their beliefs are to +be added and hence two gaussians. They are simply arithmetic additions +of the two.

+
+\[\begin{split}\begin{gathered}\mu_x = \mu_p + \mu_z \\ +\sigma_x^2 = \sigma_z^2+\sigma_p^2\, \end{gathered}\end{split}\]
+
import matplotlib.mlab as mlab
+import math
+mu1 = 5
+variance1 = 1
+sigma = math.sqrt(variance1)
+x1 = np.linspace(mu1 - 3*sigma, mu1 + 3*sigma, 100)
+plt.plot(x1,scipy.stats.norm.pdf(x1, mu1, sigma),label='prior')
+
+mu2 = 10
+variance2 = 1
+sigma = math.sqrt(variance2)
+x2 = np.linspace(mu2 - 3*sigma, mu2 + 3*sigma, 100)
+plt.plot(x2,scipy.stats.norm.pdf(x2, mu2, sigma),"g-",label='measurement')
+
+
+mu_new=mu1+mu2
+print("New mean is at: ",mu_new)
+var_new=(variance1+variance2)
+print("New variance is: ",var_new)
+sigma = math.sqrt(var_new)
+x3 = np.linspace(mu_new - 3*sigma, mu_new + 3*sigma, 100)
+plt.plot(x3,scipy.stats.norm.pdf(x3, mu_new, var_new),label="posterior")
+plt.legend(loc='upper left')
+plt.xlim(-10,20)
+plt.show()
+
+
+
New mean is at:  15
+New variance is:  2
+
+
+../../_images/Kalmanfilter_basics_21_1.png +
#Example from:
+#https://scipython.com/blog/visualizing-the-bivariate-gaussian-distribution/
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib import cm
+from mpl_toolkits.mplot3d import Axes3D
+
+# Our 2-dimensional distribution will be over variables X and Y
+N = 60
+X = np.linspace(-3, 3, N)
+Y = np.linspace(-3, 4, N)
+X, Y = np.meshgrid(X, Y)
+
+# Mean vector and covariance matrix
+mu = np.array([0., 1.])
+Sigma = np.array([[ 1. , -0.5], [-0.5,  1.5]])
+
+# Pack X and Y into a single 3-dimensional array
+pos = np.empty(X.shape + (2,))
+pos[:, :, 0] = X
+pos[:, :, 1] = Y
+
+def multivariate_gaussian(pos, mu, Sigma):
+    """Return the multivariate Gaussian distribution on array pos.
+
+    pos is an array constructed by packing the meshed arrays of variables
+    x_1, x_2, x_3, ..., x_k into its _last_ dimension.
+
+    """
+
+    n = mu.shape[0]
+    Sigma_det = np.linalg.det(Sigma)
+    Sigma_inv = np.linalg.inv(Sigma)
+    N = np.sqrt((2*np.pi)**n * Sigma_det)
+    # This einsum call calculates (x-mu)T.Sigma-1.(x-mu) in a vectorized
+    # way across all the input variables.
+    fac = np.einsum('...k,kl,...l->...', pos-mu, Sigma_inv, pos-mu)
+
+    return np.exp(-fac / 2) / N
+
+# The distribution on the variables X, Y packed into pos.
+Z = multivariate_gaussian(pos, mu, Sigma)
+
+# Create a surface plot and projected filled contour plot under it.
+fig = plt.figure()
+ax = fig.gca(projection='3d')
+ax.plot_surface(X, Y, Z, rstride=3, cstride=3, linewidth=1, antialiased=True,
+                cmap=cm.viridis)
+
+cset = ax.contourf(X, Y, Z, zdir='z', offset=-0.15, cmap=cm.viridis)
+
+# Adjust the limits, ticks and view angle
+ax.set_zlim(-0.15,0.2)
+ax.set_zticks(np.linspace(0,0.2,5))
+ax.view_init(27, -21)
+
+plt.show()
+
+
+../../_images/Kalmanfilter_basics_22_0.png +

This is a 3D projection of the gaussians involved with the lower surface +showing the 2D projection of the 3D projection above. The innermost +ellipse represents the highest peak, that is the maximum probability for +a given (X,Y) value.

+

** numpy einsum examples **

+
a = np.arange(25).reshape(5,5)
+b = np.arange(5)
+c = np.arange(6).reshape(2,3)
+print(a)
+print(b)
+print(c)
+
+
+
[[ 0  1  2  3  4]
+ [ 5  6  7  8  9]
+ [10 11 12 13 14]
+ [15 16 17 18 19]
+ [20 21 22 23 24]]
+[0 1 2 3 4]
+[[0 1 2]
+ [3 4 5]]
+
+
+
#this is the diagonal sum, i repeated means the diagonal
+np.einsum('ij', a)
+#this takes the output ii which is the diagonal and outputs to a
+np.einsum('ii->i',a)
+#this takes in the array A represented by their axes 'ij' and  B by its only axes'j'
+#and multiples them element wise
+np.einsum('ij,j',a, b)
+
+
+
array([ 30,  80, 130, 180, 230])
+
+
+
A = np.arange(3).reshape(3,1)
+B = np.array([[ 0,  1,  2,  3],
+              [ 4,  5,  6,  7],
+              [ 8,  9, 10, 11]])
+C=np.multiply(A,B)
+np.sum(C,axis=1)
+
+
+
array([ 0, 22, 76])
+
+
+
D = np.array([0,1,2])
+E = np.array([[ 0,  1,  2,  3],
+              [ 4,  5,  6,  7],
+              [ 8,  9, 10, 11]])
+
+np.einsum('i,ij->i',D,E)
+
+
+
array([ 0, 22, 76])
+
+
+
from scipy.stats import multivariate_normal
+x, y = np.mgrid[-5:5:.1, -5:5:.1]
+pos = np.empty(x.shape + (2,))
+pos[:, :, 0] = x; pos[:, :, 1] = y
+rv = multivariate_normal([0.5, -0.2], [[2.0, 0.9], [0.9, 0.5]])
+plt.contourf(x, y, rv.pdf(pos))
+
+
+
<matplotlib.contour.QuadContourSet at 0x139196438>
+
+
+../../_images/Kalmanfilter_basics_28_1.png +
+
+

References:

+
    +
  1. Roger Labbe’s +repo +on Kalman Filters. (Majority of the examples in the notes are from +this)

  2. +
  3. Probabilistic Robotics by Sebastian Thrun, Wolfram Burgard and Dieter +Fox, MIT Press.

  4. +
  5. Scipy +Documentation

  6. +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/appendix/Kalmanfilter_basics_2.html b/modules/appendix/Kalmanfilter_basics_2.html new file mode 100644 index 0000000000..ea09b3c06e --- /dev/null +++ b/modules/appendix/Kalmanfilter_basics_2.html @@ -0,0 +1,414 @@ + + + + + + + KF Basics - Part 2 — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

KF Basics - Part 2

+
+

Probabilistic Generative Laws

+
+

1st Law:

+

The belief representing the state \(x_{t}\), is conditioned on all +past states, measurements and controls. This can be shown mathematically +by the conditional probability shown below:

+
+\[p(x_{t} | x_{0:t-1},z_{1:t-1},u_{1:t})\]
+
    +
  1. \(z_{t}\) represents the measurement

  2. +
  3. \(u_{t}\) the motion command

  4. +
  5. \(x_{t}\) the state (can be the position, velocity, etc) of +the robot or its environment at time t.

  6. +
+

‘If we know the state \(x_{t-1}\) and \(u_{t}\), then knowing +the states \(x_{0:t-2}\), \(z_{1:t-1}\) becomes immaterial +through the property of conditional independence’. The state +\(x_{t-1}\) introduces a conditional independence between +\(x_{t}\) and \(z_{1:t-1}\), \(u_{1:t-1}\)

+

Therefore the law now holds as:

+
+\[p(x_{t} | x_{0:t-1},z_{1:t-1},u_{1:t})=p(x_{t} | x_{t-1},u_{t})\]
+
+
+

2nd Law:

+

If \(x_{t}\) is complete, then:

+
+\[p(z_{t} | x-_{0:t},z_{1:t-1},u_{1:t})=p(z_{t} | x_{t})\]
+

\(x_{t}\) is complete means that the past states, controls or +measurements carry no additional information to predict future.

+

\(x_{0:t-1}\), \(z_{1:t-1}\) and \(u_{1:t}\) are +conditionally independent of \(z_{t}\) given \(x_{t}\) of +complete.

+

The filter works in two parts:

+

\(p(x_{t} | x_{t-1},u_{t})\) -> State Transition Probability

+

\(p(z_{t} | x_{t})\) -> Measurement Probability

+
+
+
+

Conditional dependence and independence example:

+

\(\bullet\)Independent but conditionally dependent

+

Let’s say you flip two fair coins

+

A - Your first coin flip is heads

+

B - Your second coin flip is heads

+

C - Your first two flips were the same

+

A and B here are independent. However, A and B are conditionally +dependent given C, since if you know C then your first coin flip will +inform the other one.

+

\(\bullet\)Dependent but conditionally independent

+

A box contains two coins: a regular coin and one fake two-headed coin +((P(H)=1). I choose a coin at random and toss it twice. Define the +following events.

+

A= First coin toss results in an H.

+

B= Second coin toss results in an H.

+

C= Coin 1 (regular) has been selected.

+

If we know A has occurred (i.e., the first coin toss has resulted in +heads), we would guess that it is more likely that we have chosen Coin 2 +than Coin 1. This in turn increases the conditional probability that B +occurs. This suggests that A and B are not independent. On the other +hand, given C (Coin 1 is selected), A and B are independent.

+
+
+

Bayes Rule:

+

Posterior =

+
+\[\frac{Likelihood*Prior}{Marginal}\]
+

Here,

+

Posterior = Probability of an event occurring based on certain +evidence.

+

Likelihood = How probable is the evidence given the event.

+

Prior = Probability of the just the event occurring without having +any evidence.

+

Marginal = Probability of the evidence given all the instances of +events possible.

+

Example:

+

1% of women have breast cancer (and therefore 99% do not). 80% of +mammograms detect breast cancer when it is there (and therefore 20% miss +it). 9.6% of mammograms detect breast cancer when its not there (and +therefore 90.4% correctly return a negative result).

+

We can turn the process above into an equation, which is Bayes Theorem. +Here is the equation:

+

\(\displaystyle{\Pr(\mathrm{A}|\mathrm{X}) = \frac{\Pr(\mathrm{X}|\mathrm{A})\Pr(\mathrm{A})}{\Pr(\mathrm{X|A})\Pr(\mathrm{A})+ \Pr(\mathrm{X | not \ A})\Pr(\mathrm{not \ A})}}\)

+

\(\bullet\)Pr(A|X) = Chance of having cancer (A) given a positive +test (X). This is what we want to know: How likely is it to have cancer +with a positive result? In our case it was 7.8%.

+

\(\bullet\)Pr(X|A) = Chance of a positive test (X) given that you +had cancer (A). This is the chance of a true positive, 80% in our case.

+

\(\bullet\)Pr(A) = Chance of having cancer (1%).

+

\(\bullet\)Pr(not A) = Chance of not having cancer (99%).

+

\(\bullet\)Pr(X|not A) = Chance of a positive test (X) given that +you didn’t have cancer (~A). This is a false positive, 9.6% in our case.

+
+
+

Bayes Filter Algorithm

+

The basic filter algorithm is:

+

for all \(x_{t}\):

+
    +
  1. \(\overline{bel}(x_t) = \int p(x_t | u_t, x_{t-1}) bel(x_{t-1})dx\)

  2. +
  3. \(bel(x_t) = \eta p(z_t | x_t) \overline{bel}(x_t)\)

  4. +
+

end.

+

\(\rightarrow\)The first step in filter is to calculate the prior +for the next step that uses the bayes theorem. This is the +Prediction step. The belief, \(\overline{bel}(x_t)\), is +before incorporating measurement(\(z_{t}\)) at time t=t. This +is the step where the motion occurs and information is lost because the +means and covariances of the gaussians are added. The RHS of the +equation incorporates the law of total probability for prior +calculation.

+

\(\rightarrow\) This is the Correction or update step that +calculates the belief of the robot after taking into account the +measurement(\(z_{t}\)) at time t=t. This is where we incorporate +the sensor information for the whereabouts of the robot. We gain +information here as the gaussians get multiplied here. (Multiplication +of gaussian values allows the resultant to lie in between these numbers +and the resultant covariance is smaller.

+
+
+

Bayes filter localization example:

+
from IPython.display import Image
+Image(filename="bayes_filter.png",width=400)
+
+
+../../_images/Kalmanfilter_basics_2_5_0.png +

Given - A robot with a sensor to detect doorways along a hallway. Also, +the robot knows how the hallway looks like but doesn’t know where it is +in the map.

+
    +
  1. Initially(first scenario), it doesn’t know where it is with respect +to the map and hence the belief assigns equal probability to each +location in the map.

  2. +
  3. The first sensor reading is incorporated and it shows the presence of +a door. Now the robot knows how the map looks like but cannot +localize yet as map has 3 doors present. Therefore it assigns equal +probability to each door present.

  4. +
  5. The robot now moves forward. This is the prediction step and the +motion causes the robot to lose some of the information and hence the +variance of the gaussians increase (diagram 4.). The final belief is +convolution of posterior from previous step and the current state +after motion. Also, the means shift on the right due to the motion.

  6. +
  7. Again, incorporating the measurement, the sensor senses a door and +this time too the possibility of door is equal for the three door. +This is where the filter’s magic kicks in. For the final belief +(diagram 5.), the posterior calculated after sensing is mixed or +convolution of previous posterior and measurement. It improves +the robot’s belief at location near to the second door. The variance +decreases and peaks.

  8. +
  9. Finally after series of iterations of motion and correction, the +robot is able to localize itself with respect to the +environment.(diagram 6.)

  10. +
+

Do note that the robot knows the map but doesn’t know where exactly it +is on the map.

+
+
+

Bayes and Kalman filter structure

+

The basic structure and the concept remains the same as bayes filter for +Kalman. The only key difference is the mathematical representation of +Kalman filter. The Kalman filter is nothing but a bayesian filter that +uses Gaussians.

+

For a bayes filter to be a Kalman filter, each term of belief is now a +gaussian, unlike histograms. The basic formulation for the bayes +filter algorithm is:

+
+\[\begin{split}\begin{aligned} +\bar {\mathbf x} &= \mathbf x \ast f_{\mathbf x}(\bullet)\, \, &\text{Prediction} \\ +\mathbf x &= \mathcal L \cdot \bar{\mathbf x}\, \, &\text{Correction} +\end{aligned}\end{split}\]
+

\(\bar{\mathbf x}\) is the prior

+

\(\mathcal L\) is the likelihood of a measurement given the prior +\(\bar{\mathbf x}\)

+

\(f_{\mathbf x}(\bullet)\) is the process model or the gaussian +term that helps predict the next state like velocity to track position +or acceleration.

+

\(\ast\) denotes convolution.

+
+
+

Kalman Gain

+
+\[x = (\mathcal L \bar x)\]
+

Where x is posterior and \(\mathcal L\) and \(\bar x\) are +gaussians.

+

Therefore the mean of the posterior is given by:

+
+\[\mu=\frac{\bar\sigma^2\, \mu_z + \sigma_z^2 \, \bar\mu} {\bar\sigma^2 + \sigma_z^2}\]
+
+\[\mu = \left( \frac{\bar\sigma^2}{\bar\sigma^2 + \sigma_z^2}\right) \mu_z + \left(\frac{\sigma_z^2}{\bar\sigma^2 + \sigma_z^2}\right)\bar\mu\]
+

In this form it is easy to see that we are scaling the measurement and +the prior by weights:

+
+\[\mu = W_1 \mu_z + W_2 \bar\mu\]
+

The weights sum to one because the denominator is a normalization term. +We introduce a new term, \(K=W_1\), giving us:

+
+\[\begin{split}\begin{aligned} +\mu &= K \mu_z + (1-K) \bar\mu\\ +&= \bar\mu + K(\mu_z - \bar\mu) +\end{aligned}\end{split}\]
+

where

+
+\[K = \frac {\bar\sigma^2}{\bar\sigma^2 + \sigma_z^2}\]
+

The variance in terms of the Kalman gain:

+
+\[\begin{split}\begin{aligned} +\sigma^2 &= \frac{\bar\sigma^2 \sigma_z^2 } {\bar\sigma^2 + \sigma_z^2} \\ +&= K\sigma_z^2 \\ +&= (1-K)\bar\sigma^2 +\end{aligned}\end{split}\]
+

\(K\) is the Kalman gain. It’s the crux of the Kalman filter. It +is a scaling term that chooses a value partway between \(\mu_z\) and +\(\bar\mu\).

+
+
+

Kalman Filter - Univariate and Multivariate

+

Prediction

+

\(\begin{array}{|l|l|l|} \hline \text{Univariate} & \text{Univariate} & \text{Multivariate}\\ & \text{(Kalman form)} & \\ \hline \bar \mu = \mu + \mu_{f_x} & \bar x = x + dx & \bar{\mathbf x} = \mathbf{Fx} + \mathbf{Bu}\\ \bar\sigma^2 = \sigma_x^2 + \sigma_{f_x}^2 & \bar P = P + Q & \bar{\mathbf P} = \mathbf{FPF}^\mathsf T + \mathbf Q \\ \hline \end{array}\)

+

\(\mathbf x,\, \mathbf P\) are the state mean and covariance. They +correspond to \(x\) and \(\sigma^2\).

+

\(\mathbf F\) is the state transition function. When multiplied by +\(\bf x\) it computes the prior.

+

\(\mathbf Q\) is the process covariance. It corresponds to +\(\sigma^2_{f_x}\).

+

\(\mathbf B\) and \(\mathbf u\) are model control inputs to the +system.

+

Correction

+

\(\begin{array}{|l|l|l|} \hline \text{Univariate} & \text{Univariate} & \text{Multivariate}\\ & \text{(Kalman form)} & \\ \hline & y = z - \bar x & \mathbf y = \mathbf z - \mathbf{H\bar x} \\ & K = \frac{\bar P}{\bar P+R}& \mathbf K = \mathbf{\bar{P}H}^\mathsf T (\mathbf{H\bar{P}H}^\mathsf T + \mathbf R)^{-1} \\ \mu=\frac{\bar\sigma^2\, \mu_z + \sigma_z^2 \, \bar\mu} {\bar\sigma^2 + \sigma_z^2} & x = \bar x + Ky & \mathbf x = \bar{\mathbf x} + \mathbf{Ky} \\ \sigma^2 = \frac{\sigma_1^2\sigma_2^2}{\sigma_1^2+\sigma_2^2} & P = (1-K)\bar P & \mathbf P = (\mathbf I - \mathbf{KH})\mathbf{\bar{P}} \\ \hline \end{array}\)

+

\(\mathbf H\) is the measurement function.

+

\(\mathbf z,\, \mathbf R\) are the measurement mean and noise +covariance. They correspond to \(z\) and \(\sigma_z^2\) in the +univariate filter. \(\mathbf y\) and \(\mathbf K\) are the +residual and Kalman gain.

+

The details will be different than the univariate filter because these +are vectors and matrices, but the concepts are exactly the same:

+
    +
  • Use a Gaussian to represent our estimate of the state and error

  • +
  • Use a Gaussian to represent the measurement and its error

  • +
  • Use a Gaussian to represent the process model

  • +
  • Use the process model to predict the next state (the prior)

  • +
  • Form an estimate part way between the measurement and the prior

  • +
+
+
+

References:

+
    +
  1. Roger Labbe’s +repo +on Kalman Filters. (Majority of text in the notes are from this)

  2. +
  3. Probabilistic Robotics by Sebastian Thrun, Wolfram Burgard and Dieter +Fox, MIT Press.

  4. +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/appendix/appendix.html b/modules/appendix/appendix.html new file mode 100644 index 0000000000..9ef705b7a6 --- /dev/null +++ b/modules/appendix/appendix.html @@ -0,0 +1,170 @@ + + + + + + + Appendix — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/arm_navigation/arm_navigation.html b/modules/arm_navigation/arm_navigation.html new file mode 100644 index 0000000000..ce8a99c130 --- /dev/null +++ b/modules/arm_navigation/arm_navigation.html @@ -0,0 +1,157 @@ + + + + + + + Arm Navigation — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/arm_navigation/n_joint_arm_to_point_control.html b/modules/arm_navigation/n_joint_arm_to_point_control.html new file mode 100644 index 0000000000..d903f41397 --- /dev/null +++ b/modules/arm_navigation/n_joint_arm_to_point_control.html @@ -0,0 +1,153 @@ + + + + + + + N joint arm to point control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

N joint arm to point control

+

N joint arm to a point control simulation.

+

This is a interactive simulation.

+

You can set the goal position of the end effector with left-click on the +plotting area.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/n_joint_arm_to_point_control/animation.gif +

In this simulation N = 10, however, you can change it.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/arm_navigation/obstacle_avoidance_arm_navigation.html b/modules/arm_navigation/obstacle_avoidance_arm_navigation.html new file mode 100644 index 0000000000..b5b7bcd5cd --- /dev/null +++ b/modules/arm_navigation/obstacle_avoidance_arm_navigation.html @@ -0,0 +1,149 @@ + + + + + + + Arm navigation with obstacle avoidance — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Arm navigation with obstacle avoidance

+

Arm navigation with obstacle avoidance simulation.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/ArmNavigation/arm_obstacle_navigation/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/arm_navigation/planar_two_link_ik.html b/modules/arm_navigation/planar_two_link_ik.html new file mode 100644 index 0000000000..f4852440cf --- /dev/null +++ b/modules/arm_navigation/planar_two_link_ik.html @@ -0,0 +1,447 @@ + + + + + + + Two joint arm to point control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Two joint arm to point control

+
+TwoJointArmToPointControl +
+

This is two joint arm to a point control simulation.

+

This is a interactive simulation.

+

You can set the goal position of the end effector with left-click on the plotting area.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/bipedal/bipedal.html b/modules/bipedal/bipedal.html new file mode 100644 index 0000000000..361d38aa40 --- /dev/null +++ b/modules/bipedal/bipedal.html @@ -0,0 +1,150 @@ + + + + + + + Bipedal — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bipedal

+
+

Contents

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/bipedal/bipedal_planner/bipedal_planner.html b/modules/bipedal/bipedal_planner/bipedal_planner.html new file mode 100644 index 0000000000..a2dd022b65 --- /dev/null +++ b/modules/bipedal/bipedal_planner/bipedal_planner.html @@ -0,0 +1,147 @@ + + + + + + + Bipedal Planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bipedal Planner

+

Bipedal Walking with modifying designated footsteps

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Bipedal/bipedal_planner/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/control/control.html b/modules/control/control.html new file mode 100644 index 0000000000..85cb8a1ad6 --- /dev/null +++ b/modules/control/control.html @@ -0,0 +1,164 @@ + + + + + + + Control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/control/inverted_pendulum_control/inverted_pendulum_control.html b/modules/control/inverted_pendulum_control/inverted_pendulum_control.html new file mode 100644 index 0000000000..3ad51024b7 --- /dev/null +++ b/modules/control/inverted_pendulum_control/inverted_pendulum_control.html @@ -0,0 +1,226 @@ + + + + + + + Inverted Pendulum Control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Inverted Pendulum Control

+

An inverted pendulum on a cart consists of a mass \(m\) at the top of a pole of length \(l\) pivoted on a +horizontally moving base as shown in the adjacent.

+

The objective of the control system is to balance the inverted pendulum by applying a force to the cart that the pendulum is attached to.

+
+

Modeling

+../../../_images/inverted-pendulum.png +
    +
  • \(M\): mass of the cart

  • +
  • \(m\): mass of the load on the top of the rod

  • +
  • \(l\): length of the rod

  • +
  • \(u\): force applied to the cart

  • +
  • \(x\): cart position coordinate

  • +
  • \(\theta\): pendulum angle from vertical

  • +
+

Using Lagrange’s equations:

+
+\[\begin{split}& (M + m)\ddot{x} - ml\ddot{\theta}cos{\theta} + ml\dot{\theta^2}\sin{\theta} = u \\ +& l\ddot{\theta} - g\sin{\theta} = \ddot{x}\cos{\theta}\end{split}\]
+

See this link for more details.

+

So

+
+\[\begin{split}& \ddot{x} = \frac{m(gcos{\theta} - \dot{\theta}^2l)sin{\theta} + u}{M + m - mcos^2{\theta}} \\ +& \ddot{\theta} = \frac{g(M + m)sin{\theta} - \dot{\theta}^2lmsin{\theta}cos{\theta} + ucos{\theta}}{l(M + m - mcos^2{\theta})}\end{split}\]
+

Linearized model when \(\theta\) small, \(cos{\theta} \approx 1\), \(sin{\theta} \approx \theta\), \(\dot{\theta}^2 \approx 0\).

+
+\[\begin{split}& \ddot{x} = \frac{gm}{M}\theta + \frac{1}{M}u\\ +& \ddot{\theta} = \frac{g(M + m)}{Ml}\theta + \frac{1}{Ml}u\end{split}\]
+

State space:

+
+\[\begin{split}& \dot{x} = Ax + Bu \\ +& y = Cx + Du\end{split}\]
+

where

+
+\[\begin{split}& x = [x, \dot{x}, \theta,\dot{\theta}]\\ +& A = \begin{bmatrix} 0 & 1 & 0 & 0\\0 & 0 & \frac{gm}{M} & 0\\0 & 0 & 0 & 1\\0 & 0 & \frac{g(M + m)}{Ml} & 0 \end{bmatrix}\\ +& B = \begin{bmatrix} 0 \\ \frac{1}{M} \\ 0 \\ \frac{1}{Ml} \end{bmatrix}\end{split}\]
+

If control only theta

+
+\[\begin{split}& C = \begin{bmatrix} 0 & 0 & 1 & 0 \end{bmatrix}\\ +& D = [0]\end{split}\]
+

If control x and theta

+
+\[\begin{split}& C = \begin{bmatrix} 1 & 0 & 0 & 0\\0 & 0 & 1 & 0 \end{bmatrix}\\ +& D = \begin{bmatrix} 0 \\ 0 \end{bmatrix}\end{split}\]
+
+
+

LQR control

+

The LQR controller minimize this cost function defined as:

+
+\[J = x^T Q x + u^T R u\]
+

the feedback control law that minimizes the value of the cost is:

+
+\[u = -K x\]
+

where:

+
+\[K = (B^T P B + R)^{-1} B^T P A\]
+

and \(P\) is the unique positive definite solution to the discrete time +algebraic Riccati equation (DARE):

+
+\[P = A^T P A - A^T P B ( R + B^T P B )^{-1} B^T P A + Q\]
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation_lqr.gif +
+
+

MPC control

+

The MPC controller minimize this cost function defined as:

+
+\[J = x^T Q x + u^T R u\]
+

subject to:

+
    +
  • Linearized Inverted Pendulum model

  • +
  • Initial state

  • +
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation.gif +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/control/move_to_a_pose_control/move_to_a_pose_control.html b/modules/control/move_to_a_pose_control/move_to_a_pose_control.html new file mode 100644 index 0000000000..b23d7da1e4 --- /dev/null +++ b/modules/control/move_to_a_pose_control/move_to_a_pose_control.html @@ -0,0 +1,338 @@ + + + + + + + Move to a Pose Control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Move to a Pose Control

+

In this section, we present the logic of PathFinderController that drives a car from a start pose (x, y, theta) to a goal pose. A simulation of moving to a pose control is presented below.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/move_to_pose/animation.gif +
+

Position Control of non-Holonomic Systems

+

This section explains the logic of a position controller for systems with constraint (non-Holonomic system).

+

The position control of a 1-DOF (Degree of Freedom) system is quite straightforward. We only need to compute a position error and multiply it with a proportional gain to create a command. The actuator of the system takes this command and drive the system to the target position. This controller can be easily extended to higher dimensions (e.g., using Kp_x and Kp_y gains for a 2D position control). In these systems, the number of control commands is equal to the number of degrees of freedom (Holonomic system).

+

To describe the configuration of a car on a 2D plane, we need three DOFs (i.e., x, y, and theta). But to drive a car we only need two control commands (theta_engine and theta_steering_wheel). This difference is because of a constraint between the x and y DOFs. The relationship between the delta_x and delta_y is governed by the theta_steering_wheel.

+

Note that a car is normally a non-Holonomic system but if the road is slippery, the car turns into a Holonomic system and thus it needs three independent commands to be controlled.

+
+
+

PathFinderController class

+
+

Constructor

+
PathFinderController(Kp_rho, Kp_alpha, Kp_beta)
+
+
+

Constructs an instantiate of the PathFinderController for navigating a 3-DOF wheeled robot on a 2D plane.

+

Parameters:

+
    +
  • +
    Kp_rho : The linear velocity gain to translate the robot along a line towards the goal
    +
    +
  • +
  • +
    Kp_alpha : The angular velocity gain to rotate the robot towards the goal
    +
    +
  • +
  • +
    Kp_beta : The offset angular velocity gain accounting for smooth merging to the goal angle (i.e., it helps the robot heading to be parallel to the target angle.)
    +
    +
  • +
+
+
+

Member function(s)

+
calc_control_command(x_diff, y_diff, theta, theta_goal)
+
+
+

Returns the control command for the linear and angular velocities as well as the distance to goal

+

Parameters:

+
    +
  • +
    x_diff : The position of target with respect to current robot position in x direction
    +
    +
  • +
  • +
    y_diff : The position of target with respect to current robot position in y direction
    +
    +
  • +
  • +
    theta : The current heading angle of robot with respect to x axis
    +
    +
  • +
  • +
    theta_goal : The target angle of robot with respect to x axis
    +
    +
  • +
+

Returns:

+
    +
  • +
    rho : The distance between the robot and the goal position
    +
    +
  • +
  • +
    v : Command linear velocity
    +
    +
  • +
  • +
    w : Command angular velocity
    +
    +
  • +
+
+
+
+

How does the Algorithm Work

+

The distance between the robot and the goal position, \(\rho\), is computed as

+
+\[\rho = \sqrt{(x_{robot} - x_{target})^2 + (y_{robot} - y_{target})^2}.\]
+

The distance \(\rho\) is used to determine the robot speed. The idea is to slow down the robot as it gets closer to the target.

+
+(1)\[v = K_P{_\rho} \times \rho\qquad\]
+

Note that for your applications, you need to tune the speed gain, \(K_P{_\rho}\) to a proper value.

+

To turn the robot and align its heading, \(\theta\), toward the target position (not orientation), \(\rho \vec{u}\), we need to compute the angle difference \(\alpha\).

+
+\[\alpha = (\arctan2(y_{diff}, x_{diff}) - \theta + \pi) mod (2\pi) - \pi\]
+

The term \(mod(2\pi)\) is used to map the angle to \([-\pi, \pi)\) range.

+

Lastly to correct the orientation of the robot, we need to compute the orientation error, \(\beta\), of the robot.

+
+\[\beta = (\theta_{goal} - \theta - \alpha + \pi) mod (2\pi) - \pi\]
+

Note that to cancel out the effect of \(\alpha\) when the robot is at the vicinity of the target, the term

+

\(-\alpha\) is included.

+

The final angular speed command is given by

+
+(2)\[\omega = K_P{_\alpha} \alpha - K_P{_\beta} \beta\qquad\]
+

The linear and angular speeds (Equations (1) and (2)) are the output of the algorithm.

+
+
+

Move to a Pose Robot (Class)

+

This program (move_to_pose_robot.py) provides a Robot class to define different robots with different specifications. +Using this class, you can simulate different robots simultaneously and compare the effect of your parameter settings.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/move_to_pos_robot_class/animation.gif +

Note: The robot class is based on PathFinderController class in ‘the move_to_pose.py’.

+
+

Robot Class

+
+
+

Constructor

+
Robot(name, color, max_linear_speed, max_angular_speed, path_finder_controller)
+
+
+

Constructs an instantiate of the 3-DOF wheeled Robot navigating on a 2D plane

+

Parameters:

+
    +
  • +
    name : (string) The name of the robot
    +
    +
  • +
  • +
    color : (string) The color of the robot
    +
    +
  • +
  • +
    max_linear_speed : (float) The maximum linear speed that the robot can go
    +
    +
  • +
  • +
    max_angular_speed : (float) The maximum angular speed that the robot can rotate about its vertical axis
    +
    +
  • +
  • +
    path_finder_controller : (PathFinderController) A configurable controller to finds the path and calculates command linear and angular velocities.
    +
    +
  • +
+
+
+

Member function(s)

+
set_start_target_poses(pose_start, pose_target)
+
+
+

Sets the start and target positions of the robot.

+

Parameters:

+
    +
  • +
    pose_start : (Pose) Start postion of the robot (see the Pose class)
    +
    +
  • +
  • +
    pose_target : (Pose) Target postion of the robot (see the Pose class)
    +
    +
  • +
+
move(dt)
+
+
+

Move the robot for one time step increment

+

Parameters:

+
    +
  • +
    dt : <float> time increment
    +
    +
  • +
+
+
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/introduction.html b/modules/introduction.html new file mode 100644 index 0000000000..802694e74a --- /dev/null +++ b/modules/introduction.html @@ -0,0 +1,179 @@ + + + + + + + Introduction — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Introduction

+

TBD

+
+

Definition Of Robotics

+

TBD

+
+
+

History Of Robotics

+

TBD

+
+
+

Application Of Robotics

+

TBD

+
+
+

Software for Robotics

+

TBD

+
+
+

Software for Robotics

+

TBD

+
+
+

Python for Robotics

+

TBD

+
+
+

Learning Robotics Algorithms

+

TBD

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization.html b/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization.html new file mode 100644 index 0000000000..4dd3ad2719 --- /dev/null +++ b/modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization.html @@ -0,0 +1,153 @@ + + + + + + + Ensamble Kalman Filter Localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Ensamble Kalman Filter Localization

+
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/ensamble_kalman_filter/animation.gif +
+

This is a sensor fusion localization with Ensamble Kalman Filter(EnKF).

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization.html b/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization.html new file mode 100644 index 0000000000..3713fd728b --- /dev/null +++ b/modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization.html @@ -0,0 +1,253 @@ + + + + + + + Extended Kalman Filter Localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Extended Kalman Filter Localization

+../../../_images/extended_kalman_filter_localization_1_0.png +
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/extended_kalman_filter/animation.gif +
+

This is a sensor fusion localization with Extended Kalman Filter(EKF).

+

The blue line is true trajectory, the black line is dead reckoning +trajectory,

+

the green point is positioning observation (ex. GPS), and the red line +is estimated trajectory with EKF.

+

The red ellipse is estimated covariance ellipse with EKF.

+

Code: PythonRobotics/extended_kalman_filter.py at master · +AtsushiSakai/PythonRobotics

+
+

Filter design

+

In this simulation, the robot has a state vector includes 4 states at +time \(t\).

+
+\[\textbf{x}_t=[x_t, y_t, \phi_t, v_t]\]
+

x, y are a 2D x-y position, \(\phi\) is orientation, and v is +velocity.

+

In the code, “xEst” means the state vector. +code

+

And, \(P_t\) is covariace matrix of the state,

+

\(Q\) is covariance matrix of process noise,

+

\(R\) is covariance matrix of observation noise at time \(t\)

+

The robot has a speed sensor and a gyro sensor.

+

So, the input vecor can be used as each time step

+
+\[\textbf{u}_t=[v_t, \omega_t]\]
+

Also, the robot has a GNSS sensor, it means that the robot can observe +x-y position at each time.

+
+\[\textbf{z}_t=[x_t,y_t]\]
+

The input and observation vector includes sensor noise.

+

In the code, “observation” function generates the input and observation +vector with noise +code

+
+
+

Motion Model

+

The robot model is

+
+\[\dot{x} = v \cos(\phi)\]
+
+\[\dot{y} = v \sin(\phi)\]
+
+\[\dot{\phi} = \omega\]
+

So, the motion model is

+
+\[\textbf{x}_{t+1} = f(\textbf{x}_t, \textbf{u}_t) = F\textbf{x}_t+B\textbf{u}_t\]
+

where

+

\(\begin{equation*} F= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 \\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} B= \begin{bmatrix} cos(\phi) \Delta t & 0\\ sin(\phi) \Delta t & 0\\ 0 & \Delta t\\ 1 & 0\\ \end{bmatrix} \end{equation*}\)

+

\(\Delta t\) is a time interval.

+

This is implemented at +code

+

The motion function is that

+

\(\begin{equation*} \begin{bmatrix} x' \\ y' \\ w' \\ v' \end{bmatrix} = f(\textbf{x}, \textbf{u}) = \begin{bmatrix} x + v\cos(\phi)\Delta t \\ y + v\sin(\phi)\Delta t \\ \phi + \omega \Delta t \\ v \end{bmatrix} \end{equation*}\)

+

Its Jacobian matrix is

+

\(\begin{equation*} J_f = \begin{bmatrix} \frac{\partial x'}{\partial x}& \frac{\partial x'}{\partial y} & \frac{\partial x'}{\partial \phi} & \frac{\partial x'}{\partial v}\\ \frac{\partial y'}{\partial x}& \frac{\partial y'}{\partial y} & \frac{\partial y'}{\partial \phi} & \frac{\partial y'}{\partial v}\\ \frac{\partial \phi'}{\partial x}& \frac{\partial \phi'}{\partial y} & \frac{\partial \phi'}{\partial \phi} & \frac{\partial \phi'}{\partial v}\\ \frac{\partial v'}{\partial x}& \frac{\partial v'}{\partial y} & \frac{\partial v'}{\partial \phi} & \frac{\partial v'}{\partial v} \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*}  = \begin{bmatrix} 1& 0 & -v \sin(\phi) \Delta t & \cos(\phi) \Delta t\\ 0 & 1 & v \cos(\phi) \Delta t & \sin(\phi) \Delta t\\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{equation*}\)

+
+
+

Observation Model

+

The robot can get x-y position infomation from GPS.

+

So GPS Observation model is

+
+\[\textbf{z}_{t} = g(\textbf{x}_t) = H \textbf{x}_t\]
+

where

+

\(\begin{equation*} H = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ \end{bmatrix} \end{equation*}\)

+

The observation function states that

+

\(\begin{equation*} \begin{bmatrix} x' \\ y' \end{bmatrix} = g(\textbf{x}) = \begin{bmatrix} x \\ y \end{bmatrix} \end{equation*}\)

+

Its Jacobian matrix is

+

\(\begin{equation*} J_g = \begin{bmatrix} \frac{\partial x'}{\partial x} & \frac{\partial x'}{\partial y} & \frac{\partial x'}{\partial \phi} & \frac{\partial x'}{\partial v}\\ \frac{\partial y'}{\partial x}& \frac{\partial y'}{\partial y} & \frac{\partial y'}{\partial \phi} & \frac{\partial y'}{ \partial v}\\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*}  = \begin{bmatrix} 1& 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ \end{bmatrix} \end{equation*}\)

+
+
+

Extended Kalman Filter

+

Localization process using Extended Kalman Filter:EKF is

+

=== Predict ===

+

\(x_{Pred} = Fx_t+Bu_t\)

+

\(P_{Pred} = J_f P_t J_f^T + Q\)

+

=== Update ===

+

\(z_{Pred} = Hx_{Pred}\)

+

\(y = z - z_{Pred}\)

+

\(S = J_g P_{Pred}.J_g^T + R\)

+

\(K = P_{Pred}.J_g^T S^{-1}\)

+

\(x_{t+1} = x_{Pred} + Ky\)

+

\(P_{t+1} = ( I - K J_g) P_{Pred}\)

+
+
+

Ref:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/localization/histogram_filter_localization/histogram_filter_localization.html b/modules/localization/histogram_filter_localization/histogram_filter_localization.html new file mode 100644 index 0000000000..ae19b9b641 --- /dev/null +++ b/modules/localization/histogram_filter_localization/histogram_filter_localization.html @@ -0,0 +1,234 @@ + + + + + + + Histogram filter localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Histogram filter localization

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/histogram_filter/animation.gif +

This is a 2D localization example with Histogram filter.

+

The red cross is true position, black points are RFID positions.

+

The blue grid shows a position probability of histogram filter.

+

In this simulation, we assume the robot’s yaw orientation and RFID’s positions are known, +but x,y positions are unknown.

+

The filter uses speed input and range observations from RFID for localization.

+

Initial position information is not needed.

+
+

Filtering algorithm

+

Histogram filter is a discrete Bayes filter in continuous space.

+

It uses regular girds to manage probability of the robot existence.

+

If a grid has higher probability, it means that the robot is likely to be there.

+

In the simulation, we want to estimate x-y position, so we use 2D grid data.

+

There are 4 steps for the histogram filter to estimate the probability distribution.

+
+

Step1: Filter initialization

+

Histogram filter does not need initial position information.

+

In that case, we can initialize each grid probability as a same value.

+

If we can use initial position information, we can set initial probabilities based on it.

+

Gaussian grid map might be useful when the initial position information is provided as gaussian distribution.

+
+
+

Step2: Predict probability by motion

+

In histogram filter, when a robot move to a next grid, +all probability information of each grid are shifted towards the movement direction.

+

This process represents the change in the probability distribution as the robot moves.

+

After the robot has moved, the probability distribution needs reflect +the estimation error due to the movement.

+

For example, the position probability is peaky with observations:

+../../../_images/1.png +

But, the probability is getting uncertain without observations:

+../../../_images/2.png +

The gaussian filter +is used in the simulation for adding noize.

+
+
+

Step3: Update probability by observation

+

In this step, all probabilities are updated by observations, +this is the update step of bayesian filter.

+

The probability update formula is different by the used sensor model.

+

This simulation uses range observation model.

+

The probability of each grid is updated by this formula:

+
+\[p_t=p_{t-1}*h(z)\]
+
+\[h(z)=\frac{\exp \left(-(d - z)^{2} / 2\right)}{\sqrt{2 \pi}}\]
+
    +
  • \(p_t\) is the probability at the time t.

  • +
  • \(h(z)\) is the observation probability with the observation z.

  • +
  • \(d\) is the known distance from the RD-ID to the grid center.

  • +
+

When the d is 3.0, the h(z) distribution is:

+../../../_images/4.png +

The observation probability distribution looks a circle when a RF-ID is observed:

+../../../_images/3.png +
+
+

Step4: Estimate position from probability

+

In each time step, we can calculate the final robot position from the current probability distribution. +There are two ways to calculate the final positions:

+
    +
  1. Using the maximum probability grid position.

  2. +
  3. Using the average of probability weighted grind position.

  4. +
+
+
+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/localization/localization.html b/modules/localization/localization.html new file mode 100644 index 0000000000..84790975e4 --- /dev/null +++ b/modules/localization/localization.html @@ -0,0 +1,176 @@ + + + + + + + Localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/localization/particle_filter_localization/particle_filter_localization.html b/modules/localization/particle_filter_localization/particle_filter_localization.html new file mode 100644 index 0000000000..2e1724e4dc --- /dev/null +++ b/modules/localization/particle_filter_localization/particle_filter_localization.html @@ -0,0 +1,181 @@ + + + + + + + Particle filter localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Particle filter localization

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/particle_filter/animation.gif +

This is a sensor fusion localization with Particle Filter(PF).

+

The blue line is true trajectory, the black line is dead reckoning +trajectory,

+

and the red line is estimated trajectory with PF.

+

It is assumed that the robot can measure a distance from landmarks +(RFID).

+

This measurements are used for PF localization.

+
+

How to calculate covariance matrix from particles

+

The covariance matrix \(\Xi\) from particle information is calculated by the following equation:

+
+\[\Xi_{j,k}=\frac{1}{1-\sum^N_{i=1}(w^i)^2}\sum^N_{i=1}w^i(x^i_j-\mu_j)(x^i_k-\mu_k)\]
+
    +
  • \(\Xi_{j,k}\) is covariance matrix element at row \(i\) and column \(k\).

  • +
  • \(w^i\) is the weight of the \(i\) th particle.

  • +
  • \(x^i_j\) is the \(j\) th state of the \(i\) th particle.

  • +
  • \(\mu_j\) is the \(j\) th mean state of particles.

  • +
+
+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization.html b/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization.html new file mode 100644 index 0000000000..cd7b04b279 --- /dev/null +++ b/modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization.html @@ -0,0 +1,161 @@ + + + + + + + Unscented Kalman Filter localization — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Unscented Kalman Filter localization

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Localization/unscented_kalman_filter/animation.gif +

This is a sensor fusion localization with Unscented Kalman Filter(UKF).

+

The lines and points are same meaning of the EKF simulation.

+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/circle_fitting/circle_fitting.html b/modules/mapping/circle_fitting/circle_fitting.html new file mode 100644 index 0000000000..b9daaf8c24 --- /dev/null +++ b/modules/mapping/circle_fitting/circle_fitting.html @@ -0,0 +1,158 @@ + + + + + + + Object shape recognition using circle fitting — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Object shape recognition using circle fitting

+

This is an object shape recognition using circle fitting.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/circle_fitting/animation.gif +

The blue circle is the true object shape.

+

The red crosses are observations from a ranging sensor.

+

The red circle is the estimated object shape using circle fitting.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/gaussian_grid_map/gaussian_grid_map.html b/modules/mapping/gaussian_grid_map/gaussian_grid_map.html new file mode 100644 index 0000000000..99b39f1fbf --- /dev/null +++ b/modules/mapping/gaussian_grid_map/gaussian_grid_map.html @@ -0,0 +1,155 @@ + + + + + + + Gaussian grid map — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Gaussian grid map

+

This is a 2D Gaussian grid mapping example.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/gaussian_grid_map/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/k_means_object_clustering/k_means_object_clustering.html b/modules/mapping/k_means_object_clustering/k_means_object_clustering.html new file mode 100644 index 0000000000..478e7088d1 --- /dev/null +++ b/modules/mapping/k_means_object_clustering/k_means_object_clustering.html @@ -0,0 +1,155 @@ + + + + + + + k-means object clustering — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

k-means object clustering

+

This is a 2D object clustering with k-means algorithm.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/kmeans_clustering/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial.html b/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial.html new file mode 100644 index 0000000000..3da16e7dfc --- /dev/null +++ b/modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial.html @@ -0,0 +1,310 @@ + + + + + + + Lidar to grid map — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Lidar to grid map

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/lidar_to_grid_map/animation.gif +

This simple tutorial shows how to read LIDAR (range) measurements from a +file and convert it to occupancy grid.

+

Occupancy grid maps (Hans Moravec, A.E. Elfes: High resolution maps +from wide angle sonar, Proc. IEEE Int. Conf. Robotics Autom. (1985)) +are a popular, probabilistic approach to represent the environment. The +grid is basically discrete representation of the environment, which +shows if a grid cell is occupied or not. Here the map is represented as +a numpy array, and numbers close to 1 means the cell is occupied +(marked with red on the next image), numbers close to 0 means they are +free (marked with green). The grid has the ability to represent +unknown (unobserved) areas, which are close to 0.5.

+
+../../../_images/grid_map_example.png +
+

In order to construct the grid map from the measurement we need to +discretise the values. But, first let’s need to import some +necessary packages.

+
import math
+import numpy as np
+import matplotlib.pyplot as plt
+from math import cos, sin, radians, pi
+
+
+

The measurement file contains the distances and the corresponding angles +in a csv (comma separated values) format. Let’s write the +file_read method:

+
def file_read(f):
+    """
+    Reading LIDAR laser beams (angles and corresponding distance data)
+    """
+    measures = [line.split(",") for line in open(f)]
+    angles = []
+    distances = []
+    for measure in measures:
+        angles.append(float(measure[0]))
+        distances.append(float(measure[1]))
+    angles = np.array(angles)
+    distances = np.array(distances)
+    return angles, distances
+
+
+

From the distances and the angles it is easy to determine the x and +y coordinates with sin and cos. In order to display it +matplotlib.pyplot (plt) is used.

+
ang, dist = file_read("lidar01.csv")
+ox = np.sin(ang) * dist
+oy = np.cos(ang) * dist
+plt.figure(figsize=(6,10))
+plt.plot([oy, np.zeros(np.size(oy))], [ox, np.zeros(np.size(oy))], "ro-") # lines from 0,0 to the
+plt.axis("equal")
+bottom, top = plt.ylim()  # return the current ylim
+plt.ylim((top, bottom)) # rescale y axis, to match the grid orientation
+plt.grid(True)
+plt.show()
+
+
+../../../_images/lidar_to_grid_map_tutorial_5_0.png +

The lidar_to_grid_map.py contains handy functions which can used to +convert a 2D range measurement to a grid map. For example the +bresenham gives the a straight line between two points in a grid +map. Let’s see how this works.

+
import lidar_to_grid_map as lg
+map1 = np.ones((50, 50)) * 0.5
+line = lg.bresenham((2, 2), (40, 30))
+for l in line:
+    map1[l[0]][l[1]] = 1
+plt.imshow(map1)
+plt.colorbar()
+plt.show()
+
+
+../../../_images/lidar_to_grid_map_tutorial_7_0.png +
line = lg.bresenham((2, 30), (40, 30))
+for l in line:
+    map1[l[0]][l[1]] = 1
+line = lg.bresenham((2, 30), (2, 2))
+for l in line:
+    map1[l[0]][l[1]] = 1
+plt.imshow(map1)
+plt.colorbar()
+plt.show()
+
+
+../../../_images/lidar_to_grid_map_tutorial_8_0.png +

To fill empty areas, a queue-based algorithm can be used that can be +used on an initialized occupancy map. The center point is given: the +algorithm checks for neighbour elements in each iteration, and stops +expansion on obstacles and free boundaries.

+
from collections import deque
+def flood_fill(cpoint, pmap):
+    """
+    cpoint: starting point (x,y) of fill
+    pmap: occupancy map generated from Bresenham ray-tracing
+    """
+    # Fill empty areas with queue method
+    sx, sy = pmap.shape
+    fringe = deque()
+    fringe.appendleft(cpoint)
+    while fringe:
+        n = fringe.pop()
+        nx, ny = n
+        # West
+        if nx > 0:
+            if pmap[nx - 1, ny] == 0.5:
+                pmap[nx - 1, ny] = 0.0
+                fringe.appendleft((nx - 1, ny))
+        # East
+        if nx < sx - 1:
+            if pmap[nx + 1, ny] == 0.5:
+                pmap[nx + 1, ny] = 0.0
+                fringe.appendleft((nx + 1, ny))
+        # North
+        if ny > 0:
+            if pmap[nx, ny - 1] == 0.5:
+                pmap[nx, ny - 1] = 0.0
+                fringe.appendleft((nx, ny - 1))
+        # South
+        if ny < sy - 1:
+            if pmap[nx, ny + 1] == 0.5:
+                pmap[nx, ny + 1] = 0.0
+                fringe.appendleft((nx, ny + 1))
+
+
+

This algotihm will fill the area bounded by the yellow lines starting +from a center point (e.g. (10, 20)) with zeros:

+
flood_fill((10, 20), map1)
+map_float = np.array(map1)/10.0
+plt.imshow(map1)
+plt.colorbar()
+plt.show()
+
+
+../../../_images/lidar_to_grid_map_tutorial_12_0.png +

Let’s use this flood fill on real data:

+
xyreso = 0.02  # x-y grid resolution
+yawreso = math.radians(3.1)  # yaw angle resolution [rad]
+ang, dist = file_read("lidar01.csv")
+ox = np.sin(ang) * dist
+oy = np.cos(ang) * dist
+pmap, minx, maxx, miny, maxy, xyreso = lg.generate_ray_casting_grid_map(ox, oy, xyreso, False)
+xyres = np.array(pmap).shape
+plt.figure(figsize=(20,8))
+plt.subplot(122)
+plt.imshow(pmap, cmap = "PiYG_r")
+plt.clim(-0.4, 1.4)
+plt.gca().set_xticks(np.arange(-.5, xyres[1], 1), minor = True)
+plt.gca().set_yticks(np.arange(-.5, xyres[0], 1), minor = True)
+plt.grid(True, which="minor", color="w", linewidth = .6, alpha = 0.5)
+plt.colorbar()
+plt.show()
+
+
+
The grid map is  150 x 100 .
+
+
+../../../_images/lidar_to_grid_map_tutorial_14_1.png +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/mapping.html b/modules/mapping/mapping.html new file mode 100644 index 0000000000..e985cc48fa --- /dev/null +++ b/modules/mapping/mapping.html @@ -0,0 +1,186 @@ + + + + + + + Mapping — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/mapping/ndt_map/ndt_map.html b/modules/mapping/ndt_map/ndt_map.html new file mode 100644 index 0000000000..f127e932a4 --- /dev/null +++ b/modules/mapping/ndt_map/ndt_map.html @@ -0,0 +1,281 @@ + + + + + + + Normal Distance Transform (NDT) map — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Normal Distance Transform (NDT) map

+

This is a NDT mapping example.

+

Normal Distribution Transform (NDT) is a map representation that uses normal distribution for observation point modeling.

+
+

Normal Distribution

+

Normal distribution consists of two parameters: mean \(\mu\) and covariance \(\Sigma\).

+

\(\mathbf{X} \sim \mathcal{N}(\boldsymbol{\mu}, \boldsymbol{\Sigma})\)

+

In the 2D case, \(\boldsymbol{\mu}\) is a 2D vector and \(\boldsymbol{\Sigma}\) is a 2x2 matrix.

+

In the matrix form, the probability density function of thr normal distribution is:

+

\(X=\frac{1}{\sqrt{(2 \pi)^2|\Sigma|}} \exp \left\{-\frac{1}{2}^t(x-\mu) \sum^{-1}(x-\mu)\right\}\)

+
+
+

Normal Distance Transform mapping steps

+

NDT mapping consists of two steps:

+

When we have a new observation like this:

+
+../../../_images/raw_observations.png +
+

First, we need to cluster the observation points. +This is done by using a grid based clustering algorithm.

+

The result is:

+
+../../../_images/grid_clustering.png +
+

Then, we need to fit a normal distribution to each grid cluster.

+

Black ellipse shows each NDT grid like this:

+
+../../../_images/ndt_map1.png +
+
+../../../_images/ndt_map2.png +
+
+
+

API

+
+
+class Mapping.ndt_map.ndt_map.NDTMap(ox, oy, resolution)[source]
+

Normal Distribution Transform (NDT) map class

+
+
Parameters
+
    +
  • ox – obstacle x position list

  • +
  • oy – obstacle y position list

  • +
  • resolution – grid resolution [m]

  • +
+
+
+
+
+class NDTGrid[source]
+

NDT grid

+
+
+center_grid_x
+

Center x position of the NDT grid

+
+ +
+
+center_grid_y
+

Center y position of the NDT grid

+
+ +
+
+covariance
+

Covariance matrix of the NDT grid

+
+ +
+
+eig_values
+

Eigen values of the NDT grid

+
+ +
+
+eig_vec
+

Eigen vectors of the NDT grid

+
+ +
+
+mean_x
+

Mean x position of points in the NDTGrid cell

+
+ +
+
+mean_y
+

Mean y position of points in the NDTGrid cell

+
+ +
+
+n_points
+

Number of points in the NDTGrid grid

+
+ +
+ +
+
+grid_index_map
+

NDT grid index map

+
+ +
+
+min_n_points
+

Minimum number of points in the NDT grid

+
+ +
+
+resolution
+

Resolution of the NDT grid [m]

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/normal_vector_estimation/normal_vector_estimation.html b/modules/mapping/normal_vector_estimation/normal_vector_estimation.html new file mode 100644 index 0000000000..be73260552 --- /dev/null +++ b/modules/mapping/normal_vector_estimation/normal_vector_estimation.html @@ -0,0 +1,261 @@ + + + + + + + Normal vector estimation — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Normal vector estimation

+
+

Normal vector calculation of a 3D triangle

+

A 3D point is as a vector:

+
+\[p = [x, y, z]\]
+

When there are 3 points in 3D space, \(p_1, p_2, p_3\),

+

we can calculate a normal vector n of a 3D triangle which is consisted of the points.

+
+\[n = \frac{v1 \times v2}{|v1 \times v2|}\]
+

where

+
+\[v1 = p2 - p1\]
+
+\[v2 = p3 - p1\]
+

This is an example of normal vector calculation:

+
+../../../_images/normal_vector_calc.png +
+
+

API

+
+
+Mapping.normal_vector_estimation.normal_vector_estimation.calc_normal_vector(p1, p2, p3)[source]
+

Calculate normal vector of triangle

+
+
Parameters
+
    +
  • p1 (np.array) – 3D point

  • +
  • p2 (np.array) – 3D point

  • +
  • p3 (np.array) – 3D point

  • +
+
+
Returns
+

normal_vector – normal vector (3,)

+
+
Return type
+

np.array

+
+
+
+ +
+
+
+

Normal vector estimation with RANdam SAmpling Consensus(RANSAC)

+

Consider the problem of estimating the normal vector of a plane based on a +set of N 3D points where a plane can be observed.

+

There is a way that uses all point cloud data to estimate a plane and +a normal vector using the least-squares method

+

However, this method is vulnerable to noise of the point cloud.

+

In this document, we will use a method that uses +RANdam SAmpling Consensus(RANSAC) +to estimate a plane and a normal vector.

+

RANSAC is a robust estimation methods for data set with outliers.

+

This RANSAC based normal vector estimation method is as follows:

+
    +
  1. Select 3 points randomly from the point cloud.

  2. +
  3. Calculate a normal vector of a plane which is consists of the sampled 3 points.

  4. +
  5. Calculate the distance between the calculated plane and the all point cloud.

  6. +
  7. If the distance is less than a threshold, the point is considered to be an inlier.

  8. +
  9. Repeat the above steps until the inlier ratio is greater than a threshold.

  10. +
+

This is an example of RANSAC based normal vector estimation:

+
+../../../_images/ransac_normal_vector_estimation.png +
+
+

API

+
+
+Mapping.normal_vector_estimation.normal_vector_estimation.ransac_normal_vector_estimation(points_3d, inlier_radio_th=0.7, inlier_dist=0.1, p=0.99)[source]
+

RANSAC based normal vector estimation

+
+
Parameters
+
    +
  • points_3d (np.array) – 3D points (N, 3)

  • +
  • inlier_radio_th (float) – Inlier ratio threshold. If inlier ratio is larger than this value, +the iteration is stopped. Default is 0.7.

  • +
  • inlier_dist (float) – Inlier distance threshold. If distance between points and estimated +plane is smaller than this value, the point is inlier. Default is 0.1.

  • +
  • p (float) – Probability that at least one of the sets of random samples does not +include an outlier. If this probability is near 1, the iteration +number is large. Default is 0.99.

  • +
+
+
Returns
+

    +
  • center_vector (np.array) – Center of estimated plane. (3,)

  • +
  • normal_vector (np.array) – Normal vector of estimated plane. (3,)

  • +
+

+
+
+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/point_cloud_sampling/point_cloud_sampling.html b/modules/mapping/point_cloud_sampling/point_cloud_sampling.html new file mode 100644 index 0000000000..bd8f49351a --- /dev/null +++ b/modules/mapping/point_cloud_sampling/point_cloud_sampling.html @@ -0,0 +1,289 @@ + + + + + + + Point cloud Sampling — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Point cloud Sampling

+

This sections explains point cloud sampling algorithms in PythonRobotics.

+

Point clouds are two-dimensional and three-dimensional based data +acquired by external sensors like LIDAR, cameras, etc. +In general, Point Cloud data is very large in number of data. +So, if you process all the data, computation time might become an issue.

+

Point cloud sampling is a technique for solving this computational complexity +issue by extracting only representative point data and thinning the point +cloud data without compromising the performance of processing using the point +cloud data.

+
+

Voxel Point Sampling

+
+../../../_images/voxel_point_sampling.png +
+

Voxel grid sampling is a method of reducing point cloud data by using the +Voxel grids which is regular grids +in three-dimensional space.

+

This method determines which each point is in a grid, and replaces the point +clouds that are in the same Voxel with their average to reduce the number of +points.

+
+

API

+
+
+Mapping.point_cloud_sampling.point_cloud_sampling.voxel_point_sampling(original_points: numpy.ndarray[Any, numpy.dtype[numpy._typing._array_like._ScalarType_co]], voxel_size: float)[source]
+

Voxel Point Sampling function. +This function sample N-dimensional points with voxel grid. +Points in a same voxel grid will be merged by mean operation for sampling.

+
+
Parameters
+
    +
  • original_points ((M, N) N-dimensional points for sampling.) – The number of points is M.

  • +
  • voxel_size (voxel grid size) –

  • +
+
+
Returns
+

+
+
Return type
+

sampled points (M’, N)

+
+
+
+ +
+
+
+

Farthest Point Sampling

+
+../../../_images/farthest_point_sampling.png +
+

Farthest Point Sampling is a point cloud sampling method by a specified +number of points so that the distance between points is as far from as +possible.

+

This method is useful for machine learning and other situations where +you want to obtain a specified number of points from point cloud.

+
+

API

+
+
+Mapping.point_cloud_sampling.point_cloud_sampling.farthest_point_sampling(orig_points: numpy.ndarray[Any, numpy.dtype[numpy._typing._array_like._ScalarType_co]], n_points: int, seed: int)[source]
+

Farthest point sampling function +This function sample N-dimensional points with the farthest point policy.

+
+
Parameters
+
    +
  • orig_points ((M, N) N-dimensional points for sampling.) – The number of points is M.

  • +
  • n_points (number of points for sampling) –

  • +
  • seed (random seed number) –

  • +
+
+
Returns
+

+
+
Return type
+

sampled points (n_points, N)

+
+
+
+ +
+
+
+

Poisson Disk Sampling

+
+../../../_images/poisson_disk_sampling.png +
+

Poisson disk sample is a point cloud sampling method by a specified +number of points so that the algorithm selects points where the distance +from selected points is greater than a certain distance.

+

Although this method does not have good performance comparing the Farthest +distance sample where each point is distributed farther from each other, +this is suitable for real-time processing because of its fast computation time.

+
+

API

+
+
+Mapping.point_cloud_sampling.point_cloud_sampling.poisson_disk_sampling(orig_points: numpy.ndarray[Any, numpy.dtype[numpy._typing._array_like._ScalarType_co]], n_points: int, min_distance: float, seed: int, MAX_ITER=1000)[source]
+

Poisson disk sampling function +This function sample N-dimensional points randomly until the number of +points keeping minimum distance between selected points.

+
+
Parameters
+
    +
  • orig_points ((M, N) N-dimensional points for sampling.) – The number of points is M.

  • +
  • n_points (number of points for sampling) –

  • +
  • min_distance (minimum distance between selected points.) –

  • +
  • seed (random seed number) –

  • +
  • MAX_ITER (Maximum number of iteration. Default is 1000.) –

  • +
+
+
Returns
+

+
+
Return type
+

sampled points (n_points or less, N)

+
+
+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/ray_casting_grid_map/ray_casting_grid_map.html b/modules/mapping/ray_casting_grid_map/ray_casting_grid_map.html new file mode 100644 index 0000000000..1a8f5b2660 --- /dev/null +++ b/modules/mapping/ray_casting_grid_map/ray_casting_grid_map.html @@ -0,0 +1,155 @@ + + + + + + + Ray casting grid map — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Ray casting grid map

+

This is a 2D ray casting grid mapping example.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/raycasting_grid_map/animation.gif +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/mapping/rectangle_fitting/rectangle_fitting.html b/modules/mapping/rectangle_fitting/rectangle_fitting.html new file mode 100644 index 0000000000..bf918da088 --- /dev/null +++ b/modules/mapping/rectangle_fitting/rectangle_fitting.html @@ -0,0 +1,283 @@ + + + + + + + Object shape recognition using rectangle fitting — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Object shape recognition using rectangle fitting

+

This is an object shape recognition using rectangle fitting.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/rectangle_fitting/animation.gif +

This example code is based on this paper algorithm:

+ +

The algorithm consists of 2 steps as below.

+
+

Step1: Adaptive range segmentation

+

In the first step, all range data points are segmented into some clusters.

+

We calculate the distance between each range data and the nearest range data, and if this distance is below a certain threshold, it is judged to be in the same cluster. +This distance threshold is determined in proportion to the distance from the sensor. +This is taking advantage of the general model of distance sensors, which tends to have sparser data distribution as the distance from the sensor increases.

+

The threshold range is calculated by:

+
+\[r_{th} = R_0 + R_d * r_{origin}\]
+

where

+
    +
  • \(r_{th}\): Threashold range

  • +
  • \(R_0, R_d\): Constant parameters

  • +
  • \(r_{origin}\): Distance from the sensor for a range data.

  • +
+
+ +
+

API

+
+
+class Mapping.rectangle_fitting.rectangle_fitting.LShapeFitting[source]
+

LShapeFitting class. You can use this class by initializing the class and +changing the parameters, and then calling the fitting method.

+
+
+class Criteria(value)[source]
+

An enumeration.

+
+ +
+
+R0
+

Range segmentation parameter [m]

+
+ +
+
+Rd
+

Range segmentation parameter [m]

+
+ +
+
+criteria
+

Fitting criteria parameter

+
+ +
+ +

Angle difference parameter [deg]

+
+ +
+
+fitting(ox, oy)[source]
+

Fitting L-shape model to object points

+
+
Parameters
+
    +
  • ox (x positions of range points from an object) –

  • +
  • oy (y positions of range points from an object) –

  • +
+
+
Returns
+

    +
  • rects (Fitting rectangles)

  • +
  • id_sets (id sets of each cluster)

  • +
+

+
+
+
+ +
+
+min_dist_of_closeness_criteria
+

Minimum distance for closeness criteria parameter [m]

+
+ +
+ +
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/bezier_path/bezier_path.html b/modules/path_planning/bezier_path/bezier_path.html new file mode 100644 index 0000000000..d8bd37c787 --- /dev/null +++ b/modules/path_planning/bezier_path/bezier_path.html @@ -0,0 +1,177 @@ + + + + + + + Bezier path planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bezier path planning

+

A sample code of Bezier path planning.

+

It is based on 4 control points Beizer path.

+../../../_images/Figure_1.png +

If you change the offset distance from start and end point,

+

You can get different Beizer course:

+../../../_images/Figure_2.png +

Ref:

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/bspline_path/bspline_path.html b/modules/path_planning/bspline_path/bspline_path.html new file mode 100644 index 0000000000..dc64667837 --- /dev/null +++ b/modules/path_planning/bspline_path/bspline_path.html @@ -0,0 +1,343 @@ + + + + + + + B-Spline planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

B-Spline planning

+../../../_images/bspline_path_planning.png +

This is a B-Spline path planning routines.

+

If you input waypoints, it generates a smooth path with B-Spline curve.

+

This codes provide two types of B-Spline curve generations:

+
    +
  1. Interpolation: generate a curve that passes through all waypoints.

  2. +
  3. Approximation: generate a curve that approximates the waypoints. (Not passing through all waypoints)

  4. +
+
+

Bspline basics

+

BSpline (Basis-Spline) is a piecewise polynomial spline curve.

+

It is expressed by the following equation.

+

\(\mathbf{S}(x)=\sum_{i=k-p}^k \mathbf{c}_i B_{i, p}(x)\)

+

here:

+
    +
  • \(S(x)\) is the curve point on the spline at x.

  • +
  • \(c_i\) is the representative point generating the spline, called the control point.

  • +
  • \(p+1\) is the dimension of the BSpline.

  • +
  • \(k\) is the number of knots.

  • +
  • \(B_{i,p}(x)\) is a function called Basis Function.

  • +
+

The the basis function can be calculated by the following De Boor recursion formula:

+

\(B_{i, 0}(x):= \begin{cases}1 & \text { if } \quad t_i \leq x<t_{i+1} \\ 0 & \text { otherwise }\end{cases}\)

+

\(B_{i, p}(x):=\frac{x-t_i}{t_{i+p}-t_i} B_{i, p-1}(x)+\frac{t_{i+p+1}-x}{t_{i+p+1}-t_{i+1}} B_{i+1, p-1}(x)\)

+

here:

+
    +
  • \(t_i\) is each element of the knot vector.

  • +
+

This figure shows the BSpline basis functions for each of \(i\):

+../../../_images/basis_functions.png +

Note that when all the basis functions are added together, summation is 1.0 for any x value.

+

This means that the result curve is smooth when each control point is weighted addition by this basis function,

+

This code is for generating the upper basis function graph using scipy.

+
from scipy.interpolate import BSpline
+
+def B_orig(x, k, i, t):
+        if k == 0:
+                return 1.0 if t[i] <= x < t[i + 1] else 0.0
+        if t[i + k] == t[i]:
+                c1 = 0.0
+        else:
+                c1 = (x - t[i]) / (t[i + k] - t[i]) * B(x, k - 1, i, t)
+
+        if t[i + k + 1] == t[i + 1]:
+                c2 = 0.0
+        else:
+                c2 = (t[i + k + 1] - x) / (t[i + k + 1] - t[i + 1]) * B(x, k - 1, i + 1, t)
+        return c1 + c2
+
+
+def B(x, k, i, t):
+        c = np.zeros_like(t)
+        c[i] = 1
+        return BSpline(t, c, k)(x)
+
+
+def main():
+        k = 3  # degree of the spline
+        t = [0, 1, 2, 3, 4, 5]  # knots vector
+
+        x = np.linspace(0, 5, 1000, endpoint=False)
+        t = np.r_[[np.min(t)]*k, t, [np.max(t)]*k]
+
+        n = len(t) - k - 1
+        for i in range(n):
+                y = np.array([B(ix, k, i, t) for ix in x])
+                plt.plot(x, y, label=f'i = {i}')
+
+        plt.title(f'Basis functions (k = {k}, knots = {t})')
+        plt.show()
+
+
+
+
+

Bspline interpolation planning

+

PathPlanning.BSplinePath.bspline_path.interpolate_b_spline_path() generates a curve that passes through all waypoints.

+

This is a simple example of the interpolation planning:

+../../../_images/interpolation1.png +

This figure also shows curvatures of each path point using utils.plot.plot_curvature.

+

The default spline degree is 3, so curvature changes smoothly.

+../../../_images/interp_and_curvature.png +
+

API

+
+
+PathPlanning.BSplinePath.bspline_path.interpolate_b_spline_path(x, y, n_path_points: int, degree: int = 3) tuple[source]
+

Interpolate x-y points with a B-Spline path

+
+
Parameters
+
    +
  • x (array_like) – x positions of interpolated points

  • +
  • y (array_like) – y positions of interpolated points

  • +
  • n_path_points (int) – number of path points

  • +
  • degree (int, optional) – B-Spline degree. Must be 2<= k <= 5. Default: 3

  • +
+
+
Returns
+

    +
  • x (array) – x positions of the result path

  • +
  • y (array) – y positions of the result path

  • +
  • heading (array) – heading of the result path

  • +
  • curvature (array) – curvature of the result path

  • +
+

+
+
+
+ +
+
+
+

Bspline approximation planning

+

PathPlanning.BSplinePath.bspline_path.approximate_b_spline_path() +generates a curve that approximates the waypoints, which means that +the curve might not pass through waypoints.

+

Users can adjust path smoothness by the smoothing parameter s. If this +value is bigger, the path will be smoother, but it will be less accurate. +If this value is smaller, the path will be more accurate, but it will be +less smooth.

+

This is a simple example of the approximation planning:

+../../../_images/approximation1.png +

This figure also shows curvatures of each path point using utils.plot.plot_curvature.

+

The default spline degree is 3, so curvature changes smoothly.

+../../../_images/approx_and_curvature.png +
+

API

+
+
+PathPlanning.BSplinePath.bspline_path.approximate_b_spline_path(x: list, y: list, n_path_points: int, degree: int = 3, s=None) tuple[source]
+

Approximate points with a B-Spline path

+
+
Parameters
+
    +
  • x (array_like) – x position list of approximated points

  • +
  • y (array_like) – y position list of approximated points

  • +
  • n_path_points (int) – number of path points

  • +
  • degree (int, optional) – B Spline curve degree. Must be 2<= k <= 5. Default: 3.

  • +
  • s (int, optional) – smoothing parameter. If this value is bigger, the path will be +smoother, but it will be less accurate. If this value is smaller, +the path will be more accurate, but it will be less smooth. +When s is 0, it is equivalent to the interpolation. Default is None, +in this case s will be len(x).

  • +
+
+
Returns
+

    +
  • x (array) – x positions of the result path

  • +
  • y (array) – y positions of the result path

  • +
  • heading (array) – heading of the result path

  • +
  • curvature (array) – curvature of the result path

  • +
+

+
+
+
+ +
+
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/bugplanner/bugplanner.html b/modules/path_planning/bugplanner/bugplanner.html new file mode 100644 index 0000000000..46ef6f5bf5 --- /dev/null +++ b/modules/path_planning/bugplanner/bugplanner.html @@ -0,0 +1,170 @@ + + + + + + + Bug planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/clothoid_path/clothoid_path.html b/modules/path_planning/clothoid_path/clothoid_path.html new file mode 100644 index 0000000000..c68c084ac0 --- /dev/null +++ b/modules/path_planning/clothoid_path/clothoid_path.html @@ -0,0 +1,229 @@ + + + + + + + Clothoid path planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Clothoid path planning

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ClothoidPath/animation1.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ClothoidPath/animation2.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ClothoidPath/animation3.gif +

This is a clothoid path planning sample code.

+

This can interpolate two 2D pose (x, y, yaw) with a clothoid path, +which its curvature is linearly continuous. +In other words, this is G1 Hermite interpolation with a single clothoid segment.

+

This path planning algorithm as follows:

+
+

Step1: Solve g function

+

Solve the g(A) function with a nonlinear optimization solver.

+
+\[g(A):=Y(2A, \delta-A, \phi_{s})\]
+

Where

+
    +
  • \(\delta\): the orientation difference between start and goal pose.

  • +
  • \(\phi_{s}\): the orientation of the start pose.

  • +
  • \(Y\): \(Y(a, b, c)=\int_{0}^{1} \sin \left(\frac{a}{2} \tau^{2}+b \tau+c\right) d \tau\)

  • +
+
+
+

Step2: Calculate path parameters

+

We can calculate these path parameters using \(A\),

+

\(L\): path length

+
+\[L=\frac{R}{X\left(2 A, \delta-A, \phi_{s}\right)}\]
+

where

+
    +
  • \(R\): the distance between start and goal pose

  • +
  • \(X\): \(X(a, b, c)=\int_{0}^{1} \cos \left(\frac{a}{2} \tau^{2}+b \tau+c\right) d \tau\)

  • +
+
    +
  • \(\kappa\): curvature

  • +
+
+\[\kappa=(\delta-A) / L\]
+
    +
  • \(\kappa'\): curvature rate

  • +
+
+\[\kappa^{\prime}=2 A / L^{2}\]
+
+
+

Step3: Construct a path with Fresnel integral

+

The final clothoid path can be calculated with the path parameters and Fresnel integrals.

+
+\[\begin{split}\begin{aligned} +&x(s)=x_{0}+\int_{0}^{s} \cos \left(\frac{1}{2} \kappa^{\prime} \tau^{2}+\kappa \tau+\vartheta_{0}\right) \mathrm{d} \tau \\ +&y(s)=y_{0}+\int_{0}^{s} \sin \left(\frac{1}{2} \kappa^{\prime} \tau^{2}+\kappa \tau+\vartheta_{0}\right) \mathrm{d} \tau +\end{aligned}\end{split}\]
+
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/coverage_path/coverage_path.html b/modules/path_planning/coverage_path/coverage_path.html new file mode 100644 index 0000000000..e5e7f1640b --- /dev/null +++ b/modules/path_planning/coverage_path/coverage_path.html @@ -0,0 +1,195 @@ + + + + + + + Coverage path planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Coverage path planner

+
+

Grid based sweep

+

This is a 2D grid based sweep coverage path planner simulation:

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/GridBasedSweepCPP/animation.gif +
+
+

Spiral Spanning Tree

+

This is a 2D grid based spiral spanning tree coverage path planner simulation:

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/SpiralSpanningTreeCPP/animation1.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/SpiralSpanningTreeCPP/animation2.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/SpiralSpanningTreeCPP/animation3.gif + +
+
+

Wavefront path

+

This is a 2D grid based wavefront coverage path planner simulation:

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/WavefrontCPP/animation1.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/WavefrontCPP/animation2.gif +https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/WavefrontCPP/animation3.gif + +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/cubic_spline/cubic_spline.html b/modules/path_planning/cubic_spline/cubic_spline.html new file mode 100644 index 0000000000..7a7035d724 --- /dev/null +++ b/modules/path_planning/cubic_spline/cubic_spline.html @@ -0,0 +1,524 @@ + + + + + + + Cubic spline planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Cubic spline planning

+
+

Spline curve continuity

+

Spline curve smoothness is depending on the which kind of spline model is used.

+

The smoothness of the spline curve is expressed as \(C_0, C_1\), and so on.

+

This representation represents continuity of the curve. +For example, for a spline curve in two-dimensional space:

+
    +
  • \(C_0\) is position continuous

  • +
  • \(C_1\) is tangent vector continuous

  • +
  • \(C_2\) is curvature vector continuous

  • +
+

as shown in the following figure:

+../../../_images/spline_continuity.png +

Cubic spline can generate a curve with \(C_0, C_1, C_2\).

+
+
+

1D cubic spline

+

Cubic spline interpolation is a method of smoothly +interpolating between multiple data points when given +multiple data points, as shown in the figure below.

+../../../_images/spline.png +

It separates between each interval between data points.

+

The each interval part is approximated by each cubic polynomial.

+

The cubic spline uses the cubic polynomial equation for interpolation:

+

\(S_j(x)=a_j+b_j(x-x_j)+c_j(x-x_j)^2+d_j(x-x_j)^3\)

+

where \(x_j < x < x_{j+1}\), \(x_j\) is the j-th node of the spline, +\(a_j\), \(b_j\), \(c_j\), \(d_j\) are the coefficients +of the spline.

+

As the above equation, there are 4 unknown parameters \((a,b,c,d)\) for +one interval, so if the number of data points is \(N\), the +interpolation has \(4N\) unknown parameters.

+

The following five conditions are used to determine the \(4N\) +unknown parameters:

+
+

Constraint 1: Terminal constraints

+

\(S_j(x_j)=y_j\)

+

This constraint is the terminal constraint of each interval.

+

The polynomial of each interval will pass through the x,y coordinates of +the data points.

+
+
+

Constraint 2: Point continuous constraints

+

\(S_j(x_{j+1})=S_{j+1}(x_{j+1})=y_{j+1}\)

+

This constraint is a continuity condition for the boundary of each interval.

+

This constraint ensures that the boundary of each interval is continuous.

+
+
+

Constraint 3: Tangent vector continuous constraints

+

\(S'_j(x_{j+1})=S'_{j+1}(x_{j+1})\)

+

This constraint is a continuity condition for the first derivative of +the boundary of each interval.

+

This constraint makes the vectors of the boundaries of each +interval continuous.

+
+
+

Constraint 4: Curvature continuous constraints

+

\(S''_j(x_{j+1})=S''_{j+1}(x_{j+1})\)

+

This constraint is the continuity condition for the second derivative of +the boundary of each interval.

+

This constraint makes the curvature of the boundaries of each +interval continuous.

+
+
+

Constraint 5: Terminal curvature constraints

+

\(S''_0(0)=S''_{n+1}(x_{n})=0\).

+

The constraint is a boundary condition for the second derivative of the starting and ending points.

+

Our sample code assumes these terminal curvatures are 0, which is well known as Natural Cubic Spline.

+
+
+

How to calculate the unknown parameters \(a_j, b_j, c_j, d_j\)

+
+

Step1: calculate \(a_j\)

+

Spline coefficients \(a_j\) can be calculated by y positions of the data points:

+

\(a_j = y_i\).

+
+
+

Step2: calculate \(c_j\)

+

Spline coefficients \(c_j\) can be calculated by solving the linear equation:

+

\(Ac_j = B\).

+

The matrix \(A\) and \(B\) are defined as follows:

+
+\[\begin{split}A=\left[\begin{array}{cccccc} +1 & 0 & 0 & 0 & \cdots & 0 \\ +h_{0} & 2\left(h_{0}+h_{1}\right) & h_{1} & 0 & \cdots & 0 \\ +0 & h_{1} & 2\left(h_{1}+h_{2}\right) & h_{2} & \cdots & 0 \\ +0 & 0 & h_{2} & 2\left(h_{2}+h_{3}\right) & \cdots & 0 \\ +0 & 0 & 0 & h_{3} & \ddots & \\ +\vdots & \vdots & & & & \\ +0 & 0 & 0 & \cdots & 0 & 1 +\end{array}\right]\end{split}\]
+
+\[\begin{split}B=\left[\begin{array}{c} +0 \\ +\frac{3}{h_{1}}\left(a_{2}-a_{1}\right)-\frac{3}{h_{0}}\left(a_{1}-a_{0}\right) \\ +\vdots \\ +\frac{3}{h_{n-1}}\left(a_{n}-a_{n-1}\right)-\frac{3}{h_{n-2}}\left(a_{n-1}-a_{n-2}\right) \\ +0 +\end{array}\right]\end{split}\]
+

where \(h_{i}\) is the x position distance between the i-th and (i+1)-th data points.

+
+
+

Step3: calculate \(d_j\)

+

Spline coefficients \(d_j\) can be calculated by the following equation:

+

\(d_{j}=\frac{c_{j+1}-c_{j}}{3 h_{j}}\)

+
+
+

Step4: calculate \(b_j\)

+

Spline coefficients \(b_j\) can be calculated by the following equation:

+

\(b_{i}=\frac{1}{h_i}(a_{i+1}-a_{i})-\frac{h_i}{3}(2c_{i}+c_{i+1})\)

+
+
+
+

How to calculate the first and second derivatives of the spline curve

+

After we can get the coefficients of the spline curve, we can calculate

+

the first derivative by:

+

\(y^{\prime}(x)=b+2cx+3dx^2\)

+

the second derivative by:

+

\(y^{\prime \prime}(x)=2c+6dx\)

+

These equations can be calculated by differentiating the cubic polynomial.

+
+
+

API

+

This is the 1D cubic spline class API:

+
+
+class PathPlanning.CubicSpline.cubic_spline_planner.CubicSpline1D(x, y)[source]
+

1D Cubic Spline class

+
+
Parameters
+
    +
  • x (list) – x coordinates for data points. This x coordinates must be +sorted +in ascending order.

  • +
  • y (list) – y coordinates for data points

  • +
+
+
+

Examples

+

You can interpolate 1D data points.

+
>>> import numpy as np
+>>> import matplotlib.pyplot as plt
+>>> x = np.arange(5)
+>>> y = [1.7, -6, 5, 6.5, 0.0]
+>>> sp = CubicSpline1D(x, y)
+>>> xi = np.linspace(0.0, 5.0)
+>>> yi = [sp.calc_position(x) for x in xi]
+>>> plt.plot(x, y, "xb", label="Data points")
+>>> plt.plot(xi, yi , "r", label="Cubic spline interpolation")
+>>> plt.grid(True)
+>>> plt.legend()
+>>> plt.show()
+
+
+../../../_images/cubic_spline_1d.png +
+
+calc_first_derivative(x)[source]
+

Calc first derivative at given x.

+

if x is outside the input x, return None

+
+
Returns
+

dy – first derivative for given x.

+
+
Return type
+

float

+
+
+
+ +
+
+calc_position(x)[source]
+

Calc y position for given x.

+

if x is outside the data point’s x range, return None.

+
+
Returns
+

y – y position for given x.

+
+
Return type
+

float

+
+
+
+ +
+
+calc_second_derivative(x)[source]
+

Calc second derivative at given x.

+

if x is outside the input x, return None

+
+
Returns
+

ddy – second derivative for given x.

+
+
Return type
+

float

+
+
+
+ +
+ +
+
+
+

2D cubic spline

+

Data x positions needs to be mono-increasing for 1D cubic spline.

+

So, it cannot be used for 2D path planning.

+

2D cubic spline uses two 1D cubic splines based on path distance from +the start point for each dimension x and y.

+

This can generate a curvature continuous path based on x-y waypoints.

+

Heading angle of each point can be calculated analytically by:

+

\(\theta=\tan ^{-1} \frac{y’}{x’}\)

+

Curvature of each point can be also calculated analytically by:

+

\(\kappa=\frac{y^{\prime \prime} x^{\prime}-x^{\prime \prime} y^{\prime}}{\left(x^{\prime2}+y^{\prime2}\right)^{\frac{2}{3}}}\)

+
+

API

+
+
+class PathPlanning.CubicSpline.cubic_spline_planner.CubicSpline2D(x, y)[source]
+

Cubic CubicSpline2D class

+
+
Parameters
+
    +
  • x (list) – x coordinates for data points.

  • +
  • y (list) – y coordinates for data points.

  • +
+
+
+

Examples

+

You can interpolate a 2D data points.

+
>>> import matplotlib.pyplot as plt
+>>> x = [-2.5, 0.0, 2.5, 5.0, 7.5, 3.0, -1.0]
+>>> y = [0.7, -6, 5, 6.5, 0.0, 5.0, -2.0]
+>>> ds = 0.1  # [m] distance of each interpolated points
+>>> sp = CubicSpline2D(x, y)
+>>> s = np.arange(0, sp.s[-1], ds)
+>>> rx, ry, ryaw, rk = [], [], [], []
+>>> for i_s in s:
+...     ix, iy = sp.calc_position(i_s)
+...     rx.append(ix)
+...     ry.append(iy)
+...     ryaw.append(sp.calc_yaw(i_s))
+...     rk.append(sp.calc_curvature(i_s))
+>>> plt.subplots(1)
+>>> plt.plot(x, y, "xb", label="Data points")
+>>> plt.plot(rx, ry, "-r", label="Cubic spline path")
+>>> plt.grid(True)
+>>> plt.axis("equal")
+>>> plt.xlabel("x[m]")
+>>> plt.ylabel("y[m]")
+>>> plt.legend()
+>>> plt.show()
+
+
+../../../_images/cubic_spline_2d_path.png +
>>> plt.subplots(1)
+>>> plt.plot(s, [np.rad2deg(iyaw) for iyaw in ryaw], "-r", label="yaw")
+>>> plt.grid(True)
+>>> plt.legend()
+>>> plt.xlabel("line length[m]")
+>>> plt.ylabel("yaw angle[deg]")
+
+
+../../../_images/cubic_spline_2d_yaw.png +
>>> plt.subplots(1)
+>>> plt.plot(s, rk, "-r", label="curvature")
+>>> plt.grid(True)
+>>> plt.legend()
+>>> plt.xlabel("line length[m]")
+>>> plt.ylabel("curvature [1/m]")
+
+
+../../../_images/cubic_spline_2d_curvature.png +
+
+calc_curvature(s)[source]
+

calc curvature

+
+
Parameters
+

s (float) – distance from the start point. if s is outside the data point’s +range, return None.

+
+
Returns
+

k – curvature for given s.

+
+
Return type
+

float

+
+
+
+ +
+
+calc_position(s)[source]
+

calc position

+
+
Parameters
+

s (float) – distance from the start point. if s is outside the data point’s +range, return None.

+
+
Returns
+

    +
  • x (float) – x position for given s.

  • +
  • y (float) – y position for given s.

  • +
+

+
+
+
+ +
+
+calc_yaw(s)[source]
+

calc yaw

+
+
Parameters
+

s (float) – distance from the start point. if s is outside the data point’s +range, return None.

+
+
Returns
+

yaw – yaw angle (tangent vector) for given s.

+
+
Return type
+

float

+
+
+
+ +
+ +
+
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/dubins_path/dubins_path.html b/modules/path_planning/dubins_path/dubins_path.html new file mode 100644 index 0000000000..e4c16f25ce --- /dev/null +++ b/modules/path_planning/dubins_path/dubins_path.html @@ -0,0 +1,270 @@ + + + + + + + Dubins path planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Dubins path planning

+

A sample code for Dubins path planning.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/DubinsPath/animation.gif?raw=True +
+

Dubins path

+

Dubins path is a analytical path planning algorithm for a simple car model.

+

It can generates a shortest path between two 2D poses (x, y, yaw) with maximum curvature constraint and tangent(yaw angle) constraint.

+

Generated paths consist of 3 segments of maximum curvature curves or a straight line segment.

+

Each segment type can is categorized by 3 type: ‘Right turn (R)’ , ‘Left turn (L)’, and ‘Straight (S).’

+

Possible path will be at least one of these six types: RSR, RSL, LSR, LSL, RLR, LRL.

+

Dubins path planner can output each segment type and distance of each course segment.

+

For example, a RSR Dubins path is:

+../../../_images/RSR.jpg +

Each segment distance can be calculated by:

+

\(\alpha = mod(-\theta)\)

+

\(\beta = mod(x_{e, yaw} - \theta)\)

+

\(p^2 = 2 + d ^ 2 - 2\cos(\alpha-\beta) + 2d(\sin\alpha - \sin\beta)\)

+

\(t = atan2(\cos\beta - \cos\alpha, d + \sin\alpha - \sin\beta)\)

+

\(d_1 = mod(-\alpha + t)\)

+

\(d_2 = p\)

+

\(d_3 = mod(\beta - t)\)

+

where \(\theta\) is tangent and d is distance from \(x_s\) to \(x_e\)

+

A RLR Dubins path is:

+../../../_images/RLR.jpg +

Each segment distance can be calculated by:

+

\(t = (6.0 - d^2 + 2\cos(\alpha-\beta) + 2d(\sin\alpha - \sin\beta)) / 8.0\)

+

\(d_2 = mod(2\pi - acos(t))\)

+

\(d_1 = mod(\alpha - atan2(\cos\beta - \cos\alpha, d + \sin\alpha - \sin\beta) + d_2 / 2.0)\)

+

\(d_3 = mod(\alpha - \beta - d_1 + d_2)\)

+

You can generate a path from these information and the maximum curvature information.

+

A path type which has minimum course length among 6 types is selected, +and then a path is constructed based on the selected type and its distances.

+
+
+

API

+
+
+PathPlanning.DubinsPath.dubins_path_planner.plan_dubins_path(s_x, s_y, s_yaw, g_x, g_y, g_yaw, curvature, step_size=0.1, selected_types=None)[source]
+

Plan dubins path

+
+
Parameters
+
    +
  • s_x (float) – x position of the start point [m]

  • +
  • s_y (float) – y position of the start point [m]

  • +
  • s_yaw (float) – yaw angle of the start point [rad]

  • +
  • g_x (float) – x position of the goal point [m]

  • +
  • g_y (float) – y position of the end point [m]

  • +
  • g_yaw (float) – yaw angle of the end point [rad]

  • +
  • curvature (float) – curvature for curve [1/m]

  • +
  • step_size (float (optional)) – step size between two path points [m]. Default is 0.1

  • +
  • selected_types (a list of string or None) – selected path planning types. If None, all types are used for +path planning, and minimum path length result is returned. +You can select used path plannings types by a string list. +e.g.: [“RSL”, “RSR”]

  • +
+
+
Returns
+

    +
  • x_list (array) – x positions of the path

  • +
  • y_list (array) – y positions of the path

  • +
  • yaw_list (array) – yaw angles of the path

  • +
  • modes (array) – mode list of the path

  • +
  • lengths (array) – arrow_length list of the path segments.

  • +
+

+
+
+

Examples

+

You can generate a dubins path.

+
>>> start_x = 1.0  # [m]
+>>> start_y = 1.0  # [m]
+>>> start_yaw = np.deg2rad(45.0)  # [rad]
+>>> end_x = -3.0  # [m]
+>>> end_y = -3.0  # [m]
+>>> end_yaw = np.deg2rad(-45.0)  # [rad]
+>>> curvature = 1.0
+>>> path_x, path_y, path_yaw, mode, _ = plan_dubins_path(
+            start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature)
+>>> plt.plot(path_x, path_y, label="final course " + "".join(mode))
+>>> plot_arrow(start_x, start_y, start_yaw)
+>>> plot_arrow(end_x, end_y, end_yaw)
+>>> plt.legend()
+>>> plt.grid(True)
+>>> plt.axis("equal")
+>>> plt.show()
+
+
+../../../_images/dubins_path.jpg +
+ +
+
+

Reference

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/dynamic_window_approach/dynamic_window_approach.html b/modules/path_planning/dynamic_window_approach/dynamic_window_approach.html new file mode 100644 index 0000000000..72f37ad705 --- /dev/null +++ b/modules/path_planning/dynamic_window_approach/dynamic_window_approach.html @@ -0,0 +1,171 @@ + + + + + + + Dynamic Window Approach — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/eta3_spline/eta3_spline.html b/modules/path_planning/eta3_spline/eta3_spline.html new file mode 100644 index 0000000000..98af3a8750 --- /dev/null +++ b/modules/path_planning/eta3_spline/eta3_spline.html @@ -0,0 +1,172 @@ + + + + + + + Eta^3 Spline path planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/frenet_frame_path/frenet_frame_path.html b/modules/path_planning/frenet_frame_path/frenet_frame_path.html new file mode 100644 index 0000000000..38f0137762 --- /dev/null +++ b/modules/path_planning/frenet_frame_path/frenet_frame_path.html @@ -0,0 +1,177 @@ + + + + + + + Optimal Trajectory in a Frenet Frame — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Optimal Trajectory in a Frenet Frame

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/FrenetOptimalTrajectory/animation.gif +

This is optimal trajectory generation in a Frenet Frame.

+

The cyan line is the target course and black crosses are obstacles.

+

The red line is predicted path.

+

Ref:

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/grid_base_search/grid_base_search.html b/modules/path_planning/grid_base_search/grid_base_search.html new file mode 100644 index 0000000000..bb97eec86f --- /dev/null +++ b/modules/path_planning/grid_base_search/grid_base_search.html @@ -0,0 +1,236 @@ + + + + + + + Grid based search — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/hybridastar/hybridastar.html b/modules/path_planning/hybridastar/hybridastar.html new file mode 100644 index 0000000000..b743874eda --- /dev/null +++ b/modules/path_planning/hybridastar/hybridastar.html @@ -0,0 +1,167 @@ + + + + + + + Hybrid a star — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/lqr_path/lqr_path.html b/modules/path_planning/lqr_path/lqr_path.html new file mode 100644 index 0000000000..bfa72d349b --- /dev/null +++ b/modules/path_planning/lqr_path/lqr_path.html @@ -0,0 +1,167 @@ + + + + + + + LQR based path planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator.html b/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator.html new file mode 100644 index 0000000000..c812e10bd4 --- /dev/null +++ b/modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator.html @@ -0,0 +1,185 @@ + + + + + + + Model Predictive Trajectory Generator — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Model Predictive Trajectory Generator

+

This is a path optimization sample on model predictive trajectory +generator.

+

This algorithm is used for state lattice planner.

+
+

Path optimization sample

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ModelPredictiveTrajectoryGenerator/kn05animation.gif +
+
+

Lookup table generation sample

+../../../_images/lookup_table.png +

Ref:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/path_planning.html b/modules/path_planning/path_planning.html new file mode 100644 index 0000000000..2afac5fb13 --- /dev/null +++ b/modules/path_planning/path_planning.html @@ -0,0 +1,256 @@ + + + + + + + Path Planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Path Planning

+
+

Contents

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/prm_planner/prm_planner.html b/modules/path_planning/prm_planner/prm_planner.html new file mode 100644 index 0000000000..b149c14d3b --- /dev/null +++ b/modules/path_planning/prm_planner/prm_planner.html @@ -0,0 +1,175 @@ + + + + + + + Probabilistic Road-Map (PRM) planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Probabilistic Road-Map (PRM) planning

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ProbabilisticRoadMap/animation.gif +

This PRM planner uses Dijkstra method for graph search.

+

In the animation, blue points are sampled points,

+

Cyan crosses means searched points with Dijkstra method,

+

The red line is the final path of PRM.

+

Ref:

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner.html b/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner.html new file mode 100644 index 0000000000..263ee9b7ac --- /dev/null +++ b/modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner.html @@ -0,0 +1,235 @@ + + + + + + + Quintic polynomials planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Quintic polynomials planning

+

Motion planning with quintic polynomials.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/QuinticPolynomialsPlanner/animation.gif +

It can calculate 2D path, velocity, and acceleration profile based on +quintic polynomials.

+
+

Quintic polynomials for one dimensional robot motion

+

We assume a one-dimensional robot motion \(x(t)\) at time \(t\) is +formulated as a quintic polynomials based on time as follows:

+
+(1)\[x(t) = a_0+a_1t+a_2t^2+a_3t^3+a_4t^4+a_5t^5\]
+

\(a_0, a_1. a_2, a_3, a_4, a_5\) are parameters of the quintic polynomial.

+

It is assumed that terminal states (start and end) are known as boundary conditions.

+

Start position, velocity, and acceleration are \(x_s, v_s, a_s\) respectively.

+

End position, velocity, and acceleration are \(x_e, v_e, a_e\) respectively.

+

So, when time is 0.

+
+(2)\[x(0) = a_0 = x_s\]
+

Then, differentiating the equation (1) with t,

+
+(3)\[x'(t) = a_1+2a_2t+3a_3t^2+4a_4t^3+5a_5t^4\]
+

So, when time is 0,

+
+(4)\[x'(0) = a_1 = v_s\]
+

Then, differentiating the equation (3) with t again,

+
+(5)\[x''(t) = 2a_2+6a_3t+12a_4t^2\]
+

So, when time is 0,

+
+(6)\[x''(0) = 2a_2 = a_s\]
+

so, we can calculate \(a_0, a_1, a_2\) with eq. (2), (4), (6) and boundary conditions.

+

\(a_3, a_4, a_5\) are still unknown in eq (1).

+

We assume that the end time for a maneuver is \(T\), we can get these equations from eq (1), (3), (5):

+
+(7)\[x(T)=a_0+a_1T+a_2T^2+a_3T^3+a_4T^4+a_5T^5=x_e\]
+
+(8)\[x'(T)=a_1+2a_2T+3a_3T^2+4a_4T^3+5a_5T^4=v_e\]
+
+(9)\[x''(T)=2a_2+6a_3T+12a_4T^2+20a_5T^3=a_e\]
+

From eq (7), (8), (9), we can calculate \(a_3, a_4, a_5\) to solve the linear equations: \(Ax=b\)

+
+\[\begin{split}\begin{bmatrix} T^3 & T^4 & T^5 \\ 3T^2 & 4T^3 & 5T^4 \\ 6T & 12T^2 & 20T^3 \end{bmatrix}\begin{bmatrix} a_3\\ a_4\\ a_5\end{bmatrix}=\begin{bmatrix} x_e-x_s-v_sT-0.5a_sT^2\\ v_e-v_s-a_sT\\ a_e-a_s\end{bmatrix}\end{split}\]
+

We can get all unknown parameters now.

+
+
+

Quintic polynomials for two dimensional robot motion (x-y)

+

If you use two quintic polynomials along x axis and y axis, you can plan for two dimensional robot motion in x-y plane.

+
+(10)\[x(t) = a_0+a_1t+a_2t^2+a_3t^3+a_4t^4+a_5t^5\]
+
+(11)\[y(t) = b_0+b_1t+b_2t^2+b_3t^3+b_4t^4+b_5t^5\]
+

It is assumed that terminal states (start and end) are known as boundary conditions.

+

Start position, orientation, velocity, and acceleration are \(x_s, y_s, \theta_s, v_s, a_s\) respectively.

+

End position, orientation, velocity, and acceleration are \(x_e, y_e. \theta_e, v_e, a_e\) respectively.

+

Each velocity and acceleration boundary condition can be calculated with each orientation.

+

\(v_{xs}=v_scos(\theta_s), v_{ys}=v_ssin(\theta_s)\)

+

\(v_{xe}=v_ecos(\theta_e), v_{ye}=v_esin(\theta_e)\)

+
+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/reeds_shepp_path/reeds_shepp_path.html b/modules/path_planning/reeds_shepp_path/reeds_shepp_path.html new file mode 100644 index 0000000000..f80ca19660 --- /dev/null +++ b/modules/path_planning/reeds_shepp_path/reeds_shepp_path.html @@ -0,0 +1,176 @@ + + + + + + + Reeds Shepp planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_planning/rrt/rrt.html b/modules/path_planning/rrt/rrt.html new file mode 100644 index 0000000000..3340af6431 --- /dev/null +++ b/modules/path_planning/rrt/rrt.html @@ -0,0 +1,274 @@ + + + + + + + Rapidly-Exploring Random Trees (RRT) — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Rapidly-Exploring Random Trees (RRT)

+
+

Basic RRT

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRT/animation.gif +

This is a simple path planning code with Rapidly-Exploring Random Trees +(RRT)

+

Black circles are obstacles, green line is a searched tree, red crosses +are start and goal positions.

+
+
+

RRT*

+
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTstar/animation.gif +
+

This is a path planning code with RRT*

+

Black circles are obstacles, green line is a searched tree, red crosses are start and goal positions.

+
+

Simulation

+../../../_images/rrt_star_1_0.png +
+
+

Ref

+ +
+
+
+

RRT with dubins path

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTDubins/animation.gif +

Path planning for a car robot with RRT and dubins path planner.

+
+
+

RRT* with dubins path

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTStarDubins/animation.gif +

Path planning for a car robot with RRT* and dubins path planner.

+
+
+

RRT* with reeds-sheep path

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/RRTStarReedsShepp/animation.gif +

Path planning for a car robot with RRT* and reeds sheep path planner.

+
+
+

Informed RRT*

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/InformedRRTStar/animation.gif +

This is a path planning code with Informed RRT*.

+

The cyan ellipse is the heuristic sampling domain of Informed RRT*.

+

Ref:

+ +
+
+

Batch Informed RRT*

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/BatchInformedRRTStar/animation.gif +

This is a path planning code with Batch Informed RRT*.

+

Ref:

+ +
+
+

Closed Loop RRT*

+

A vehicle model based path planning with closed loop RRT*.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/ClosedLoopRRTStar/animation.gif +

In this code, pure-pursuit algorithm is used for steering control,

+

PID is used for speed control.

+

Ref:

+ +
+
+

LQR-RRT*

+

This is a path planning simulation with LQR-RRT*.

+

A double integrator motion model is used for LQR local planner.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/LQRRRTStar/animation.gif +

Ref:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/state_lattice_planner/state_lattice_planner.html b/modules/path_planning/state_lattice_planner/state_lattice_planner.html new file mode 100644 index 0000000000..ed9ab6700f --- /dev/null +++ b/modules/path_planning/state_lattice_planner/state_lattice_planner.html @@ -0,0 +1,193 @@ + + + + + + + State Lattice Planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

State Lattice Planning

+

This script is a path planning code with state lattice planning.

+

This code uses the model predictive trajectory generator to solve +boundary problem.

+
+

Uniform polar sampling

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/UniformPolarSampling.gif +
+
+

Biased polar sampling

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/BiasedPolarSampling.gif +
+
+

Lane sampling

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/StateLatticePlanner/LaneSampling.gif +

Ref:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner.html b/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner.html new file mode 100644 index 0000000000..7389c299b1 --- /dev/null +++ b/modules/path_planning/visibility_road_map_planner/visibility_road_map_planner.html @@ -0,0 +1,226 @@ + + + + + + + Visibility Road-Map planner — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Visibility Road-Map planner

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/VisibilityRoadMap/animation.gif +

[Code]

+

This visibility road-map planner uses Dijkstra method for graph search.

+

In the animation, the black lines are polygon obstacles,

+

red crosses are visibility nodes, and blue lines area collision free visibility graphs.

+

The red line is the final path searched by dijkstra algorithm frm the visibility graphs.

+
+

Algorithms

+

In this chapter, how does the visibility road map planner search a path.

+

We assume this planner can be provided these information in the below figure.

+
    +
    1. +
    2. Start point (Red point)

    3. +
    +
  • +
    1. +
    2. Goal point (Blue point)

    3. +
    +
  • +
    1. +
    2. Obstacle polygons (Black lines)

    3. +
    +
  • +
+../../../_images/step0.png +
+

Step1: Generate visibility nodes based on polygon obstacles

+

The nodes are generated by expanded these polygons vertexes like the below figure:

+../../../_images/step1.png +

Each polygon vertex is expanded outward from the vector of adjacent vertices.

+

The start and goal point are included as nodes as well.

+
+
+

Step2: Generate visibility graphs connecting the nodes.

+

When connecting the nodes, the arc between two nodes is checked to collided or not to each obstacles.

+

If the arc is collided, the graph is removed.

+

The blue lines are generated visibility graphs in the figure:

+../../../_images/step2.png +
+
+

Step3: Search the shortest path in the graphs using Dijkstra algorithm

+

The red line is searched path in the figure:

+../../../_images/step3.png +

You can find the details of Dijkstra algorithm in Dijkstra algorithm.

+
+
+

References

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_planning/vrm_planner/vrm_planner.html b/modules/path_planning/vrm_planner/vrm_planner.html new file mode 100644 index 0000000000..9744d729b2 --- /dev/null +++ b/modules/path_planning/vrm_planner/vrm_planner.html @@ -0,0 +1,174 @@ + + + + + + + Voronoi Road-Map planning — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Voronoi Road-Map planning

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathPlanning/VoronoiRoadMap/animation.gif +

This Voronoi road-map planner uses Dijkstra method for graph search.

+

In the animation, blue points are Voronoi points,

+

Cyan crosses mean searched points with Dijkstra method,

+

The red line is the final path of Vornoi Road-Map.

+

Ref:

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/cgmres_nmpc/cgmres_nmpc.html b/modules/path_tracking/cgmres_nmpc/cgmres_nmpc.html new file mode 100644 index 0000000000..36b079f96e --- /dev/null +++ b/modules/path_tracking/cgmres_nmpc/cgmres_nmpc.html @@ -0,0 +1,201 @@ + + + + + + + Nonlinear Model Predictive Control with C-GMRES — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Nonlinear Model Predictive Control with C-GMRES

+../../../_images/cgmres_nmpc_1_0.png +../../../_images/cgmres_nmpc_2_0.png +../../../_images/cgmres_nmpc_3_0.png +../../../_images/cgmres_nmpc_4_0.png +
+gif +
+
+

Mathematical Formulation

+

Motion model is

+
+\[\dot{x}=vcos\theta\]
+
+\[\dot{y}=vsin\theta\]
+
+\[\dot{\theta}=\frac{v}{WB}sin(u_{\delta})\]
+

(tan is not good for optimization)

+
+\[\dot{v}=u_a\]
+

Cost function is

+
+\[J=\frac{1}{2}(u_a^2+u_{\delta}^2)-\phi_a d_a-\phi_\delta d_\delta\]
+

Input constraints are

+
+\[|u_a| \leq u_{a,max}\]
+
+\[|u_\delta| \leq u_{\delta,max}\]
+

So, Hamiltonian is

+
+\[\begin{split}J=\frac{1}{2}(u_a^2+u_{\delta}^2)-\phi_a d_a-\phi_\delta d_\delta\\ +\lambda_1vcos\theta+\lambda_2vsin\theta+\lambda_3\frac{v}{WB}sin(u_{\delta})+\lambda_4u_a\\ ++\rho_1(u_a^2+d_a^2+u_{a,max}^2)+\rho_2(u_\delta^2+d_\delta^2+u_{\delta,max}^2)\end{split}\]
+

Partial differential equations of the Hamiltonian are:

+

\(\begin{equation*} \frac{\partial H}{\partial \bf{x}}=\\ \begin{bmatrix} \frac{\partial H}{\partial x}= 0&\\ \frac{\partial H}{\partial y}= 0&\\ \frac{\partial H}{\partial \theta}= -\lambda_1vsin\theta+\lambda_2vcos\theta&\\ \frac{\partial H}{\partial v}=-\lambda_1cos\theta+\lambda_2sin\theta+\lambda_3\frac{1}{WB}sin(u_{\delta})&\\ \end{bmatrix} \\ \end{equation*}\)

+

\(\begin{equation*} \frac{\partial H}{\partial \bf{u}}=\\ \begin{bmatrix} \frac{\partial H}{\partial u_a}= u_a+\lambda_4+2\rho_1u_a&\\ \frac{\partial H}{\partial u_\delta}= u_\delta+\lambda_3\frac{v}{WB}cos(u_{\delta})+2\rho_2u_\delta&\\ \frac{\partial H}{\partial d_a}= -\phi_a+2\rho_1d_a&\\ \frac{\partial H}{\partial d_\delta}=-\phi_\delta+2\rho_2d_\delta&\\ \end{bmatrix} \\ \end{equation*}\)

+
+
+

Ref

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control.html b/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control.html new file mode 100644 index 0000000000..66dbb838a0 --- /dev/null +++ b/modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control.html @@ -0,0 +1,162 @@ + + + + + + + Linear–quadratic regulator (LQR) speed and steering control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Linear–quadratic regulator (LQR) speed and steering control

+

Path tracking simulation with LQR speed and steering control.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_speed_steer_control/animation.gif +
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/lqr_steering_control/lqr_steering_control.html b/modules/path_tracking/lqr_steering_control/lqr_steering_control.html new file mode 100644 index 0000000000..5fe51907a1 --- /dev/null +++ b/modules/path_tracking/lqr_steering_control/lqr_steering_control.html @@ -0,0 +1,163 @@ + + + + + + + Linear–quadratic regulator (LQR) steering control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Linear–quadratic regulator (LQR) steering control

+

Path tracking simulation with LQR steering control and PID speed +control.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_steer_control/animation.gif +
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control.html b/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control.html new file mode 100644 index 0000000000..75895c94d0 --- /dev/null +++ b/modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control.html @@ -0,0 +1,271 @@ + + + + + + + Model predictive speed and steering control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Model predictive speed and steering control

+
+Model predictive speed and steering control +
+

Model predictive speed and steering control

+
+
+

code:

+

PythonRobotics/model_predictive_speed_and_steer_control.py at master · +AtsushiSakai/PythonRobotics

+

This is a path tracking simulation using model predictive control (MPC).

+

The MPC controller controls vehicle speed and steering base on +linearized model.

+

This code uses cvxpy as an optimization modeling tool.

+ +
+

MPC modeling

+

State vector is:

+
+\[z = [x, y, v,\phi]\]
+

x: x-position, y:y-position, v:velocity, φ: yaw angle

+

Input vector is:

+
+\[u = [a, \delta]\]
+

a: accellation, δ: steering angle

+

The MPC cotroller minimize this cost function for path tracking:

+
+\[min\ Q_f(z_{T,ref}-z_{T})^2+Q\Sigma({z_{t,ref}-z_{t}})^2+R\Sigma{u_t}^2+R_d\Sigma({u_{t+1}-u_{t}})^2\]
+

z_ref come from target path and speed.

+

subject to:

+
    +
  • Linearlied vehicle model

  • +
+
+\[z_{t+1}=Az_t+Bu+C\]
+
    +
  • Maximum steering speed

  • +
+
+\[|u_{t+1}-u_{t}|<du_{max}\]
+
    +
  • Maximum steering angle

  • +
+
+\[|u_{t}|<u_{max}\]
+
    +
  • Initial state

  • +
+
+\[z_0 = z_{0,ob}\]
+
    +
  • Maximum and minimum speed

  • +
+
+\[v_{min} < v_t < v_{max}\]
+
    +
  • Maximum and minimum input

  • +
+
+\[u_{min} < u_t < u_{max}\]
+

This is implemented at

+

PythonRobotics/model_predictive_speed_and_steer_control.py at +f51a73f47cb922a12659f8ce2d544c347a2a8156 · +AtsushiSakai/PythonRobotics

+
+
+

Vehicle model linearization

+

Vehicle model is

+
+\[\dot{x} = vcos(\phi)\]
+
+\[\dot{y} = vsin((\phi)\]
+
+\[\dot{v} = a\]
+
+\[\dot{\phi} = \frac{vtan(\delta)}{L}\]
+

ODE is

+
+\[\dot{z} =\frac{\partial }{\partial z} z = f(z, u) = A'z+B'u\]
+

where

+

\(\begin{equation*} A' = \begin{bmatrix} \frac{\partial }{\partial x}vcos(\phi) & \frac{\partial }{\partial y}vcos(\phi) & \frac{\partial }{\partial v}vcos(\phi) & \frac{\partial }{\partial \phi}vcos(\phi)\\ \frac{\partial }{\partial x}vsin(\phi) & \frac{\partial }{\partial y}vsin(\phi) & \frac{\partial }{\partial v}vsin(\phi) & \frac{\partial }{\partial \phi}vsin(\phi)\\ \frac{\partial }{\partial x}a& \frac{\partial }{\partial y}a& \frac{\partial }{\partial v}a& \frac{\partial }{\partial \phi}a\\ \frac{\partial }{\partial x}\frac{vtan(\delta)}{L}& \frac{\partial }{\partial y}\frac{vtan(\delta)}{L}& \frac{\partial }{\partial v}\frac{vtan(\delta)}{L}& \frac{\partial }{\partial \phi}\frac{vtan(\delta)}{L}\\ \end{bmatrix} \\  = \begin{bmatrix} 0 & 0 & cos(\bar{\phi}) & -\bar{v}sin(\bar{\phi})\\ 0 & 0 & sin(\bar{\phi}) & \bar{v}cos(\bar{\phi}) \\ 0 & 0 & 0 & 0 \\ 0 & 0 &\frac{tan(\bar{\delta})}{L} & 0 \\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} B' = \begin{bmatrix} \frac{\partial }{\partial a}vcos(\phi) & \frac{\partial }{\partial \delta}vcos(\phi)\\ \frac{\partial }{\partial a}vsin(\phi) & \frac{\partial }{\partial \delta}vsin(\phi)\\ \frac{\partial }{\partial a}a & \frac{\partial }{\partial \delta}a\\ \frac{\partial }{\partial a}\frac{vtan(\delta)}{L} & \frac{\partial }{\partial \delta}\frac{vtan(\delta)}{L}\\ \end{bmatrix} \\  = \begin{bmatrix} 0 & 0 \\ 0 & 0 \\ 1 & 0 \\ 0 & \frac{\bar{v}}{Lcos^2(\bar{\delta})} \\ \end{bmatrix} \end{equation*}\)

+

You can get a discrete-time mode with Forward Euler Discretization with +sampling time dt.

+
+\[z_{k+1}=z_k+f(z_k,u_k)dt\]
+

Using first degree Tayer expantion around zbar and ubar

+
+\[z_{k+1}=z_k+(f(\bar{z},\bar{u})+A'z_k+B'u_k-A'\bar{z}-B'\bar{u})dt\]
+
+\[z_{k+1}=(I + dtA')z_k+(dtB')u_k + (f(\bar{z},\bar{u})-A'\bar{z}-B'\bar{u})dt\]
+

So,

+
+\[z_{k+1}=Az_k+Bu_k +C\]
+

where,

+

\(\begin{equation*} A = (I + dtA')\\ = \begin{bmatrix} 1 & 0 & cos(\bar{\phi})dt & -\bar{v}sin(\bar{\phi})dt\\ 0 & 1 & sin(\bar{\phi})dt & \bar{v}cos(\bar{\phi})dt \\ 0 & 0 & 1 & 0 \\ 0 & 0 &\frac{tan(\bar{\delta})}{L}dt & 1 \\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} B = dtB'\\ = \begin{bmatrix} 0 & 0 \\ 0 & 0 \\ dt & 0 \\ 0 & \frac{\bar{v}}{Lcos^2(\bar{\delta})}dt \\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} C = (f(\bar{z},\bar{u})-A'\bar{z}-B'\bar{u})dt\\ = dt( \begin{bmatrix} \bar{v}cos(\bar{\phi})\\ \bar{v}sin(\bar{\phi}) \\ \bar{a}\\ \frac{\bar{v}tan(\bar{\delta})}{L}\\ \end{bmatrix} - \begin{bmatrix} \bar{v}cos(\bar{\phi})-\bar{v}sin(\bar{\phi})\bar{\phi}\\ \bar{v}sin(\bar{\phi})+\bar{v}cos(\bar{\phi})\bar{\phi}\\ 0\\ \frac{\bar{v}tan(\bar{\delta})}{L}\\ \end{bmatrix} - \begin{bmatrix} 0\\ 0 \\ \bar{a}\\ \frac{\bar{v}\bar{\delta}}{Lcos^2(\bar{\delta})}\\ \end{bmatrix} )\\ = \begin{bmatrix} \bar{v}sin(\bar{\phi})\bar{\phi}dt\\ -\bar{v}cos(\bar{\phi})\bar{\phi}dt\\ 0\\ -\frac{\bar{v}\bar{\delta}}{Lcos^2(\bar{\delta})}dt\\ \end{bmatrix} \end{equation*}\)

+

This equation is implemented at

+

PythonRobotics/model_predictive_speed_and_steer_control.py at +eb6d1cbe6fc90c7be9210bf153b3a04f177cc138 · +AtsushiSakai/PythonRobotics

+
+
+

Reference

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/path_tracking.html b/modules/path_tracking/path_tracking.html new file mode 100644 index 0000000000..b89c48a67c --- /dev/null +++ b/modules/path_tracking/path_tracking.html @@ -0,0 +1,186 @@ + + + + + + + Path Tracking — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking.html b/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking.html new file mode 100644 index 0000000000..05f35b4fac --- /dev/null +++ b/modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking.html @@ -0,0 +1,166 @@ + + + + + + + Pure pursuit tracking — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Pure pursuit tracking

+

Path tracking simulation with pure pursuit steering control and PID +speed control.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/pure_pursuit/animation.gif +

The red line is a target course, the green cross means the target point +for pure pursuit control, the blue line is the tracking.

+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control.html b/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control.html new file mode 100644 index 0000000000..51d52456fd --- /dev/null +++ b/modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control.html @@ -0,0 +1,164 @@ + + + + + + + Rear wheel feedback control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Rear wheel feedback control

+

Path tracking simulation with rear wheel feedback steering control and +PID speed control.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/rear_wheel_feedback/animation.gif +
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/path_tracking/stanley_control/stanley_control.html b/modules/path_tracking/stanley_control/stanley_control.html new file mode 100644 index 0000000000..536f41fd48 --- /dev/null +++ b/modules/path_tracking/stanley_control/stanley_control.html @@ -0,0 +1,166 @@ + + + + + + + Stanley control — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Stanley control

+

Path tracking simulation with Stanley steering control and PID speed +control.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/stanley_controller/animation.gif +
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/FastSLAM1/FastSLAM1.html b/modules/slam/FastSLAM1/FastSLAM1.html new file mode 100644 index 0000000000..cb84afc95d --- /dev/null +++ b/modules/slam/FastSLAM1/FastSLAM1.html @@ -0,0 +1,649 @@ + + + + + + + FastSLAM1.0 — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

FastSLAM1.0

+
+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/FastSLAM1/animation.gif +
+
+

Simulation

+

This is a feature based SLAM example using FastSLAM 1.0.

+../../../_images/FastSLAM1_1_0.png +

The blue line is ground truth, the black line is dead reckoning, the red +line is the estimated trajectory with FastSLAM.

+

The red points are particles of FastSLAM.

+

Black points are landmarks, blue crosses are estimated landmark +positions by FastSLAM.

+
+
+

Introduction

+

FastSLAM algorithm implementation is based on particle filters and +belongs to the family of probabilistic SLAM approaches. It is used with +feature-based maps (see gif above) or with occupancy grid maps.

+

As it is shown, the particle filter differs from EKF by representing the +robot’s estimation through a set of particles. Each single particle has +an independent belief, as it holds the pose \((x, y, \theta)\) and +an array of landmark locations +\([(x_1, y_1), (x_2, y_2), ... (x_n, y_n)]\) for n landmarks.

+
    +
  • The blue line is the true trajectory

  • +
  • The red line is the estimated trajectory

  • +
  • The red dots represent the distribution of particles

  • +
  • The black line represent dead reckoning trajectory

  • +
  • The blue x is the observed and estimated landmarks

  • +
  • The black x is the true landmark

  • +
+

I.e. Each particle maintains a deterministic pose and n-EKFs for each +landmark and update it with each measurement.

+
+
+

Algorithm walk through

+

The particles are initially drawn from a uniform distribution the +represent the initial uncertainty. At each time step we do:

+
    +
  • Predict the pose for each particle by using \(u\) and the motion +model (the landmarks are not updated).

  • +
  • Update the particles with observations \(z\), where the weights +are adjusted based on how likely the particle to have the correct +pose given the sensor measurement

  • +
  • Resampling such that the particles with the largest weights survive +and the unlikely ones with the lowest weights die out.

  • +
+
+
+

1- Predict

+

The following equations and code snippets we can see how the particles +distribution evolves in case we provide only the control \((v,w)\), +which are the linear and angular velocity respectively.

+

\(\begin{equation*} F= \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} B= \begin{bmatrix} \Delta t cos(\theta) & 0\\ \Delta t sin(\theta) & 0\\ 0 & \Delta t \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} X = FX + BU \end{equation*}\)

+

\(\begin{equation*} \begin{bmatrix} x_{t+1} \\ y_{t+1} \\ \theta_{t+1} \end{bmatrix}= \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} x_{t} \\ y_{t} \\ \theta_{t} \end{bmatrix}+ \begin{bmatrix} \Delta t cos(\theta) & 0\\ \Delta t sin(\theta) & 0\\ 0 & \Delta t \end{bmatrix} \begin{bmatrix} v_{t} + \sigma_v\\ w_{t} + \sigma_w\\ \end{bmatrix} \end{equation*}\)

+

The following snippets playback the recorded trajectory of each +particle.

+

To get the insight of the motion model change the value of \(R\) and +re-run the cells again. As R is the parameters that indicates how much +we trust that the robot executed the motion commands.

+

It is interesting to notice also that only motion will increase the +uncertainty in the system as the particles start to spread out more. If +observations are included the uncertainty will decrease and particles +will converge to the correct estimate.

+
# CODE SNIPPET #
+import numpy as np
+import math
+from copy import deepcopy
+# Fast SLAM covariance
+Q = np.diag([3.0, np.deg2rad(10.0)])**2
+R = np.diag([1.0, np.deg2rad(20.0)])**2
+
+#  Simulation parameter
+Qsim = np.diag([0.3, np.deg2rad(2.0)])**2
+Rsim = np.diag([0.5, np.deg2rad(10.0)])**2
+OFFSET_YAWRATE_NOISE = 0.01
+
+DT = 0.1  # time tick [s]
+SIM_TIME = 50.0  # simulation time [s]
+MAX_RANGE = 20.0  # maximum observation range
+M_DIST_TH = 2.0  # Threshold of Mahalanobis distance for data association.
+STATE_SIZE = 3  # State size [x,y,yaw]
+LM_SIZE = 2  # LM srate size [x,y]
+N_PARTICLE = 100  # number of particle
+NTH = N_PARTICLE / 1.5  # Number of particle for re-sampling
+
+class Particle:
+
+    def __init__(self, N_LM):
+        self.w = 1.0 / N_PARTICLE
+        self.x = 0.0
+        self.y = 0.0
+        self.yaw = 0.0
+        # landmark x-y positions
+        self.lm = np.zeros((N_LM, LM_SIZE))
+        # landmark position covariance
+        self.lmP = np.zeros((N_LM * LM_SIZE, LM_SIZE))
+
+def motion_model(x, u):
+    F = np.array([[1.0, 0, 0],
+                  [0, 1.0, 0],
+                  [0, 0, 1.0]])
+
+    B = np.array([[DT * math.cos(x[2, 0]), 0],
+                  [DT * math.sin(x[2, 0]), 0],
+                  [0.0, DT]])
+    x = F @ x + B @ u
+
+    x[2, 0] = pi_2_pi(x[2, 0])
+    return x
+
+def predict_particles(particles, u):
+    for i in range(N_PARTICLE):
+        px = np.zeros((STATE_SIZE, 1))
+        px[0, 0] = particles[i].x
+        px[1, 0] = particles[i].y
+        px[2, 0] = particles[i].yaw
+        ud = u + (np.random.randn(1, 2) @ R).T  # add noise
+        px = motion_model(px, ud)
+        particles[i].x = px[0, 0]
+        particles[i].y = px[1, 0]
+        particles[i].yaw = px[2, 0]
+
+    return particles
+
+def pi_2_pi(angle):
+    return (angle + math.pi) % (2 * math.pi) - math.pi
+
+# END OF SNIPPET
+
+N_LM = 0
+particles = [Particle(N_LM) for i in range(N_PARTICLE)]
+time= 0.0
+v = 1.0  # [m/s]
+yawrate = 0.1  # [rad/s]
+u = np.array([v, yawrate]).reshape(2, 1)
+history = []
+while SIM_TIME >= time:
+    time += DT
+    particles = predict_particles(particles, u)
+    history.append(deepcopy(particles))
+
+
+
# from IPython.html.widgets import *
+from ipywidgets import *
+import numpy as np
+import matplotlib.pyplot as plt
+%matplotlib inline
+
+# playback the recorded motion of the particles
+def plot_particles(t=0):
+    x = []
+    y = []
+    for i in range(len(history[t])):
+        x.append(history[t][i].x)
+        y.append(history[t][i].y)
+    plt.figtext(0.15,0.82,'t = ' + str(t))
+    plt.plot(x, y, '.r')
+    plt.axis([-20,20, -5,25])
+
+interact(plot_particles, t=(0,len(history)-1,1));
+
+
+
interactive(children=(IntSlider(value=0, description='t', max=499), Output()), _dom_classes=('widget-interact'…
+
+
+
+
+

2- Update

+

For the update step it is useful to observe a single particle and the +effect of getting a new measurements on the weight of the particle.

+

As mentioned earlier, each particle maintains \(N\) \(2x2\) EKFs +to estimate the landmarks, which includes the EKF process described in +the EKF notebook. The difference is the change in the weight of the +particle according to how likely the measurement is.

+

The weight is updated according to the following equation:

+

\(\begin{equation*} w_i = |2\pi Q|^{\frac{-1}{2}} exp\{\frac{-1}{2}(z_t - \hat z_i)^T Q^{-1}(z_t-\hat z_i)\} \end{equation*}\)

+

Where, \(w_i\) is the computed weight, \(Q\) is the measurement +covariance, \(z_t\) is the actual measurment and \(\hat z_i\) is +the predicted measurement of particle \(i\).

+

To experiment this, a single particle is initialized then passed an +initial measurement, which results in a relatively average weight. +However, setting the particle coordinate to a wrong value to simulate +wrong estimation will result in a very low weight. The lower the weight +the less likely that this particle will be drawn during resampling and +probably will die out.

+
# CODE SNIPPET #
+def observation(xTrue, xd, u, RFID):
+
+    # calc true state
+    xTrue = motion_model(xTrue, u)
+
+    # add noise to range observation
+    z = np.zeros((3, 0))
+    for i in range(len(RFID[:, 0])):
+
+        dx = RFID[i, 0] - xTrue[0, 0]
+        dy = RFID[i, 1] - xTrue[1, 0]
+        d = math.sqrt(dx**2 + dy**2)
+        angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0])
+        if d <= MAX_RANGE:
+            dn = d + np.random.randn() * Qsim[0, 0]  # add noise
+            anglen = angle + np.random.randn() * Qsim[1, 1]  # add noise
+            zi = np.array([dn, pi_2_pi(anglen), i]).reshape(3, 1)
+            z = np.hstack((z, zi))
+
+    # add noise to input
+    ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0]
+    ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1] + OFFSET_YAWRATE_NOISE
+    ud = np.array([ud1, ud2]).reshape(2, 1)
+
+    xd = motion_model(xd, ud)
+
+    return xTrue, z, xd, ud
+
+def update_with_observation(particles, z):
+    for iz in range(len(z[0, :])):
+
+        lmid = int(z[2, iz])
+
+        for ip in range(N_PARTICLE):
+            # new landmark
+            if abs(particles[ip].lm[lmid, 0]) <= 0.01:
+                particles[ip] = add_new_lm(particles[ip], z[:, iz], Q)
+            # known landmark
+            else:
+                w = compute_weight(particles[ip], z[:, iz], Q)
+                particles[ip].w *= w
+                particles[ip] = update_landmark(particles[ip], z[:, iz], Q)
+
+    return particles
+
+def compute_weight(particle, z, Q):
+    lm_id = int(z[2])
+    xf = np.array(particle.lm[lm_id, :]).reshape(2, 1)
+    Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2])
+    zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q)
+    dx = z[0:2].reshape(2, 1) - zp
+    dx[1, 0] = pi_2_pi(dx[1, 0])
+
+    try:
+        invS = np.linalg.inv(Sf)
+    except np.linalg.linalg.LinAlgError:
+        print("singuler")
+        return 1.0
+
+    num = math.exp(-0.5 * dx.T @ invS @ dx)
+    den = 2.0 * math.pi * math.sqrt(np.linalg.det(Sf))
+    w = num / den
+
+    return w
+
+def compute_jacobians(particle, xf, Pf, Q):
+    dx = xf[0, 0] - particle.x
+    dy = xf[1, 0] - particle.y
+    d2 = dx**2 + dy**2
+    d = math.sqrt(d2)
+
+    zp = np.array(
+        [d, pi_2_pi(math.atan2(dy, dx) - particle.yaw)]).reshape(2, 1)
+
+    Hv = np.array([[-dx / d, -dy / d, 0.0],
+                   [dy / d2, -dx / d2, -1.0]])
+
+    Hf = np.array([[dx / d, dy / d],
+                   [-dy / d2, dx / d2]])
+
+    Sf = Hf @ Pf @ Hf.T + Q
+
+    return zp, Hv, Hf, Sf
+
+def add_new_lm(particle, z, Q):
+
+    r = z[0]
+    b = z[1]
+    lm_id = int(z[2])
+
+    s = math.sin(pi_2_pi(particle.yaw + b))
+    c = math.cos(pi_2_pi(particle.yaw + b))
+
+    particle.lm[lm_id, 0] = particle.x + r * c
+    particle.lm[lm_id, 1] = particle.y + r * s
+
+    # covariance
+    Gz = np.array([[c, -r * s],
+                   [s, r * c]])
+
+    particle.lmP[2 * lm_id:2 * lm_id + 2] = Gz @ Q @ Gz.T
+
+    return particle
+
+def update_KF_with_cholesky(xf, Pf, v, Q, Hf):
+    PHt = Pf @ Hf.T
+    S = Hf @ PHt + Q
+
+    S = (S + S.T) * 0.5
+    SChol = np.linalg.cholesky(S).T
+    SCholInv = np.linalg.inv(SChol)
+    W1 = PHt @ SCholInv
+    W = W1 @ SCholInv.T
+
+    x = xf + W @ v
+    P = Pf - W1 @ W1.T
+
+    return x, P
+
+def update_landmark(particle, z, Q):
+
+    lm_id = int(z[2])
+    xf = np.array(particle.lm[lm_id, :]).reshape(2, 1)
+    Pf = np.array(particle.lmP[2 * lm_id:2 * lm_id + 2, :])
+
+    zp, Hv, Hf, Sf = compute_jacobians(particle, xf, Pf, Q)
+
+    dz = z[0:2].reshape(2, 1) - zp
+    dz[1, 0] = pi_2_pi(dz[1, 0])
+
+    xf, Pf = update_KF_with_cholesky(xf, Pf, dz, Q, Hf)
+
+    particle.lm[lm_id, :] = xf.T
+    particle.lmP[2 * lm_id:2 * lm_id + 2, :] = Pf
+
+    return particle
+
+# END OF CODE SNIPPET #
+
+
+
+# Setting up the landmarks
+RFID = np.array([[10.0, -2.0],
+                [15.0, 10.0]])
+N_LM = RFID.shape[0]
+
+# Initialize 1 particle
+N_PARTICLE = 1
+particles = [Particle(N_LM) for i in range(N_PARTICLE)]
+
+xTrue = np.zeros((STATE_SIZE, 1))
+xDR = np.zeros((STATE_SIZE, 1))
+
+print("initial weight", particles[0].w)
+
+xTrue, z, _, ud = observation(xTrue, xDR, u, RFID)
+# Initialize landmarks
+particles = update_with_observation(particles, z)
+print("weight after landmark intialization", particles[0].w)
+particles = update_with_observation(particles, z)
+print("weight after update ", particles[0].w)
+
+particles[0].x = -10
+particles = update_with_observation(particles, z)
+print("weight after wrong prediction", particles[0].w)
+
+
+
initial weight 1.0
+weight after landmark intialization 1.0
+weight after update  0.023098460073039763
+weight after wrong prediction 7.951154575772496e-07
+
+
+
+
+

3- Resampling

+

In the reseampling steps a new set of particles are chosen from the old +set. This is done according to the weight of each particle.

+

The figure shows 100 particles distributed uniformly between [-0.5, 0.5] +with the weights of each particle distributed according to a Gaussian +funciton.

+

The resampling picks

+

\(i \in 1,...,N\) particles with probability to pick particle with +index \(i ∝ \omega_i\), where \(\omega_i\) is the weight of that +particle

+

To get the intuition of the resampling step we will look at a set of +particles which are initialized with a given x location and weight. +After the resampling the particles are more concetrated in the location +where they had the highest weights. This is also indicated by the +indices

+
# CODE SNIPPET #
+def normalize_weight(particles):
+
+    sumw = sum([p.w for p in particles])
+
+    try:
+        for i in range(N_PARTICLE):
+            particles[i].w /= sumw
+    except ZeroDivisionError:
+        for i in range(N_PARTICLE):
+            particles[i].w = 1.0 / N_PARTICLE
+
+        return particles
+
+    return particles
+
+
+def resampling(particles):
+    """
+    low variance re-sampling
+    """
+
+    particles = normalize_weight(particles)
+
+    pw = []
+    for i in range(N_PARTICLE):
+        pw.append(particles[i].w)
+
+    pw = np.array(pw)
+
+    Neff = 1.0 / (pw @ pw.T)  # Effective particle number
+    # print(Neff)
+
+    if Neff < NTH:  # resampling
+        wcum = np.cumsum(pw)
+        base = np.cumsum(pw * 0.0 + 1 / N_PARTICLE) - 1 / N_PARTICLE
+        resampleid = base + np.random.rand(base.shape[0]) / N_PARTICLE
+
+        inds = []
+        ind = 0
+        for ip in range(N_PARTICLE):
+            while ((ind < wcum.shape[0] - 1) and (resampleid[ip] > wcum[ind])):
+                ind += 1
+            inds.append(ind)
+
+        tparticles = particles[:]
+        for i in range(len(inds)):
+            particles[i].x = tparticles[inds[i]].x
+            particles[i].y = tparticles[inds[i]].y
+            particles[i].yaw = tparticles[inds[i]].yaw
+            particles[i].w = 1.0 / N_PARTICLE
+
+    return particles, inds
+# END OF SNIPPET #
+
+
+
+def gaussian(x, mu, sig):
+    return np.exp(-np.power(x - mu, 2.) / (2 * np.power(sig, 2.)))
+N_PARTICLE = 100
+particles = [Particle(N_LM) for i in range(N_PARTICLE)]
+x_pos = []
+w = []
+for i in range(N_PARTICLE):
+    particles[i].x = np.linspace(-0.5,0.5,N_PARTICLE)[i]
+    x_pos.append(particles[i].x)
+    particles[i].w = gaussian(i, N_PARTICLE/2, N_PARTICLE/20)
+    w.append(particles[i].w)
+
+
+# Normalize weights
+sw = sum(w)
+for i in range(N_PARTICLE):
+    w[i] /= sw
+
+particles, new_indices = resampling(particles)
+x_pos2 = []
+for i in range(N_PARTICLE):
+    x_pos2.append(particles[i].x)
+
+# Plot results
+fig, ((ax1,ax2,ax3)) = plt.subplots(nrows=3, ncols=1)
+fig.tight_layout()
+ax1.plot(x_pos,np.ones((N_PARTICLE,1)), '.r', markersize=2)
+ax1.set_title("Particles before resampling")
+ax1.axis((-1, 1, 0, 2))
+ax2.plot(w)
+ax2.set_title("Weights distribution")
+ax3.plot(x_pos2,np.ones((N_PARTICLE,1)), '.r')
+ax3.set_title("Particles after resampling")
+ax3.axis((-1, 1, 0, 2))
+fig.subplots_adjust(hspace=0.8)
+plt.show()
+
+plt.figure()
+plt.hist(new_indices)
+plt.xlabel("Particles indices to be resampled")
+plt.ylabel("# of time index is used")
+plt.show()
+
+
+../../../_images/FastSLAM1_12_0.png +../../../_images/FastSLAM1_12_1.png +
+
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/FastSLAM2/FastSLAM2.html b/modules/slam/FastSLAM2/FastSLAM2.html new file mode 100644 index 0000000000..7c1359bb7b --- /dev/null +++ b/modules/slam/FastSLAM2/FastSLAM2.html @@ -0,0 +1,162 @@ + + + + + + + FastSLAM 2.0 — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

FastSLAM 2.0

+

This is a feature based SLAM example using FastSLAM 2.0.

+

The animation has the same meanings as one of FastSLAM 1.0.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/FastSLAM2/animation.gif +
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/ekf_slam/ekf_slam.html b/modules/slam/ekf_slam/ekf_slam.html new file mode 100644 index 0000000000..98cb6aa92a --- /dev/null +++ b/modules/slam/ekf_slam/ekf_slam.html @@ -0,0 +1,685 @@ + + + + + + + EKF SLAM — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EKF SLAM

+

This is an Extended Kalman Filter based SLAM example.

+

The blue line is ground truth, the black line is dead reckoning, the red +line is the estimated trajectory with EKF SLAM.

+

The green crosses are estimated landmarks.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/EKFSLAM/animation.gif +
+

Simulation

+

This is a simulation of EKF SLAM.

+
    +
  • Black stars: landmarks

  • +
  • Green crosses: estimates of landmark positions

  • +
  • Black line: dead reckoning

  • +
  • Blue line: ground truth

  • +
  • Red line: EKF SLAM position estimation

  • +
+
+
+

Introduction

+

EKF SLAM models the SLAM problem in a single EKF where the modeled state +is both the pose \((x, y, \theta)\) and an array of landmarks +\([(x_1, y_1), (x_2, x_y), ... , (x_n, y_n)]\) for \(n\) +landmarks. The covariance between each of the positions and landmarks +are also tracked.

+

\(\begin{equation} X = \begin{bmatrix} x \\ y \\ \theta \\ x_1 \\ y_1 \\ x_2 \\ y_2 \\ \dots \\ x_n \\ y_n \end{bmatrix} \end{equation}\)

+

\(\begin{equation} P = \begin{bmatrix} \sigma_{xx} & \sigma_{xy} & \sigma_{x\theta} & \sigma_{xx_1} & \sigma_{xy_1} & \sigma_{xx_2} & \sigma_{xy_2} & \dots & \sigma_{xx_n} & \sigma_{xy_n} \\ \sigma_{yx} & \sigma_{yy} & \sigma_{y\theta} & \sigma_{yx_1} & \sigma_{yy_1} & \sigma_{yx_2} & \sigma_{yy_2} & \dots & \sigma_{yx_n} & \sigma_{yy_n} \\ & & & & \vdots & & & & & \\ \sigma_{x_nx} & \sigma_{x_ny} & \sigma_{x_n\theta} & \sigma_{x_nx_1} & \sigma_{x_ny_1} & \sigma_{x_nx_2} & \sigma_{x_ny_2} & \dots & \sigma_{x_nx_n} & \sigma_{x_ny_n} \end{bmatrix} \end{equation}\)

+

A single estimate of the pose is tracked over time, while the confidence +in the pose is tracked by the covariance matrix \(P\). \(P\) is +a symmetric square matrix which each element in the matrix corresponding +to the covariance between two parts of the system. For example, +\(\sigma_{xy}\) represents the covariance between the belief of +\(x\) and \(y\) and is equal to \(\sigma_{yx}\).

+

The state can be represented more concisely as follows.

+

\(\begin{equation} X = \begin{bmatrix} x \\ m \end{bmatrix} \end{equation}\) +\(\begin{equation} P = \begin{bmatrix} \Sigma_{xx} & \Sigma_{xm}\\ \Sigma_{mx} & \Sigma_{mm}\\ \end{bmatrix} \end{equation}\)

+

Here the state simplifies to a combination of pose (\(x\)) and map +(\(m\)). The covariance matrix becomes easier to understand and +simply reads as the uncertainty of the robots pose +(\(\Sigma_{xx}\)), the uncertainty of the map (\(\Sigma_{mm}\)), +and the uncertainty of the robots pose with respect to the map and vice +versa (\(\Sigma_{xm}\), \(\Sigma_{mx}\)).

+

Take care to note the difference between \(X\) (state) and \(x\) +(pose).

+
"""
+Extended Kalman Filter SLAM example
+original author: Atsushi Sakai (@Atsushi_twi)
+notebook author: Andrew Tu (drewtu2)
+"""
+
+import math
+import numpy as np
+%matplotlib notebook
+import matplotlib.pyplot as plt
+
+
+# EKF state covariance
+Cx = np.diag([0.5, 0.5, np.deg2rad(30.0)])**2 # Change in covariance
+
+#  Simulation parameter
+Qsim = np.diag([0.2, np.deg2rad(1.0)])**2  # Sensor Noise
+Rsim = np.diag([1.0, np.deg2rad(10.0)])**2 # Process Noise
+
+DT = 0.1  # time tick [s]
+SIM_TIME = 50.0  # simulation time [s]
+MAX_RANGE = 20.0  # maximum observation range
+M_DIST_TH = 2.0  # Threshold of Mahalanobis distance for data association.
+STATE_SIZE = 3  # State size [x,y,yaw]
+LM_SIZE = 2  # LM state size [x,y]
+
+show_animation = True
+
+
+
+
+

Algorithm Walk through

+

At each time step, the following is done. - predict the new state using +the control functions - update the belief in landmark positions based on +the estimated state and measurements

+
def ekf_slam(xEst, PEst, u, z):
+    """
+    Performs an iteration of EKF SLAM from the available information.
+
+    :param xEst: the belief in last position
+    :param PEst: the uncertainty in last position
+    :param u:    the control function applied to the last position
+    :param z:    measurements at this step
+    :returns:    the next estimated position and associated covariance
+    """
+    S = STATE_SIZE
+
+    # Predict
+    xEst, PEst, G, Fx = predict(xEst, PEst, u)
+    initP = np.eye(2)
+
+    # Update
+    xEst, PEst = update(xEst, PEst, u, z, initP)
+
+    return xEst, PEst
+
+
+
+
+

1- Predict

+

Predict State update: The following equations describe the predicted +motion model of the robot in case we provide only the control +\((v,w)\), which are the linear and angular velocity respectively.

+

\(\begin{equation*} F= \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} B= \begin{bmatrix} \Delta t cos(\theta) & 0\\ \Delta t sin(\theta) & 0\\ 0 & \Delta t \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} U= \begin{bmatrix} v_t\\ w_t\\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} X = FX + BU \end{equation*}\)

+

\(\begin{equation*} \begin{bmatrix} x_{t+1} \\ y_{t+1} \\ \theta_{t+1} \end{bmatrix}= \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} x_{t} \\ y_{t} \\ \theta_{t} \end{bmatrix}+ \begin{bmatrix} \Delta t cos(\theta) & 0\\ \Delta t sin(\theta) & 0\\ 0 & \Delta t \end{bmatrix} \begin{bmatrix} v_{t} + \sigma_v\\ w_{t} + \sigma_w\\ \end{bmatrix} \end{equation*}\)

+

Notice that while \(U\) is only defined by \(v_t\) and +\(w_t\), in the actual calculations, a \(+\sigma_v\) and +\(+\sigma_w\) appear. These values represent the error between the +given control inputs and the actual control inputs.

+

As a result, the simulation is set up as the following. \(R\) +represents the process noise which is added to the control inputs to +simulate noise experienced in the real world. A set of truth values are +computed from the raw control values while the values dead reckoning +values incorporate the error into the estimation.

+

\(\begin{equation*} R= \begin{bmatrix} \sigma_v\\ \sigma_w\\ \end{bmatrix} \end{equation*}\)

+

\(\begin{equation*} X_{true} = FX + B(U) \end{equation*}\)

+

\(\begin{equation*} X_{DR} = FX + B(U + R) \end{equation*}\)

+

The implementation of the motion model prediction code is shown in +motion_model. The observation function shows how the simulation +uses (or doesn’t use) the process noise Rsim to the find the ground +truth and dead reckoning estimates of the pose.

+

Predict covariance: Add the state covariance to the the current +uncertainty of the EKF. At each time step, the uncertainty in the system +grows by the covariance of the pose, \(Cx\).

+

\(P = G^TPG + Cx\)

+

Notice this uncertainty is only growing with respect to the pose, not +the landmarks.

+
def predict(xEst, PEst, u):
+    """
+    Performs the prediction step of EKF SLAM
+
+    :param xEst: nx1 state vector
+    :param PEst: nxn covariance matrix
+    :param u:    2x1 control vector
+    :returns:    predicted state vector, predicted covariance, jacobian of control vector, transition fx
+    """
+    S = STATE_SIZE
+    G, Fx = jacob_motion(xEst[0:S], u)
+    xEst[0:S] = motion_model(xEst[0:S], u)
+    # Fx is an an identity matrix of size (STATE_SIZE)
+    # sigma = G*sigma*G.T + Noise
+    PEst[0:S, 0:S] = G.T @ PEst[0:S, 0:S] @ G + Fx.T @ Cx @ Fx
+    return xEst, PEst, G, Fx
+
+
+
def motion_model(x, u):
+    """
+    Computes the motion model based on current state and input function.
+
+    :param x: 3x1 pose estimation
+    :param u: 2x1 control input [v; w]
+    :returns: the resulting state after the control function is applied
+    """
+    F = np.array([[1.0, 0, 0],
+                  [0, 1.0, 0],
+                  [0, 0, 1.0]])
+
+    B = np.array([[DT * math.cos(x[2, 0]), 0],
+                  [DT * math.sin(x[2, 0]), 0],
+                  [0.0, DT]])
+
+    x = (F @ x) + (B @ u)
+    return x
+
+
+
+
+

2 - Update

+

In the update phase, the observations of nearby landmarks are used to +correct the location estimate.

+

For every landmark observed, it is associated to a particular landmark +in the known map. If no landmark exists in the position surrounding the +landmark, it is taken as a NEW landmark. The distance threshold for how +far a landmark must be from the next known landmark before its +considered to be a new landmark is set by M_DIST_TH.

+

With an observation associated to the appropriate landmark, the +innovation can be calculated. Innovation (\(y\)) is the +difference between the observation and the observation that should +have been made if the observation were made from the pose predicted in +the predict stage.

+

\(y = z_t - h(X)\)

+

With the innovation calculated, the question becomes which to trust more +- the observations or the predictions? To determine this, we calculate +the Kalman Gain - a percent of how much of the innovation to add to the +prediction based on the uncertainty in the predict step and the update +step.

+

\(K = \bar{P_t}H_t^T(H_t\bar{P_t}H_t^T + Q_t)^{-1}\) +In these equations, \(H\) is the jacobian of the +measurement function. The multiplications by \(H^T\) and \(H\) +represent the application of the delta to the measurement covariance. +Intuitively, this equation is applying the following from the single +variate Kalman equation but in the multivariate form, i.e. finding the +ratio of the uncertainty of the process compared the measurement.

+

K = \(\frac{\bar{P_t}}{\bar{P_t} + Q_t}\)

+

If \(Q_t << \bar{P_t}\), (i.e. the measurement covariance is low +relative to the current estimate), then the Kalman gain will be +\(~1\). This results in adding all of the innovation to the estimate +– and therefore completely believing the measurement.

+

However, if \(Q_t >> \bar{P_t}\) then the Kalman gain will go to 0, +signaling a high trust in the process and little trust in the +measurement.

+

The update is captured in the following.

+

\(xUpdate = xEst + (K * y)\)

+

Of course, the covariance must also be updated as well to account for +the changing uncertainty.

+

\(P_{t} = (I-K_tH_t)\bar{P_t}\)

+
def update(xEst, PEst, u, z, initP):
+    """
+    Performs the update step of EKF SLAM
+
+    :param xEst:  nx1 the predicted pose of the system and the pose of the landmarks
+    :param PEst:  nxn the predicted covariance
+    :param u:     2x1 the control function
+    :param z:     the measurements read at new position
+    :param initP: 2x2 an identity matrix acting as the initial covariance
+    :returns:     the updated state and covariance for the system
+    """
+    for iz in range(len(z[:, 0])):  # for each observation
+        minid = search_correspond_LM_ID(xEst, PEst, z[iz, 0:2]) # associate to a known landmark
+
+        nLM = calc_n_LM(xEst) # number of landmarks we currently know about
+
+        if minid == nLM: # Landmark is a NEW landmark
+            print("New LM")
+            # Extend state and covariance matrix
+            xAug = np.vstack((xEst, calc_LM_Pos(xEst, z[iz, :])))
+            PAug = np.vstack((np.hstack((PEst, np.zeros((len(xEst), LM_SIZE)))),
+                              np.hstack((np.zeros((LM_SIZE, len(xEst))), initP))))
+            xEst = xAug
+            PEst = PAug
+
+        lm = get_LM_Pos_from_state(xEst, minid)
+        y, S, H = calc_innovation(lm, xEst, PEst, z[iz, 0:2], minid)
+
+        K = (PEst @ H.T) @ np.linalg.inv(S) # Calculate Kalman Gain
+        xEst = xEst + (K @ y)
+        PEst = (np.eye(len(xEst)) - (K @ H)) @ PEst
+
+    xEst[2] = pi_2_pi(xEst[2])
+    return xEst, PEst
+
+
+
def calc_innovation(lm, xEst, PEst, z, LMid):
+    """
+    Calculates the innovation based on expected position and landmark position
+
+    :param lm:   landmark position
+    :param xEst: estimated position/state
+    :param PEst: estimated covariance
+    :param z:    read measurements
+    :param LMid: landmark id
+    :returns:    returns the innovation y, and the jacobian H, and S, used to calculate the Kalman Gain
+    """
+    delta = lm - xEst[0:2]
+    q = (delta.T @ delta)[0, 0]
+    zangle = math.atan2(delta[1, 0], delta[0, 0]) - xEst[2, 0]
+    zp = np.array([[math.sqrt(q), pi_2_pi(zangle)]])
+    # zp is the expected measurement based on xEst and the expected landmark position
+
+    y = (z - zp).T # y = innovation
+    y[1] = pi_2_pi(y[1])
+
+    H = jacobH(q, delta, xEst, LMid + 1)
+    S = H @ PEst @ H.T + Cx[0:2, 0:2]
+
+    return y, S, H
+
+def jacobH(q, delta, x, i):
+    """
+    Calculates the jacobian of the measurement function
+
+    :param q:     the range from the system pose to the landmark
+    :param delta: the difference between a landmark position and the estimated system position
+    :param x:     the state, including the estimated system position
+    :param i:     landmark id + 1
+    :returns:     the jacobian H
+    """
+    sq = math.sqrt(q)
+    G = np.array([[-sq * delta[0, 0], - sq * delta[1, 0], 0, sq * delta[0, 0], sq * delta[1, 0]],
+                  [delta[1, 0], - delta[0, 0], - q, - delta[1, 0], delta[0, 0]]])
+
+    G = G / q
+    nLM = calc_n_LM(x)
+    F1 = np.hstack((np.eye(3), np.zeros((3, 2 * nLM))))
+    F2 = np.hstack((np.zeros((2, 3)), np.zeros((2, 2 * (i - 1))),
+                    np.eye(2), np.zeros((2, 2 * nLM - 2 * i))))
+
+    F = np.vstack((F1, F2))
+
+    H = G @ F
+
+    return H
+
+
+
+
+

Observation Step

+

The observation step described here is outside the main EKF SLAM process +and is primarily used as a method of driving the simulation. The +observations function is in charge of calculating how the poses of the +robots change and accumulate error over time, and the theoretical +measurements that are expected as a result of each measurement.

+

Observations are based on the TRUE position of the robot. Error in dead +reckoning and control functions are passed along here as well.

+
def observation(xTrue, xd, u, RFID):
+    """
+    :param xTrue: the true pose of the system
+    :param xd:    the current noisy estimate of the system
+    :param u:     the current control input
+    :param RFID:  the true position of the landmarks
+
+    :returns:     Computes the true position, observations, dead reckoning (noisy) position,
+                  and noisy control function
+    """
+    xTrue = motion_model(xTrue, u)
+
+    # add noise to gps x-y
+    z = np.zeros((0, 3))
+
+    for i in range(len(RFID[:, 0])): # Test all beacons, only add the ones we can see (within MAX_RANGE)
+
+        dx = RFID[i, 0] - xTrue[0, 0]
+        dy = RFID[i, 1] - xTrue[1, 0]
+        d = math.sqrt(dx**2 + dy**2)
+        angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0])
+        if d <= MAX_RANGE:
+            dn = d + np.random.randn() * Qsim[0, 0]  # add noise
+            anglen = angle + np.random.randn() * Qsim[1, 1]  # add noise
+            zi = np.array([dn, anglen, i])
+            z = np.vstack((z, zi))
+
+    # add noise to input
+    ud = np.array([[
+        u[0, 0] + np.random.randn() * Rsim[0, 0],
+        u[1, 0] + np.random.randn() * Rsim[1, 1]]]).T
+
+    xd = motion_model(xd, ud)
+    return xTrue, z, xd, ud
+
+
+
def calc_n_LM(x):
+    """
+    Calculates the number of landmarks currently tracked in the state
+    :param x: the state
+    :returns: the number of landmarks n
+    """
+    n = int((len(x) - STATE_SIZE) / LM_SIZE)
+    return n
+
+
+def jacob_motion(x, u):
+    """
+    Calculates the jacobian of motion model.
+
+    :param x: The state, including the estimated position of the system
+    :param u: The control function
+    :returns: G:  Jacobian
+              Fx: STATE_SIZE x (STATE_SIZE + 2 * num_landmarks) matrix where the left side is an identity matrix
+    """
+
+    # [eye(3) [0 x y; 0 x y; 0 x y]]
+    Fx = np.hstack((np.eye(STATE_SIZE), np.zeros(
+        (STATE_SIZE, LM_SIZE * calc_n_LM(x)))))
+
+    jF = np.array([[0.0, 0.0, -DT * u[0] * math.sin(x[2, 0])],
+                   [0.0, 0.0, DT * u[0] * math.cos(x[2, 0])],
+                   [0.0, 0.0, 0.0]],dtype=object)
+
+    G = np.eye(STATE_SIZE) + Fx.T @ jF @ Fx
+    if calc_n_LM(x) > 0:
+        print(Fx.shape)
+    return G, Fx,
+
+
+
def calc_LM_Pos(x, z):
+    """
+    Calculates the pose in the world coordinate frame of a landmark at the given measurement.
+
+    :param x: [x; y; theta]
+    :param z: [range; bearing]
+    :returns: [x; y] for given measurement
+    """
+    zp = np.zeros((2, 1))
+
+    zp[0, 0] = x[0, 0] + z[0] * math.cos(x[2, 0] + z[1])
+    zp[1, 0] = x[1, 0] + z[0] * math.sin(x[2, 0] + z[1])
+    #zp[0, 0] = x[0, 0] + z[0, 0] * math.cos(x[2, 0] + z[0, 1])
+    #zp[1, 0] = x[1, 0] + z[0, 0] * math.sin(x[2, 0] + z[0, 1])
+
+    return zp
+
+
+def get_LM_Pos_from_state(x, ind):
+    """
+    Returns the position of a given landmark
+
+    :param x:   The state containing all landmark positions
+    :param ind: landmark id
+    :returns:   The position of the landmark
+    """
+    lm = x[STATE_SIZE + LM_SIZE * ind: STATE_SIZE + LM_SIZE * (ind + 1), :]
+
+    return lm
+
+
+def search_correspond_LM_ID(xAug, PAug, zi):
+    """
+    Landmark association with Mahalanobis distance.
+
+    If this landmark is at least M_DIST_TH units away from all known landmarks,
+    it is a NEW landmark.
+
+    :param xAug: The estimated state
+    :param PAug: The estimated covariance
+    :param zi:   the read measurements of specific landmark
+    :returns:    landmark id
+    """
+
+    nLM = calc_n_LM(xAug)
+
+    mdist = []
+
+    for i in range(nLM):
+        lm = get_LM_Pos_from_state(xAug, i)
+        y, S, H = calc_innovation(lm, xAug, PAug, zi, i)
+        mdist.append(y.T @ np.linalg.inv(S) @ y)
+
+    mdist.append(M_DIST_TH)  # new landmark
+
+    minid = mdist.index(min(mdist))
+
+    return minid
+
+def calc_input():
+    v = 1.0  # [m/s]
+    yawrate = 0.1  # [rad/s]
+    u = np.array([[v, yawrate]]).T
+    return u
+
+def pi_2_pi(angle):
+    return (angle + math.pi) % (2 * math.pi) - math.pi
+
+
+
def main():
+    print(" start!!")
+
+    time = 0.0
+
+    # RFID positions [x, y]
+    RFID = np.array([[10.0, -2.0],
+                     [15.0, 10.0],
+                     [3.0, 15.0],
+                     [-5.0, 20.0]])
+
+    # State Vector [x y yaw v]'
+    xEst = np.zeros((STATE_SIZE, 1))
+    xTrue = np.zeros((STATE_SIZE, 1))
+    PEst = np.eye(STATE_SIZE)
+
+    xDR = np.zeros((STATE_SIZE, 1))  # Dead reckoning
+
+    # history
+    hxEst = xEst
+    hxTrue = xTrue
+    hxDR = xTrue
+
+    while SIM_TIME >= time:
+        time += DT
+        u = calc_input()
+
+        xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)
+
+        xEst, PEst = ekf_slam(xEst, PEst, ud, z)
+
+        x_state = xEst[0:STATE_SIZE]
+
+        # store data history
+        hxEst = np.hstack((hxEst, x_state))
+        hxDR = np.hstack((hxDR, xDR))
+        hxTrue = np.hstack((hxTrue, xTrue))
+
+        if show_animation:  # pragma: no cover
+            plt.cla()
+
+            plt.plot(RFID[:, 0], RFID[:, 1], "*k")
+            plt.plot(xEst[0], xEst[1], ".r")
+
+            # plot landmark
+            for i in range(calc_n_LM(xEst)):
+                plt.plot(xEst[STATE_SIZE + i * 2],
+                         xEst[STATE_SIZE + i * 2 + 1], "xg")
+
+            plt.plot(hxTrue[0, :],
+                     hxTrue[1, :], "-b")
+            plt.plot(hxDR[0, :],
+                     hxDR[1, :], "-k")
+            plt.plot(hxEst[0, :],
+                     hxEst[1, :], "-r")
+            plt.axis("equal")
+            plt.grid(True)
+            plt.pause(0.001)
+
+
+
%matplotlib notebook
+main()
+
+
+
 start!!
+New LM
+New LM
+New LM
+
+
+../../../_images/ekf_slam_1_0.png +
+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/graph_slam/graph_slam.html b/modules/slam/graph_slam/graph_slam.html new file mode 100644 index 0000000000..5ce2f4dd68 --- /dev/null +++ b/modules/slam/graph_slam/graph_slam.html @@ -0,0 +1,1025 @@ + + + + + + + Graph based SLAM — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Graph based SLAM

+

This is a graph based SLAM example.

+

The blue line is ground truth.

+

The black line is dead reckoning.

+

The red line is the estimated trajectory with Graph based SLAM.

+

The black stars are landmarks for graph edge generation.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/GraphBasedSLAM/animation.gif +
+

Graph SLAM

+
import copy
+import math
+import itertools
+import numpy as np
+import matplotlib.pyplot as plt
+from graph_based_slam import calc_rotational_matrix, calc_jacobian, cal_observation_sigma, \
+                             calc_input, observation, motion_model, Edge, pi_2_pi
+
+%matplotlib inline
+np.set_printoptions(precision=3, suppress=True)
+np.random.seed(0)
+
+
+
+

Introduction

+

In contrast to the probabilistic approaches for solving SLAM, such as +EKF, UKF, particle filters, and so on, the graph technique formulates +the SLAM as an optimization problem. It is mostly used to solve the full +SLAM problem in an offline fashion, i.e. optimize all the poses of the +robot after the path has been traversed. However, some variants are +available that uses graph-based approaches to perform online estimation +or to solve for a subset of the poses.

+

GraphSLAM uses the motion information as well as the observations of the +environment to create least square problem that can be solved using +standard optimization techniques.

+
+
+

Minimal Example

+

The following example illustrates the main idea behind graphSLAM. A +simple case of a 1D robot is considered that can only move in 1 +direction. The robot is commanded to move forward with a control input +\(u_t=1\), however, the motion is not perfect and the measured +odometry will deviate from the true path. At each time step the robot can +observe its environment, for this simple case as well, there is only a +single landmark at coordinates \(x=3\). The measured observations +are the range between the robot and landmark. These measurements are +also subjected to noise. No bearing information is required since this +is a 1D problem.

+

To solve this, graphSLAM creates what is called as the system +information matrix \(\Omega\) also referred to as \(H\) and the +information vector \(\xi\) also known as \(b\). The entries are +created based on the information of the motion and the observation.

+
R = 0.2
+Q = 0.2
+N = 3
+graphics_radius = 0.1
+
+odom = np.empty((N,1))
+obs = np.empty((N,1))
+x_true = np.empty((N,1))
+
+landmark = 3
+# Simulated readings of odometry and observations
+x_true[0], odom[0], obs[0] = 0.0, 0.0, 2.9
+x_true[1], odom[1], obs[1] = 1.0, 1.5, 2.0
+x_true[2], odom[2], obs[2] = 2.0, 2.4, 1.0
+
+hxDR = copy.deepcopy(odom)
+# Visualization
+plt.plot(landmark,0, '*k', markersize=30)
+for i in range(N):
+    plt.plot(odom[i], 0, '.', markersize=50, alpha=0.8, color='steelblue')
+    plt.plot([odom[i], odom[i] + graphics_radius],
+             [0,0], 'r')
+    plt.text(odom[i], 0.02, "X_{}".format(i), fontsize=12)
+    plt.plot(obs[i]+odom[i],0,'.', markersize=25, color='brown')
+    plt.plot(x_true[i],0,'.g', markersize=20)
+plt.grid()
+plt.show()
+
+
+# Defined as a function to facilitate iteration
+def get_H_b(odom, obs):
+    """
+    Create the information matrix and information vector. This implementation is
+    based on the concept of virtual measurement i.e. the observations of the
+    landmarks are converted into constraints (edges) between the nodes that
+    have observed this landmark.
+    """
+    measure_constraints = {}
+    omegas = {}
+    zids = list(itertools.combinations(range(N),2))
+    H = np.zeros((N,N))
+    b = np.zeros((N,1))
+    for (t1, t2) in zids:
+        x1 = odom[t1]
+        x2 = odom[t2]
+        z1 = obs[t1]
+        z2 = obs[t2]
+
+        # Adding virtual measurement constraint
+        measure_constraints[(t1,t2)] = (x2-x1-z1+z2)
+        omegas[(t1,t2)] = (1 / (2*Q))
+
+        # populate system's information matrix and vector
+        H[t1,t1] += omegas[(t1,t2)]
+        H[t2,t2] += omegas[(t1,t2)]
+        H[t2,t1] -= omegas[(t1,t2)]
+        H[t1,t2] -= omegas[(t1,t2)]
+
+        b[t1] += omegas[(t1,t2)] * measure_constraints[(t1,t2)]
+        b[t2] -= omegas[(t1,t2)] * measure_constraints[(t1,t2)]
+
+    return H, b
+
+
+H, b = get_H_b(odom, obs)
+print("The determinant of H: ", np.linalg.det(H))
+H[0,0] += 1 # np.inf ?
+print("The determinant of H after anchoring constraint: ", np.linalg.det(H))
+
+for i in range(5):
+    H, b = get_H_b(odom, obs)
+    H[(0,0)] += 1
+
+    # Recover the posterior over the path
+    dx = np.linalg.inv(H) @ b
+    odom += dx
+    # repeat till convergence
+print("Running graphSLAM ...")
+print("Odometry values after optimzation: \n", odom)
+
+plt.figure()
+plt.plot(x_true, np.zeros(x_true.shape), '.', markersize=20, label='Ground truth')
+plt.plot(odom, np.zeros(x_true.shape), '.', markersize=20, label='Estimation')
+plt.plot(hxDR, np.zeros(x_true.shape), '.', markersize=20, label='Odom')
+plt.legend()
+plt.grid()
+plt.show()
+
+
+../../../_images/graphSLAM_doc_2_0.png +
The determinant of H:  0.0
+The determinant of H after anchoring constraint:  18.750000000000007
+Running graphSLAM ...
+Odometry values after optimzation:
+ [[-0. ]
+ [ 0.9]
+ [ 1.9]]
+
+
+../../../_images/graphSLAM_doc_2_2.png +

In particular, the tasks are split into 2 parts, graph construction, and +graph optimization. ### Graph Construction

+

Firstly the nodes are defined +\(\boldsymbol{x} = \boldsymbol{x}_{1:n}\) such that each node is the +pose of the robot at time \(t_i\) Secondly, the edges i.e. the +constraints, are constructed according to the following conditions:

+
    +
  • robot moves from \(\boldsymbol{x}_i\) to +\(\boldsymbol{x}_j\). This edge corresponds to the odometry +measurement. Relative motion constraints (Not included in the +previous minimal example).

  • +
  • Measurement constraints, this can be done in two ways:

    +
      +
    • The information matrix is set in such a way that it includes the +landmarks in the map as well. Then the constraints can be entered +in a straightforward fashion between a node +\(\boldsymbol{x}_i\) and some landmark \(m_k\)

    • +
    • Through creating a virtual measurement among all the node that +have observed the same landmark. More concretely, robot observes +the same landmark from \(\boldsymbol{x}_i\) and +\(\boldsymbol{x}_j\). Relative measurement constraint. The +“virtual measurement” \(\boldsymbol{z}_{ij}\), which is the +estimated pose of \(\boldsymbol{x}_j\) as seen from the node +\(\boldsymbol{x}_i\). The virtual measurement can then be +entered in the information matrix and vector in a similar fashion +to the motion constraints.

    • +
    +
  • +
+

An edge is fully characterized by the values of the error (entry to +information vector) and the local information matrix (entry to the +system’s information vector). The larger the local information matrix +(lower \(Q\) or \(R\)) the values that this edge will contribute +with.

+

Important Notes:

+
    +
  • The addition to the information matrix and vector are added to the +earlier values.

  • +
  • In the case of 2D robot, the constraints will be non-linear, +therefore, a Jacobian of the error w.r.t the states is needed when +updated \(H\) and \(b\).

  • +
  • The anchoring constraint is needed in order to avoid having a +singular information matri.

  • +
+
+
+

Graph Optimization

+

The result from this formulation yields an overdetermined system of +equations. The goal after constructing the graph is to find: +\(x^*=\underset{x}{\mathrm{argmin}}~\underset{ij}\Sigma~f(e_{ij})\), +where \(f\) is some error function that depends on the edges between +to related nodes \(i\) and \(j\). The derivation in the references +arrive at the solution for \(x^* = H^{-1}b\)

+
+
+

Planar Example:

+

Now we will go through an example with a more realistic case of a 2D +robot with 3DoF, namely, \([x, y, \theta]^T\)

+
#  Simulation parameter
+Qsim = np.diag([0.01, np.deg2rad(0.010)])**2 # error added to range and bearing
+Rsim = np.diag([0.1, np.deg2rad(1.0)])**2 # error added to [v, w]
+
+DT = 2.0  # time tick [s]
+SIM_TIME = 100.0  # simulation time [s]
+MAX_RANGE = 30.0  # maximum observation range
+STATE_SIZE = 3  # State size [x,y,yaw]
+
+# TODO: Why not use Qsim ?
+# Covariance parameter of Graph Based SLAM
+C_SIGMA1 = 0.1
+C_SIGMA2 = 0.1
+C_SIGMA3 = np.deg2rad(1.0)
+
+MAX_ITR = 20  # Maximum iteration during optimization
+timesteps = 1
+
+# consider only 2 landmarks for simplicity
+# RFID positions [x, y, yaw]
+RFID = np.array([[10.0, -2.0, 0.0],
+#                  [15.0, 10.0, 0.0],
+#                  [3.0, 15.0, 0.0],
+#                  [-5.0, 20.0, 0.0],
+#                  [-5.0, 5.0, 0.0]
+                 ])
+
+# State Vector [x y yaw v]'
+xTrue = np.zeros((STATE_SIZE, 1))
+xDR = np.zeros((STATE_SIZE, 1))  # Dead reckoning
+xTrue[2] = np.deg2rad(45)
+xDR[2] = np.deg2rad(45)
+# history initial values
+hxTrue = xTrue
+hxDR = xTrue
+_, z, _, _ = observation(xTrue, xDR, np.array([[0,0]]).T, RFID)
+hz = [z]
+
+for i in range(timesteps):
+    u = calc_input()
+    xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)
+    hxDR = np.hstack((hxDR, xDR))
+    hxTrue = np.hstack((hxTrue, xTrue))
+    hz.append(z)
+
+# visualize
+graphics_radius = 0.3
+plt.plot(RFID[:, 0], RFID[:, 1], "*k", markersize=20)
+plt.plot(hxDR[0, :], hxDR[1, :], '.', markersize=50, alpha=0.8, label='Odom')
+plt.plot(hxTrue[0, :], hxTrue[1, :], '.', markersize=20, alpha=0.6, label='X_true')
+
+for i in range(hxDR.shape[1]):
+    x = hxDR[0, i]
+    y = hxDR[1, i]
+    yaw = hxDR[2, i]
+    plt.plot([x, x + graphics_radius * np.cos(yaw)],
+             [y, y + graphics_radius * np.sin(yaw)], 'r')
+    d = hz[i][:, 0]
+    angle = hz[i][:, 1]
+    plt.plot([x + d * np.cos(angle + yaw)], [y + d * np.sin(angle + yaw)], '.',
+             markersize=20, alpha=0.7)
+    plt.legend()
+plt.grid()
+plt.show()
+
+
+../../../_images/graphSLAM_doc_4_0.png +
# Copy the data to have a consistent naming with the .py file
+zlist = copy.deepcopy(hz)
+x_opt = copy.deepcopy(hxDR)
+xlist = copy.deepcopy(hxDR)
+number_of_nodes = x_opt.shape[1]
+n = number_of_nodes * STATE_SIZE
+
+
+

After the data has been saved, the graph will be constructed by looking +at each pair for nodes. The virtual measurement is only created if two +nodes have observed the same landmark at different points in time. The +next cells are a walk through for a single iteration of graph +construction -> optimization -> estimate update.

+
# get all the possible combination of the different node
+zids = list(itertools.combinations(range(len(zlist)), 2))
+print("Node combinations: \n", zids)
+
+for i in range(xlist.shape[1]):
+    print("Node {} observed landmark with ID {}".format(i, zlist[i][0, 3]))
+
+
+
Node combinations:
+ [(0, 1)]
+Node 0 observed landmark with ID 0.0
+Node 1 observed landmark with ID 0.0
+
+
+

In the following code snippet the error based on the virtual measurement +between node 0 and 1 will be created. The equations for the error is as follows: +\(e_{ij}^x = x_j + d_j cos(\psi_j +\theta_j) - x_i - d_i cos(\psi_i + \theta_i)\)

+

\(e_{ij}^y = y_j + d_j sin(\psi_j + \theta_j) - y_i - d_i sin(\psi_i + \theta_i)\)

+

\(e_{ij}^x = \psi_j + \theta_j - \psi_i - \theta_i\)

+

Where \([x_i, y_i, \psi_i]\) is the pose for node \(i\) and +similarly for node \(j\), \(d\) is the measured distance at +nodes \(i\) and \(j\), and \(\theta\) is the measured +bearing to the landmark. The difference is visualized with the figure in +the next cell.

+

In case of perfect motion and perfect measurement the error shall be +zero since \(x_j + d_j cos(\psi_j + \theta_j)\) should equal +\(x_i + d_i cos(\psi_i + \theta_i)\)

+
# Initialize edges list
+edges = []
+
+# Go through all the different combinations
+for (t1, t2) in zids:
+    x1, y1, yaw1 = xlist[0, t1], xlist[1, t1], xlist[2, t1]
+    x2, y2, yaw2 = xlist[0, t2], xlist[1, t2], xlist[2, t2]
+
+    # All nodes have valid observation with ID=0, therefore, no data association condition
+    iz1 = 0
+    iz2 = 0
+
+    d1 = zlist[t1][iz1, 0]
+    angle1, phi1 = zlist[t1][iz1, 1], zlist[t1][iz1, 2]
+    d2 = zlist[t2][iz2, 0]
+    angle2, phi2 = zlist[t2][iz2, 1], zlist[t2][iz2, 2]
+
+    # find angle between observation and horizontal
+    tangle1 = pi_2_pi(yaw1 + angle1)
+    tangle2 = pi_2_pi(yaw2 + angle2)
+
+    # project the observations
+    tmp1 = d1 * math.cos(tangle1)
+    tmp2 = d2 * math.cos(tangle2)
+    tmp3 = d1 * math.sin(tangle1)
+    tmp4 = d2 * math.sin(tangle2)
+
+    edge = Edge()
+    print(y1,y2, tmp3, tmp4)
+    # calculate the error of the virtual measurement
+    # node 1 as seen from node 2 throught the observations 1,2
+    edge.e[0, 0] = x2 - x1 - tmp1 + tmp2
+    edge.e[1, 0] = y2 - y1 - tmp3 + tmp4
+    edge.e[2, 0] = pi_2_pi(yaw2 - yaw1 - tangle1 + tangle2)
+
+    edge.d1, edge.d2 = d1, d2
+    edge.yaw1, edge.yaw2 = yaw1, yaw2
+    edge.angle1, edge.angle2 = angle1, angle2
+    edge.id1, edge.id2 = t1, t2
+
+    edges.append(edge)
+
+    print("For nodes",(t1,t2))
+    print("Added edge with errors: \n", edge.e)
+
+    # Visualize measurement projections
+    plt.plot(RFID[0, 0], RFID[0, 1], "*k", markersize=20)
+    plt.plot([x1,x2],[y1,y2], '.', markersize=50, alpha=0.8)
+    plt.plot([x1, x1 + graphics_radius * np.cos(yaw1)],
+             [y1, y1 + graphics_radius * np.sin(yaw1)], 'r')
+    plt.plot([x2, x2 + graphics_radius * np.cos(yaw2)],
+             [y2, y2 + graphics_radius * np.sin(yaw2)], 'r')
+
+    plt.plot([x1,x1+tmp1], [y1,y1], label="obs 1 x")
+    plt.plot([x2,x2+tmp2], [y2,y2], label="obs 2 x")
+    plt.plot([x1,x1], [y1,y1+tmp3], label="obs 1 y")
+    plt.plot([x2,x2], [y2,y2+tmp4], label="obs 2 y")
+    plt.plot(x1+tmp1, y1+tmp3, 'o')
+    plt.plot(x2+tmp2, y2+tmp4, 'o')
+plt.legend()
+plt.grid()
+plt.show()
+
+
+
0.0 1.427649841628278 -2.0126109674819155 -3.524048014922737
+For nodes (0, 1)
+Added edge with errors:
+ [[-0.02 ]
+ [-0.084]
+ [ 0.   ]]
+
+
+../../../_images/graphSLAM_doc_9_1.png +

Since the constraints equations derived before are non-linear, +linearization is needed before we can insert them into the information +matrix and information vector. Two jacobians

+

\(A = \frac{\partial e_{ij}}{\partial \boldsymbol{x}_i}\) as +\(\boldsymbol{x}_i\) holds the three variabls x, y, and theta. +Similarly, \(B = \frac{\partial e_{ij}}{\partial \boldsymbol{x}_j}\)

+
# Initialize the system information matrix and information vector
+H = np.zeros((n, n))
+b = np.zeros((n, 1))
+x_opt = copy.deepcopy(hxDR)
+
+for edge in edges:
+    id1 = edge.id1 * STATE_SIZE
+    id2 = edge.id2 * STATE_SIZE
+
+    t1 = edge.yaw1 + edge.angle1
+    A = np.array([[-1.0, 0, edge.d1 * math.sin(t1)],
+                  [0, -1.0, -edge.d1 * math.cos(t1)],
+                  [0, 0, -1.0]])
+
+    t2 = edge.yaw2 + edge.angle2
+    B = np.array([[1.0, 0, -edge.d2 * math.sin(t2)],
+                  [0, 1.0, edge.d2 * math.cos(t2)],
+                  [0, 0, 1.0]])
+
+    # TODO: use Qsim instead of sigma
+    sigma = np.diag([C_SIGMA1, C_SIGMA2, C_SIGMA3])
+    Rt1 = calc_rotational_matrix(tangle1)
+    Rt2 = calc_rotational_matrix(tangle2)
+    edge.omega = np.linalg.inv(Rt1 @ sigma @ Rt1.T + Rt2 @ sigma @ Rt2.T)
+
+    # Fill in entries in H and b
+    H[id1:id1 + STATE_SIZE, id1:id1 + STATE_SIZE] += A.T @ edge.omega @ A
+    H[id1:id1 + STATE_SIZE, id2:id2 + STATE_SIZE] += A.T @ edge.omega @ B
+    H[id2:id2 + STATE_SIZE, id1:id1 + STATE_SIZE] += B.T @ edge.omega @ A
+    H[id2:id2 + STATE_SIZE, id2:id2 + STATE_SIZE] += B.T @ edge.omega @ B
+
+    b[id1:id1 + STATE_SIZE] += (A.T @ edge.omega @ edge.e)
+    b[id2:id2 + STATE_SIZE] += (B.T @ edge.omega @ edge.e)
+
+
+print("The determinant of H: ", np.linalg.det(H))
+plt.figure()
+plt.subplot(1,2,1)
+plt.imshow(H, extent=[0, n, 0, n])
+plt.subplot(1,2,2)
+plt.imshow(b, extent=[0, 1, 0, n])
+plt.show()
+
+# Fix the origin, multiply by large number gives same results but better visualization
+H[0:STATE_SIZE, 0:STATE_SIZE] += np.identity(STATE_SIZE)
+print("The determinant of H after origin constraint: ", np.linalg.det(H))
+plt.figure()
+plt.subplot(1,2,1)
+plt.imshow(H, extent=[0, n, 0, n])
+plt.subplot(1,2,2)
+plt.imshow(b, extent=[0, 1, 0, n])
+plt.show()
+
+
+
The determinant of H:  0.0
+The determinant of H after origin constraint:  716.1972439134893
+
+
+../../../_images/graphSLAM_doc_11_1.png +../../../_images/graphSLAM_doc_11_2.png +
# Find the solution (first iteration)
+dx = - np.linalg.inv(H) @ b
+for i in range(number_of_nodes):
+    x_opt[0:3, i] += dx[i * 3:i * 3 + 3, 0]
+print("dx: \n",dx)
+print("ground truth: \n ",hxTrue)
+print("Odom: \n", hxDR)
+print("SLAM: \n", x_opt)
+
+# performance will improve with more iterations, nodes and landmarks.
+print("\ngraphSLAM localization error: ", np.sum((x_opt - hxTrue) ** 2))
+print("Odom localization error: ", np.sum((hxDR - hxTrue) ** 2))
+
+
+
dx:
+ [[-0.   ]
+ [-0.   ]
+ [ 0.   ]
+ [ 0.02 ]
+ [ 0.084]
+ [-0.   ]]
+ground truth:
+  [[0.    1.414]
+ [0.    1.414]
+ [0.785 0.985]]
+Odom:
+ [[0.    1.428]
+ [0.    1.428]
+ [0.785 0.976]]
+SLAM:
+ [[-0.     1.448]
+ [-0.     1.512]
+ [ 0.785  0.976]]
+
+graphSLAM localization error:  0.010729072751057656
+Odom localization error:  0.0004460978857535104
+
+
+
+
+

The references:

+ +

N.B. An additional step is required that uses the estimated path to +update the belief regarding the map.

+
+
+
+

Graph SLAM Formulation

+

Author Jeff Irion

+
+

Problem Formulation

+

Let a robot’s trajectory through its environment be represented by a +sequence of \(N\) poses: +\(\mathbf{p}_1, \mathbf{p}_2, \ldots, \mathbf{p}_N\). Each pose lies +on a manifold: \(\mathbf{p}_i \in \mathcal{M}\). Simple examples of +manifolds used in Graph SLAM include 1-D, 2-D, and 3-D space, i.e., +\(\mathbb{R}\), \(\mathbb{R}^2\), and \(\mathbb{R}^3\). +These environments are rectilinear, meaning that there is no concept +of orientation. By contrast, in \(SE(2)\) problem settings a robot’s +pose consists of its location in \(\mathbb{R}^2\) and its +orientation \(\theta\). Similarly, in \(SE(3)\) a robot’s pose +consists of its location in \(\mathbb{R}^3\) and its orientation, +which can be represented via Euler angles, quaternions, or \(SO(3)\) +rotation matrices.

+

As the robot explores its environment, it collects a set of \(M\) +measurements \(\mathcal{Z} = \{\mathbf{z}_j\}\). Examples of such +measurements include odometry, GPS, and IMU data. Given a set of poses +\(\mathbf{p}_1, \ldots, \mathbf{p}_N\), we can compute the estimated +measurement +\(\hat{\mathbf{z}}_j(\mathbf{p}_1, \ldots, \mathbf{p}_N)\). We can +then compute the residual +\(\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j)\) for measurement +\(j\). The formula for the residual depends on the type of +measurement. As an example, let \(\mathbf{z}_1\) be an odometry +measurement that was collected when the robot traveled from +\(\mathbf{p}_1\) to \(\mathbf{p}_2\). The expected measurement +and the residual are computed as

+
+\[\begin{split}\begin{aligned} + \hat{\mathbf{z}}_1(\mathbf{p}_1, \mathbf{p}_2) &= \mathbf{p}_2 \ominus \mathbf{p}_1 \\ + \mathbf{e}_1(\mathbf{z}_1, \hat{\mathbf{z}}_1) &= \mathbf{z}_1 \ominus \hat{\mathbf{z}}_1 = \mathbf{z}_1 \ominus (\mathbf{p}_2 \ominus \mathbf{p}_1),\end{aligned}\end{split}\]
+

where the \(\ominus\) operator indicates inverse pose composition. +We model measurement \(\mathbf{z}_j\) as having independent Gaussian +noise with zero mean and covariance matrix \(\Omega_j^{-1}\); we +refer to \(\Omega_j\) as the information matrix for measurement +\(j\). That is,

+
+(1)\[p(\mathbf{z}_j \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) = \eta_j \exp (-\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\mathsf{T}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j)\]
+

where \(\eta_j\) is the normalization constant.

+

The objective of Graph SLAM is to find the maximum likelihood set of +poses given the measurements \(\mathcal{Z} = \{\mathbf{z}_j\}\); in +other words, we want to find

+
+\[\mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z})\]
+

Using Bayes’ rule, we can write this probability as

+
+(2)\[\begin{split}\begin{aligned} + p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z}) &= \frac{p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) p(\mathbf{p}_1, \ldots, \mathbf{p}_N) }{ p(\mathcal{Z}) } \notag \\ + &\propto p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) +\end{aligned}\end{split}\]
+

since \(p(\mathcal{Z})\) is a constant (albeit, an unknown constant) +and we assume that \(p(\mathbf{p}_1, \ldots, \mathbf{p}_N)\) is +uniformly distributed. Therefore, we +can use Eq. (1) and and Eq. (2) to simplify the Graph SLAM +optimization as follows:

+
+\[\begin{split}\begin{aligned} + \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p(\mathbf{p}_1, \ldots, \mathbf{p}_N \ | \ \mathcal{Z}) &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \ p( \mathcal{Z} \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) \\ + &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \prod_{j=1}^M p(\mathbf{z}_j \ | \ \mathbf{p}_1, \ldots, \mathbf{p}_N) \\ + &= \mathop{\mathrm{arg\,max}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \prod_{j=1}^M \exp \left( -(\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j) \right) \\ + &= \mathop{\mathrm{arg\,min}}_{\mathbf{p}_1, \ldots, \mathbf{p}_N} \sum_{j=1}^M (\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j).\end{aligned}\end{split}\]
+

We define

+
+\[\chi^2 := \sum_{j=1}^M (\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j))^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j),\]
+

and this is what we seek to minimize.

+
+
+

Dimensionality and Pose Representation

+

Before proceeding further, it is helpful to discuss the dimensionality +of the problem. We have:

+
    +
  • A set of \(N\) poses +\(\mathbf{p}_1, \mathbf{p}_2, \ldots, \mathbf{p}_N\), where each +pose lies on the manifold \(\mathcal{M}\)

    +
      +
    • Each pose \(\mathbf{p}_i\) is represented as a vector in (a +subset of) \(\mathbb{R}^d\). For example:

      +
        +
      • An \(SE(2)\) pose is typically represented as +\((x, y, \theta)\), and thus \(d = 3\).

      • +
      • An \(SE(3)\) pose is typically represented as +\((x, y, z, q_x, q_y, q_z, q_w)\), where \((x, y, z)\) +is a point in \(\mathbb{R}^3\) and +\((q_x, q_y, q_z, q_w)\) is a quaternion, and so +\(d = 7\). For more information about \(SE(3)\) +parameterization and pose transformations, see +[blanco2010tutorial].

      • +
      +
    • +
    • We also need to be able to represent each pose compactly as a +vector in (a subset of) \(\mathbb{R}^c\).

      +
        +
      • Since an \(SE(2)\) pose has three degrees of freedom, the +\((x, y, \theta)\) representation is again sufficient and +\(c=3\).

      • +
      • An \(SE(3)\) pose only has six degrees of freedom, and we +can represent it compactly as \((x, y, z, q_x, q_y, q_z)\), +and thus \(c=6\).

      • +
      +
    • +
    • We use the \(\boxplus\) operator to indicate pose composition +when one or both of the poses are represented compactly. The +output can be a pose in \(\mathcal{M}\) or a vector in +\(\mathbb{R}^c\), as required by context.

    • +
    +
  • +
  • A set of \(M\) measurements +\(\mathcal{Z} = \{\mathbf{z}_1, \mathbf{z}_2, \ldots, \mathbf{z}_M\}\)

    +
      +
    • Each measurement’s dimensionality can be unique, and we will use +\(\bullet\) to denote a “wildcard” variable.

    • +
    • Measurement \(\mathbf{z}_j \in \mathbb{R}^\bullet\) has an +associated information matrix +\(\Omega_j \in \mathbb{R}^{\bullet \times \bullet}\) and +residual function +\(\mathbf{e}_j(\mathbf{z}_j, \hat{\mathbf{z}}_j) = \mathbf{e}_j(\mathbf{z}_j, \mathbf{p}_1, \ldots, \mathbf{p}_N) \in \mathbb{R}^\bullet\).

    • +
    • A measurement could, in theory, constrain anywhere from 1 pose to +all \(N\) poses. In practice, each measurement usually +constrains only 1 or 2 poses.

    • +
    +
  • +
+
+
+

Graph SLAM Algorithm

+

The “Graph” in Graph SLAM refers to the fact that we view the problem as +a graph. The graph has a set \(\mathcal{V}\) of \(N\) vertices, +where each vertex \(v_i\) has an associated pose +\(\mathbf{p}_i\). Similarly, the graph has a set \(\mathcal{E}\) +of \(M\) edges, where each edge \(e_j\) has an associated +measurement \(\mathbf{z}_j\). In practice, the edges in this graph +are either unary (i.e., a loop) or binary. (Note: \(e_j\) refers to +the edge in the graph associated with measurement \(\mathbf{z}_j\), +whereas \(\mathbf{e}_j\) refers to the residual function associated +with \(\mathbf{z}_j\).) For more information about the Graph SLAM +algorithm, see [grisetti2010tutorial].

+

We want to optimize

+
+\[\chi^2 = \sum_{e_j \in \mathcal{E}} \mathbf{e}_j^{\scriptstyle{\mathsf{T}}}\Omega_j \mathbf{e}_j.\]
+

Let \(\mathbf{x}_i \in \mathbb{R}^c\) be the compact representation +of pose \(\mathbf{p}_i \in \mathcal{M}\), and let

+
+\[\begin{split}\mathbf{x} := \begin{bmatrix} \mathbf{x}_1 \\ \mathbf{x}_2 \\ \vdots \\ \mathbf{x}_N \end{bmatrix} \in \mathbb{R}^{cN}\end{split}\]
+

We will solve this optimization problem iteratively. Let

+
+(3)\[\begin{split}\mathbf{x}^{k+1} := \mathbf{x}^k \boxplus \Delta \mathbf{x}^k = \begin{bmatrix} \mathbf{x}_1 \boxplus \Delta \mathbf{x}_1 \\ \mathbf{x}_2 \boxplus \Delta \mathbf{x}_2 \\ \vdots \\ \mathbf{x}_N \boxplus \Delta \mathbf{x}_2 \end{bmatrix}\end{split}\]
+

The \(\chi^2\) error at iteration \(k+1\) is

+
+(4)\[\chi_{k+1}^2 = \sum_{e_j \in \mathcal{E}} \underbrace{\left[ \mathbf{e}_j(\mathbf{x}^{k+1}) \right]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\mathbf{e}_j(\mathbf{x}^{k+1})}_{\bullet \times 1}.\]
+

We will linearize the residuals as:

+
+(5)\[\begin{split}\begin{aligned} + \mathbf{e}_j(\mathbf{x}^{k+1}) &= \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \\ + &\approx \mathbf{e}_j(\mathbf{x}^{k}) + \frac{\partial}{\partial \Delta \mathbf{x}^k} \left[ \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \right] \Delta \mathbf{x}^k \\ + &= \mathbf{e}_j(\mathbf{x}^{k}) + \left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right) \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \Delta \mathbf{x}^k. +\end{aligned}\end{split}\]
+

Plugging (5) into (4), we get:

+
+\[\begin{split}\begin{aligned} + \chi_{k+1}^2 &\approx \ \ \ \ \ \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x}^k)]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\mathbf{e}_j(\mathbf{x}^k)}_{\bullet \times 1} \notag \\ + &\hphantom{\approx} \ \ \ + \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x^k}) ]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \underbrace{\Delta \mathbf{x}^k}_{cN \times 1} \notag \\ + &\hphantom{\approx} \ \ \ + \sum_{e_j \in \mathcal{E}} \underbrace{(\Delta \mathbf{x}^k)^{\scriptstyle{\mathsf{T}}}}_{1 \times cN} \underbrace{ \left( \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \right)^{\scriptstyle{\mathsf{T}}}}_{cN \times dN} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)^{\scriptstyle{\mathsf{T}}}}_{dN \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \underbrace{\Delta \mathbf{x}^k}_{cN \times 1} \notag \\ + &= \chi_k^2 + 2 \mathbf{b}^{\scriptstyle{\mathsf{T}}}\Delta \mathbf{x}^k + (\Delta \mathbf{x}^k)^{\scriptstyle{\mathsf{T}}}H \Delta \mathbf{x}^k, \notag\end{aligned}\end{split}\]
+

where

+
+\[\begin{split}\begin{aligned} + \mathbf{b}^{\scriptstyle{\mathsf{T}}}&= \sum_{e_j \in \mathcal{E}} \underbrace{[ \mathbf{e}_j(\mathbf{x^k}) ]^{\scriptstyle{\mathsf{T}}}}_{1 \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN} \\ + H &= \sum_{e_j \in \mathcal{E}} \underbrace{ \left( \frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k} \right)^{\scriptstyle{\mathsf{T}}}}_{cN \times dN} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)^{\scriptstyle{\mathsf{T}}}}_{dN \times \bullet} \underbrace{\Omega_j}_{\bullet \times \bullet} \underbrace{\left( \left. \frac{\partial \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)} \right|_{\Delta \mathbf{x}^k = \mathbf{0}} \right)}_{\bullet \times dN} \underbrace{\frac{\partial (\mathbf{x}^k \boxplus \Delta \mathbf{x}^k)}{\partial \Delta \mathbf{x}^k}}_{dN \times cN}.\end{aligned}\end{split}\]
+

Using this notation, we obtain the optimal update as

+
+\[\Delta \mathbf{x}^k = -H^{-1} \mathbf{b}. \label{eq:deltax}\]
+

We apply this update to the poses via (3) and repeat until convergence.

+
+
blanco2010tutorial
+

Blanco, J.-L.A tutorial onSE(3) transformation parameterization and on-manifold optimization.University of Malaga, Tech. Rep 3(2010)

+
+
grisetti2010tutorial
+

Grisetti, G., Kummerle, R., Stachniss, C., and Burgard, W.A tutorial on graph-based SLAM.IEEE Intelligent Transportation Systems Magazine 2, 4 (2010), 31–43.

+
+
+
+
+
+

Graph SLAM for a real-world SE(2) dataset

+
from graphslam.graph import Graph
+from graphslam.load import load_g2o_se2
+
+
+
+

Introduction

+

For a complete derivation of the Graph SLAM algorithm, please see +Graph SLAM Formulation.

+

This notebook illustrates the iterative optimization of a real-world +\(SE(2)\) dataset. The code can be found in the graphslam +folder. For simplicity, numerical differentiation is used in lieu of +analytic Jacobians. This code originated from the +python-graphslam +repo, which is a full-featured Graph SLAM solver. The dataset in this +example is used with permission from Luca Carlone and was downloaded +from his website.

+
+
+

The Dataset

+
g = load_g2o_se2("data/input_INTEL.g2o")
+
+print("Number of edges:    {}".format(len(g._edges)))
+print("Number of vertices: {}".format(len(g._vertices)))
+
+
+
Number of edges:    1483
+Number of vertices: 1228
+
+
+
g.plot(title=r"Original ($\chi^2 = {:.0f}$)".format(g.calc_chi2()))
+
+
+../../../_images/graphSLAM_SE2_example_4_0.png +

Each edge in this dataset is a constraint that compares the measured +\(SE(2)\) transformation between two poses to the expected +\(SE(2)\) transformation between them, as computed using the current +pose estimates. These edges can be classified into two categories:

+
    +
  1. Odometry edges constrain two consecutive vertices, and the +measurement for the \(SE(2)\) transformation comes directly from +odometry data.

  2. +
  3. Scan-matching edges constrain two non-consecutive vertices. These +scan matches can be computed using, for example, 2-D LiDAR data or +landmarks; the details of how these constraints are determined is +beyond the scope of this example. This is often referred to as loop +closure in the Graph SLAM literature.

  4. +
+

We can easily parse out the two different types of edges present in this +dataset and plot them.

+
def parse_edges(g):
+    """Split the graph `g` into two graphs, one with only odometry edges and the other with only scan-matching edges.
+
+    Parameters
+    ----------
+    g : graphslam.graph.Graph
+        The input graph
+
+    Returns
+    -------
+    g_odom : graphslam.graph.Graph
+        A graph consisting of the vertices and odometry edges from `g`
+    g_scan : graphslam.graph.Graph
+        A graph consisting of the vertices and scan-matching edges from `g`
+
+    """
+    edges_odom = []
+    edges_scan = []
+
+    for e in g._edges:
+        if abs(e.vertex_ids[1] - e.vertex_ids[0]) == 1:
+            edges_odom.append(e)
+        else:
+            edges_scan.append(e)
+
+    g_odom = Graph(edges_odom, g._vertices)
+    g_scan = Graph(edges_scan, g._vertices)
+
+    return g_odom, g_scan
+
+
+
g_odom, g_scan = parse_edges(g)
+
+print("Number of odometry edges:      {:4d}".format(len(g_odom._edges)))
+print("Number of scan-matching edges: {:4d}".format(len(g_scan._edges)))
+
+print("\nχ^2 error from odometry edges:       {:11.3f}".format(g_odom.calc_chi2()))
+print("χ^2 error from scan-matching edges:  {:11.3f}".format(g_scan.calc_chi2()))
+
+
+
Number of odometry edges:      1227
+Number of scan-matching edges:  256
+
+χ^2 error from odometry edges:             0.232
+χ^2 error from scan-matching edges:  7191686.151
+
+
+
g_odom.plot(title="Odometry edges")
+
+
+../../../_images/graphSLAM_SE2_example_8_0.png +
g_scan.plot(title="Scan-matching edges")
+
+
+../../../_images/graphSLAM_SE2_example_9_0.png +
+
+

Optimization

+

Initially, the pose estimates are consistent with the collected odometry +measurements, and the odometry edges contribute almost zero towards the +\(\chi^2\) error. However, there are large discrepancies between the +scan-matching constraints and the initial pose estimates. This is not +surprising, since small errors in odometry readings that are propagated +over time can lead to large errors in the robot’s trajectory. What makes +Graph SLAM effective is that it allows incorporation of multiple +different data sources into a single optimization problem.

+
g.optimize()
+
+
+
Iteration                chi^2        rel. change
+---------                -----        -----------
+        0         7191686.3825
+        1       320031728.8624          43.500234
+        2       125083004.3299          -0.609154
+        3          338155.9074          -0.997297
+        4             735.1344          -0.997826
+        5             215.8405          -0.706393
+        6             215.8405          -0.000000
+
+
+
+../../../_images/Graph_SLAM_optimization.gif +
+
g.plot(title="Optimized")
+
+
+../../../_images/graphSLAM_SE2_example_13_0.png +
print("\nχ^2 error from odometry edges:       {:7.3f}".format(g_odom.calc_chi2()))
+print("χ^2 error from scan-matching edges:  {:7.3f}".format(g_scan.calc_chi2()))
+
+
+
χ^2 error from odometry edges:       142.189
+χ^2 error from scan-matching edges:   73.652
+
+
+
g_odom.plot(title="Odometry edges")
+
+
+../../../_images/graphSLAM_SE2_example_15_0.png +
g_scan.plot(title="Scan-matching edges")
+
+
+../../../_images/graphSLAM_SE2_example_16_0.png +
+
+
+

References:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching.html b/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching.html new file mode 100644 index 0000000000..b964247c12 --- /dev/null +++ b/modules/slam/iterative_closest_point_matching/iterative_closest_point_matching.html @@ -0,0 +1,162 @@ + + + + + + + Iterative Closest Point (ICP) Matching — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Iterative Closest Point (ICP) Matching

+

This is a 2D ICP matching example with singular value decomposition.

+

It can calculate a rotation matrix and a translation vector between +points to points.

+https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/iterative_closest_point/animation.gif +
+

References

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/slam/slam.html b/modules/slam/slam.html new file mode 100644 index 0000000000..e51e01beee --- /dev/null +++ b/modules/slam/slam.html @@ -0,0 +1,189 @@ + + + + + + + SLAM — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/utils/plot/plot.html b/modules/utils/plot/plot.html new file mode 100644 index 0000000000..28391f6176 --- /dev/null +++ b/modules/utils/plot/plot.html @@ -0,0 +1,176 @@ + + + + + + + Plotting Utilities — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Plotting Utilities

+

This module contains a number of utility functions for plotting data.

+
+

plot_curvature

+
+
+utils.plot.plot_curvature(x_list, y_list, heading_list, curvature, k=0.01, c='-c', label='Curvature')[source]
+

Plot curvature on 2D path. This plot is a line from the original path, +the lateral distance from the original path shows curvature magnitude. +Left turning shows right side plot, right turning shows left side plot. +For straight path, the curvature plot will be on the path, because +curvature is 0 on the straight path.

+
+
Parameters
+
    +
  • x_list (array_like) – x position list of the path

  • +
  • y_list (array_like) – y position list of the path

  • +
  • heading_list (array_like) – heading list of the path

  • +
  • curvature (array_like) – curvature list of the path

  • +
  • k (float) – curvature scale factor to calculate distance from the original path

  • +
  • c (string) – color of the plot

  • +
  • label (string) – label of the plot

  • +
+
+
+
+ +../../../_images/curvature_plot.png +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/utils/utils.html b/modules/utils/utils.html new file mode 100644 index 0000000000..8643bc14bb --- /dev/null +++ b/modules/utils/utils.html @@ -0,0 +1,154 @@ + + + + + + + Utilities — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Utilities

+

Common utilities for PythonRobotics.

+
+

Contents

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 1215375ed9..0000000000 --- a/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -ignore_missing_imports = True \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000..aa6e436ffc Binary files /dev/null and b/objects.inv differ diff --git a/requirements/environment.yml b/requirements/environment.yml deleted file mode 100644 index 023a3d75bf..0000000000 --- a/requirements/environment.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: python_robotics -channels: - - conda-forge -dependencies: - - python=3.13 - - pip - - scipy - - numpy - - cvxpy - - matplotlib diff --git a/requirements/requirements.txt b/requirements/requirements.txt deleted file mode 100644 index 348b54fc76..0000000000 --- a/requirements/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -numpy == 2.2.4 -scipy == 1.15.2 -matplotlib == 3.10.1 -cvxpy == 1.7.2 -ecos == 2.0.14 -pytest == 8.4.1 # For unit test -pytest-xdist == 3.8.0 # For unit test -mypy == 1.17.1 # For unit test -ruff == 0.12.10 # For unit test diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index d76b715a06..0000000000 --- a/ruff.toml +++ /dev/null @@ -1,18 +0,0 @@ -line-length = 88 - -select = ["F", "E", "W", "UP"] -ignore = ["E501", "E741", "E402"] -exclude = [ -] - -# Assume Python 3.13 -target-version = "py313" - -[per-file-ignores] - -[mccabe] -# Unlike Flake8, default to a complexity level of 10. -max-complexity = 10 - -[pydocstyle] -convention = "numpy" diff --git a/runtests.sh b/runtests.sh deleted file mode 100755 index c4460c8aa7..0000000000 --- a/runtests.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -cd "$(dirname "$0")" || exit 1 -echo "Run test suites! " - -# === pytest based test runner === -# -Werror: warning as error -# --durations=0: show ranking of test durations -# -l (--showlocals); show local variables when test failed -pytest tests -l -Werror --durations=0 diff --git a/search.html b/search.html new file mode 100644 index 0000000000..0bfaa10937 --- /dev/null +++ b/search.html @@ -0,0 +1,152 @@ + + + + + + Search — PythonRobotics documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Search
  • +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2018-2021, Atsushi Sakai.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000000..e065f4a191 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["getting_started","how_to_contribute","index","modules/aerial_navigation/aerial_navigation","modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following","modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing","modules/appendix/Kalmanfilter_basics","modules/appendix/Kalmanfilter_basics_2","modules/appendix/appendix","modules/arm_navigation/arm_navigation","modules/arm_navigation/n_joint_arm_to_point_control","modules/arm_navigation/obstacle_avoidance_arm_navigation","modules/arm_navigation/planar_two_link_ik","modules/bipedal/bipedal","modules/bipedal/bipedal_planner/bipedal_planner","modules/control/control","modules/control/inverted_pendulum_control/inverted_pendulum_control","modules/control/move_to_a_pose_control/move_to_a_pose_control","modules/introduction","modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization","modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization","modules/localization/histogram_filter_localization/histogram_filter_localization","modules/localization/localization","modules/localization/particle_filter_localization/particle_filter_localization","modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization","modules/mapping/circle_fitting/circle_fitting","modules/mapping/gaussian_grid_map/gaussian_grid_map","modules/mapping/k_means_object_clustering/k_means_object_clustering","modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial","modules/mapping/mapping","modules/mapping/ndt_map/ndt_map","modules/mapping/normal_vector_estimation/normal_vector_estimation","modules/mapping/point_cloud_sampling/point_cloud_sampling","modules/mapping/ray_casting_grid_map/ray_casting_grid_map","modules/mapping/rectangle_fitting/rectangle_fitting","modules/path_planning/bezier_path/bezier_path","modules/path_planning/bspline_path/bspline_path","modules/path_planning/bugplanner/bugplanner","modules/path_planning/clothoid_path/clothoid_path","modules/path_planning/coverage_path/coverage_path","modules/path_planning/cubic_spline/cubic_spline","modules/path_planning/dubins_path/dubins_path","modules/path_planning/dynamic_window_approach/dynamic_window_approach","modules/path_planning/eta3_spline/eta3_spline","modules/path_planning/frenet_frame_path/frenet_frame_path","modules/path_planning/grid_base_search/grid_base_search","modules/path_planning/hybridastar/hybridastar","modules/path_planning/lqr_path/lqr_path","modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator","modules/path_planning/path_planning","modules/path_planning/prm_planner/prm_planner","modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner","modules/path_planning/reeds_shepp_path/reeds_shepp_path","modules/path_planning/rrt/rrt","modules/path_planning/state_lattice_planner/state_lattice_planner","modules/path_planning/visibility_road_map_planner/visibility_road_map_planner","modules/path_planning/vrm_planner/vrm_planner","modules/path_tracking/cgmres_nmpc/cgmres_nmpc","modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control","modules/path_tracking/lqr_steering_control/lqr_steering_control","modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control","modules/path_tracking/path_tracking","modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking","modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control","modules/path_tracking/stanley_control/stanley_control","modules/slam/FastSLAM1/FastSLAM1","modules/slam/FastSLAM2/FastSLAM2","modules/slam/ekf_slam/ekf_slam","modules/slam/graph_slam/graph_slam","modules/slam/iterative_closest_point_matching/iterative_closest_point_matching","modules/slam/slam","modules/utils/plot/plot","modules/utils/utils"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["getting_started_main.rst","how_to_contribute_main.rst","index_main.rst","modules/aerial_navigation/aerial_navigation_main.rst","modules/aerial_navigation/drone_3d_trajectory_following/drone_3d_trajectory_following_main.rst","modules/aerial_navigation/rocket_powered_landing/rocket_powered_landing_main.rst","modules/appendix/Kalmanfilter_basics_main.rst","modules/appendix/Kalmanfilter_basics_2_main.rst","modules/appendix/appendix_main.rst","modules/arm_navigation/arm_navigation_main.rst","modules/arm_navigation/n_joint_arm_to_point_control_main.rst","modules/arm_navigation/obstacle_avoidance_arm_navigation_main.rst","modules/arm_navigation/planar_two_link_ik_main.rst","modules/bipedal/bipedal_main.rst","modules/bipedal/bipedal_planner/bipedal_planner_main.rst","modules/control/control_main.rst","modules/control/inverted_pendulum_control/inverted_pendulum_control_main.rst","modules/control/move_to_a_pose_control/move_to_a_pose_control_main.rst","modules/introduction_main.rst","modules/localization/ensamble_kalman_filter_localization_files/ensamble_kalman_filter_localization_main.rst","modules/localization/extended_kalman_filter_localization_files/extended_kalman_filter_localization_main.rst","modules/localization/histogram_filter_localization/histogram_filter_localization_main.rst","modules/localization/localization_main.rst","modules/localization/particle_filter_localization/particle_filter_localization_main.rst","modules/localization/unscented_kalman_filter_localization/unscented_kalman_filter_localization_main.rst","modules/mapping/circle_fitting/circle_fitting_main.rst","modules/mapping/gaussian_grid_map/gaussian_grid_map_main.rst","modules/mapping/k_means_object_clustering/k_means_object_clustering_main.rst","modules/mapping/lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial_main.rst","modules/mapping/mapping_main.rst","modules/mapping/ndt_map/ndt_map_main.rst","modules/mapping/normal_vector_estimation/normal_vector_estimation_main.rst","modules/mapping/point_cloud_sampling/point_cloud_sampling_main.rst","modules/mapping/ray_casting_grid_map/ray_casting_grid_map_main.rst","modules/mapping/rectangle_fitting/rectangle_fitting_main.rst","modules/path_planning/bezier_path/bezier_path_main.rst","modules/path_planning/bspline_path/bspline_path_main.rst","modules/path_planning/bugplanner/bugplanner_main.rst","modules/path_planning/clothoid_path/clothoid_path_main.rst","modules/path_planning/coverage_path/coverage_path_main.rst","modules/path_planning/cubic_spline/cubic_spline_main.rst","modules/path_planning/dubins_path/dubins_path_main.rst","modules/path_planning/dynamic_window_approach/dynamic_window_approach_main.rst","modules/path_planning/eta3_spline/eta3_spline_main.rst","modules/path_planning/frenet_frame_path/frenet_frame_path_main.rst","modules/path_planning/grid_base_search/grid_base_search_main.rst","modules/path_planning/hybridastar/hybridastar_main.rst","modules/path_planning/lqr_path/lqr_path_main.rst","modules/path_planning/model_predictive_trajectory_generator/model_predictive_trajectory_generator_main.rst","modules/path_planning/path_planning_main.rst","modules/path_planning/prm_planner/prm_planner_main.rst","modules/path_planning/quintic_polynomials_planner/quintic_polynomials_planner_main.rst","modules/path_planning/reeds_shepp_path/reeds_shepp_path_main.rst","modules/path_planning/rrt/rrt_main.rst","modules/path_planning/state_lattice_planner/state_lattice_planner_main.rst","modules/path_planning/visibility_road_map_planner/visibility_road_map_planner_main.rst","modules/path_planning/vrm_planner/vrm_planner_main.rst","modules/path_tracking/cgmres_nmpc/cgmres_nmpc_main.rst","modules/path_tracking/lqr_speed_and_steering_control/lqr_speed_and_steering_control_main.rst","modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst","modules/path_tracking/model_predictive_speed_and_steering_control/model_predictive_speed_and_steering_control_main.rst","modules/path_tracking/path_tracking_main.rst","modules/path_tracking/pure_pursuit_tracking/pure_pursuit_tracking_main.rst","modules/path_tracking/rear_wheel_feedback_control/rear_wheel_feedback_control_main.rst","modules/path_tracking/stanley_control/stanley_control_main.rst","modules/slam/FastSLAM1/FastSLAM1_main.rst","modules/slam/FastSLAM2/FastSLAM2_main.rst","modules/slam/ekf_slam/ekf_slam_main.rst","modules/slam/graph_slam/graph_slam_main.rst","modules/slam/iterative_closest_point_matching/iterative_closest_point_matching_main.rst","modules/slam/slam_main.rst","modules/utils/plot/plot_main.rst","modules/utils/utils_main.rst"],objects:{"Mapping.ndt_map.ndt_map":[[30,0,1,"","NDTMap"]],"Mapping.ndt_map.ndt_map.NDTMap":[[30,0,1,"","NDTGrid"],[30,1,1,"","grid_index_map"],[30,1,1,"","min_n_points"],[30,1,1,"","resolution"]],"Mapping.ndt_map.ndt_map.NDTMap.NDTGrid":[[30,1,1,"","center_grid_x"],[30,1,1,"","center_grid_y"],[30,1,1,"","covariance"],[30,1,1,"","eig_values"],[30,1,1,"","eig_vec"],[30,1,1,"","mean_x"],[30,1,1,"","mean_y"],[30,1,1,"","n_points"]],"Mapping.normal_vector_estimation.normal_vector_estimation":[[31,2,1,"","calc_normal_vector"],[31,2,1,"","ransac_normal_vector_estimation"]],"Mapping.point_cloud_sampling.point_cloud_sampling":[[32,2,1,"","farthest_point_sampling"],[32,2,1,"","poisson_disk_sampling"],[32,2,1,"","voxel_point_sampling"]],"Mapping.rectangle_fitting.rectangle_fitting":[[34,0,1,"","LShapeFitting"]],"Mapping.rectangle_fitting.rectangle_fitting.LShapeFitting":[[34,0,1,"","Criteria"],[34,1,1,"","R0"],[34,1,1,"","Rd"],[34,1,1,"","criteria"],[34,1,1,"","d_theta_deg_for_search"],[34,3,1,"","fitting"],[34,1,1,"","min_dist_of_closeness_criteria"]],"PathPlanning.BSplinePath.bspline_path":[[36,2,1,"","approximate_b_spline_path"],[36,2,1,"","interpolate_b_spline_path"]],"PathPlanning.CubicSpline.cubic_spline_planner":[[40,0,1,"","CubicSpline1D"],[40,0,1,"","CubicSpline2D"]],"PathPlanning.CubicSpline.cubic_spline_planner.CubicSpline1D":[[40,3,1,"","calc_first_derivative"],[40,3,1,"","calc_position"],[40,3,1,"","calc_second_derivative"]],"PathPlanning.CubicSpline.cubic_spline_planner.CubicSpline2D":[[40,3,1,"","calc_curvature"],[40,3,1,"","calc_position"],[40,3,1,"","calc_yaw"]],"PathPlanning.DubinsPath.dubins_path_planner":[[41,2,1,"","plan_dubins_path"]],"utils.plot":[[71,2,1,"","plot_curvature"]]},objnames:{"0":["py","class","Python class"],"1":["py","attribute","Python attribute"],"2":["py","function","Python function"],"3":["py","method","Python method"]},objtypes:{"0":"py:class","1":"py:attribute","2":"py:function","3":"py:method"},terms:{"0":[2,5,6,7,12,16,20,21,28,31,36,38,40,41,51,57,60,67,68,70,71],"000000":68,"0004460978857535104":68,"001":67,"0068996":6,"00766623":6,"01":[5,65,68,71],"010":68,"010729072751057656":68,"0126109674819155":68,"02":[28,68],"0224618077401504":6,"023098460073039763":65,"05":12,"05064471":6,"06219243":6,"06326":53,"07":65,"084":68,"08855629":6,"08868895":6,"0f":68,"0x139196438":6,"1":[5,6,7,12,16,17,20,21,23,28,30,31,36,38,41,51,57,60,66,68,70],"10":[6,10,28,65,67,68],"100":[6,12,28,65,68],"1000":[6,32,36],"10703":[0,2],"11":[0,5,6,68],"11555291":6,"12":[6,68],"122":28,"1227":68,"1228":68,"125083004":68,"12561859":6,"12a_4t":51,"12t":51,"13":6,"130":6,"1344":68,"13957621":6,"14":[5,6],"14142933":6,"142":68,"1483":68,"15":[6,12,41,52,65,67,68],"150":28,"151":68,"1571437":6,"16":6,"1601":53,"17":6,"18":[6,68],"180":6,"1808":[0,2],"189":68,"19":6,"1972439134893":68,"1985":28,"1d":[49,68],"1e":5,"1n":6,"2":[2,5,6,8,12,16,17,21,23,28,30,36,38,41,51,52,57,60,70],"20":[6,7,28,65,67,68],"2010":68,"20a_5t":51,"20t":51,"21":6,"215":68,"21534324":6,"22":6,"23":6,"230":6,"232":68,"24":6,"25":[6,12,65,68],"256":68,"27":6,"2_":7,"2a":38,"2a_2":51,"2a_2t":51,"2c":40,"2c_":40,"2cx":40,"2d":[6,17,20,21,26,27,28,30,33,37,38,39,41,42,45,49,51,68,69,71],"2l":16,"2l_0l_1":12,"2lmsin":16,"2n":6,"2x1":67,"2x2":[30,65,67],"2z":6,"3":[0,2,5,6,7,12,17,21,28,31,36,41,49,51,52,67,68,70],"30":[6,28,67,68],"30943011":6,"31":68,"320031728":68,"3299":68,"338155":68,"34638597":6,"35":12,"3825":68,"38334183":6,"3a_3t":51,"3d":[2,3,6,29],"3dof":68,"3dx":40,"3f":68,"3t":51,"3x1":67,"4":[5,6,7,12,20,21,28,35,36,51,68],"40":28,"400":7,"4000000000000001":6,"414":68,"42029769":6,"427649841628278":68,"428":68,"43":68,"448":68,"45":[12,41,68],"45725355":6,"49420941":6,"499":65,"4a_4t":51,"4d":68,"4n":40,"4t":51,"5":[5,6,7,12,28,36,51,65,67,68],"50":[28,65,67,68],"500234":68,"512":68,"524048014922737":68,"53116527":6,"56812114":6,"5a_5t":51,"5a_st":51,"5t":51,"6":[5,6,7,12,28,40,41,51,60,68],"60":6,"600":5,"605077":6,"609154":68,"64203286":6,"65":5,"652":68,"67898872":6,"6a_3t":51,"6dx":40,"6t":51,"7":[5,6,7,12,31,40,51,65,68],"706393":68,"716":68,"7191686":68,"73":68,"735":68,"75":12,"750000000000007":68,"76":6,"785":68,"8":[6,7,12,28,41,51,65,68],"80":[6,7],"82":65,"8405":68,"8624":68,"9":[1,6,7,12,51,68],"90":7,"9074":68,"95":5,"951154575772496e":65,"976":68,"985":68,"99":[5,7,31],"997297":68,"997826":68,"\u03b4":60,"\u03c6":60,"\u03c7":68,"\u975e\u7dda\u5f62\u30e2\u30c7\u30eb\u4e88\u6e2c\u5236\u5fa1\u306b\u304a\u3051\u308bcgmres\u6cd5\u3092python\u3067\u5b9f\u88c5\u3059\u308b":57,"a\u00e7\u0131kme\u015f":5,"beh\u00e7et":5,"case":[1,6,7,21,30,36,65,67,68],"class":[12,15,30,34,40,65],"default":[31,32,36,41],"do":[7,12,65],"final":[5,7,17,21,38,41,50,55,56],"float":[17,28,31,32,40,41,71],"function":[1,6,7,12,16,20,28,30,32,34,36,45,49,57,60,67,68,71],"import":[5,6,7,12,28,36,40,65,67,68],"int":[7,28,32,36,65,67],"n\u03c7":68,"new":[2,6,7,30,65,67],"return":[5,6,7,12,17,28,31,32,34,36,40,41,65,67,68],"short":1,"true":[5,6,7,20,21,23,25,28,40,41,65,67,68],"try":[1,12,65],"var":6,"while":[28,65,67],A:[1,5,6,7,12,16,17,28,31,35,38,40,41,46,47,49,52,53,60,62,63,67,68],And:[20,51],As:[40,65,67,68],At:[1,65,67,68],But:[6,17,21,28],By:68,For:[0,6,7,21,28,40,41,51,65,67,68,71],If:[1,7,12,16,21,31,34,35,36,41,51,55,65,67],In:[0,1,6,7,10,12,17,20,21,28,30,31,32,34,38,45,50,51,53,55,56,65,67,68],It:[0,1,6,7,12,21,23,34,35,36,40,41,51,65,68,69],Its:[20,45],No:68,Not:[36,68],OF:65,Of:[2,67],On:[7,39,41],One:1,That:[6,68],The:[0,1,2,6,7,12,16,17,20,21,23,24,25,28,30,32,34,36,38,40,42,44,45,50,53,55,56,60,62,64,65,66,67],Then:[12,30,51,68],There:[21,31],These:[6,40,67,68],To:[2,6,17,28,65,67,68],With:67,_0:40,_1:68,_2:68,_:[6,7,12,17,20,40,41,65,68],__init__:[12,65],_array_lik:32,_dom_class:65,_edg:68,_i:[36,68],_j:[40,68],_last_:6,_m:68,_n:68,_probabilist:[21,23],_scalartype_co:32,_t:20,_type:32,_vertic:68,a_0:51,a_1:51,a_1t:51,a_2:51,a_2t:51,a_3:51,a_3t:51,a_4:51,a_4t:51,a_5:51,a_5t:51,a_:[40,51],a_st:51,ab:[0,65,68],abid:6,abil:28,abl:[7,68],about:[6,17,67,68],abov:[6,7,31,40,65],ac_j:40,academia:0,accel:60,acceler:[7,51],accept:1,access:1,accord:[6,65,68],account:[7,12,17,67],accumul:67,accur:[36,38],aco:41,acquir:32,across:6,act:67,actual:[12,65,67],actuat:17,ad:[2,6,7,12,21,36,67,68],adapt:29,add:[0,65,67],add_new_lm:65,addit:[6,7,36,68],adjac:[16,55],adjust:[6,36,65],admiss:53,advantag:34,aerial:2,after:[1,7,21,40,65,67,68],again:[7,51,65,68],agre:1,agv:51,albeit:68,algebra:16,algorithm:[0,2,6,8,15,22,27,28,30,32,34,37,38,39,41,48,49,53,58,69,70],algotihm:28,align:[6,7,17,38,68],all:[1,6,7,21,31,32,34,36,41,51,67,68],allow:[6,7,12,34,68],almost:68,along:[7,17,51,67],alpha:[12,17,28,41,68],alpha_m:5,alreadi:12,also:[1,6,7,12,20,36,40,65,67,68],although:32,alwai:6,among:[41,68],an:[0,6,7,12,16,17,25,28,31,32,34,39,45,53,59,60,65,67,68],analyt:[40,41,68],anatom:12,anchor:68,andrew:67,ang:28,angl:[6,12,16,17,28,34,40,41,60,65,67,68],angle1:68,angle2:68,anglen:[65,67],angular:[5,17,65,67],ani:[1,7,32,36],anim:[0,45,50,55,56,66],annot:12,antialias:6,anywher:68,api:[29,49],apollo:59,apolloauto:59,appear:67,append:[28,40,65,67,68],appendix:2,appendleft:28,appli:[16,34,67,68],applic:[1,2,17,53,67,68],appreci:1,approach:[2,28,49,65,68],appropri:67,approx:[16,68],approxim:[40,49],approximate_b_spline_path:36,ar:[0,1,2,6,7,17,20,21,23,24,25,28,31,32,34,36,40,41,44,45,50,51,53,55,56,57,65,67,68],arang:[6,28,40],arc:55,arctan2:17,area:[10,12,28,55],arg:68,argmin:68,arithmet:6,arm:2,around:[6,60],arrai:[5,6,7,12,28,31,36,40,41,65,67,68],array_lik:[36,71],arriv:68,arrow_length:41,arrowprop:12,artifact:1,artifici:23,arxiv:0,ascend:40,assign:7,associ:[6,65,67,68],assum:[21,23,40,51,55,68],ast:7,atan2:[12,41,65,67],atsushi:67,atsushi_twi:67,atsushisakai:[0,1,20,60],attach:16,attempt:12,au:12,author:[67,68],autom:28,automat:[53,64],automobil:64,autonom:[0,35,53,58,59,64],avail:[67,68],averag:[6,21,32,41,65],avoid:[2,9,42,45,68],awai:67,ax1:65,ax2:65,ax3:65,ax:[6,16,51],axes3d:6,axi:[6,12,17,28,34,40,41,51,65,67],az_k:60,az_t:60,b:[2,5,6,7,12,16,20,38,40,49,51,60,65,67,68],b_0:51,b_1t:51,b_2t:51,b_3t:51,b_4t:51,b_5t:51,b_:[36,40],b_orig:36,back:12,backer:1,background:1,backward:52,bailei:66,balanc:16,bar:[6,7,60,67],base:[2,7,12,16,17,21,28,30,31,32,34,35,40,41,46,49,51,53,60,65,66,67,70],basi:36,basic:[0,2,8,28,49],batch:49,bay:[6,8,21,68],bayes_filt:7,bayesian:[7,21],beacon:67,beam:28,bear:[67,68],becaus:[6,7,12,17,32,71],becom:[1,6,7,32,67],been:[7,67,68],befor:[1,7,12,65,67,68],begin:[5,6,7,16,20,36,38,40,51,57,60,65,67,68],beginn:0,behavior:0,behind:[0,68],beizer:35,bel:7,belief:[7,65,67,68],believ:67,bell:6,belong:65,below:[7,17,34,40,55],berkelei:60,best:[12,34],beta:[12,17,41],better:68,between:[6,7,12,17,28,31,32,34,38,40,41,55,65,67,68,69],beyond:68,bezier:[2,49],bf:[7,57],bia:6,bias:49,bibtex:2,bidirect:49,big:6,bigger:36,binari:68,biped:2,bit:53,bivari:6,black:[12,20,21,23,30,44,53,55,65,67,68],blanco2010tutori:68,blanco:68,blog:6,blue:[20,21,23,25,45,50,55,56,62,65,67,68],bmatrix:[6,16,20,51,57,60,65,67,68],boldsymbol:[30,68],boor:36,both:[0,6,52,67,68],bottom:[28,34],bound:28,boundari:[28,40,51,54],box:7,boxplu:68,breadth:49,breast:7,bresenham:28,brown:68,bspline:49,bspline_path:36,bsplinepath:36,bu:[7,16,60,65,67],bu_k:60,bu_t:20,bug:[1,2,49],build:1,build_doc:1,builder:1,bullet:[7,68],burgard:[6,7,68],c1:36,c2:36,c:[2,6,7,16,36,38,40,60,61,65,68,71],c_0:40,c_1:40,c_2:40,c_:40,c_b_i:5,c_i:36,c_i_b:5,c_sigma1:68,c_sigma2:68,c_sigma3:68,cal_observation_sigma:68,calc:[40,65],calc_chi2:68,calc_control_command:17,calc_curvatur:40,calc_first_deriv:40,calc_innov:67,calc_input:[67,68],calc_jacobian:68,calc_lm_po:67,calc_n_lm:67,calc_normal_vector:31,calc_posit:40,calc_rotational_matrix:68,calc_second_deriv:40,calc_yaw:40,calcul:[6,7,12,17,21,22,29,34,36,41,49,51,67,68,69,71],call:[6,12,34,36,68],camera:32,can:[1,6,7,10,12,17,20,21,23,28,31,34,35,36,38,40,41,51,55,60,65,67,68,69],cancel:17,cancer:7,cannot:[6,7,12,40],captur:67,car:[6,17,41,52,53],care:67,carlon:68,carnegi:34,carri:7,cart:16,cast:[2,29],categor:41,categori:68,caus:7,cccccc:40,cccccccccccccc:5,cd:0,cdot:[6,7,40],cell:[28,30,65,68],center:[21,28,30,31],center_grid_i:30,center_grid_x:30,center_vector:31,certain:[7,32,34],challeng:64,chanc:7,chang:[10,21,34,35,36,65,67,68],chapter:55,character:68,charecterist:6,charg:67,check:[0,1,28,55],chi:68,chi_:68,chi_k:68,children:65,choleski:65,choos:7,chosen:[7,65],ci:1,circl:[2,21,29,53],circleci:1,cla:67,classic:12,classifi:68,clear:1,click:[1,10,12],clim:28,clone:0,close:[28,49],closer:17,closest:[2,70],closur:68,clothoid:[2,49],cloud:[2,29,31],cluster:[2,29,30,34],cm:6,cmap:[6,28],cn:68,co:[12,16,20,28,38,41,57,60,65,67,68],code:[0,2,20,34,35,36,38,40,41,42,47,52,53,54,55,60,65,67,68],coeffici:40,coin:7,collect:[0,2,28,68],collid:55,collis:[42,55],color:[12,17,28,68,71],colorbar:28,column:23,com:[0,5,6],combin:[67,68],come:[60,68],comma:28,command:[7,17,65,68],comment:1,common:[12,72],commun:1,compact:68,compactli:68,compani:1,compar:[17,32,67,68],complet:[6,7,39,67,68],complex:[32,53,54],compon:12,composit:68,comprehend:6,comprehens:41,compromis:32,comput:[7,17,32,41,65,67,68],compute_jacobian:65,compute_weight:65,concept:[6,7,68],concetr:65,concis:[1,67],concret:68,conda:0,condit:[8,40,51,68],condition:7,conf:28,confid:67,configur:[12,17],confus:6,conjug:23,consecut:68,consensu:29,consid:[1,12,31,67,68],consist:[16,30,31,34,41,68],constant:[34,68],constrain:[12,68],constraint:[17,41,57,68],construct:[6,17,28,41,49,68],consumpt:5,contact:1,contain:[5,7,28,67,71],context:68,contin:6,continu:[6,21,35,38,49],contour:6,contourf:6,contrast:68,contribut:[2,68],control:[2,7,9,35,36,51,53,61,62,65,67,68],converg:[65,68],convert:[28,68],convexif:5,convolut:7,coord:12,coordin:[12,16,28,40,65,67,68],copi:[65,68],cork:17,correct:[7,17,65,67],correctli:[7,12],correl:8,correspond:[7,28,67,68],cosin:12,cost:[16,57,60],cotrol:60,could:68,cours:[35,41,44,60,62,67],cov:6,covari:[7,8,20,22,30,65,67,68],covariac:20,cover:67,coverag:[2,49],cow:6,cpoint:28,creat:[0,1,6,17,68],credit:12,cross:[21,25,44,50,53,55,56,62,65,67],crux:7,cset:6,cstride:6,csv:28,cubic:[2,49],cubic_spline_plann:40,cubicsplin:40,cubicspline1d:40,cubicspline2d:40,cumsum:65,current:[1,7,17,21,28,67,68],curv:[6,35,36,38,41,49,52],curvatur:[35,36,38,41,71],cvxpy:[0,60],cx:[16,67],cyan:[44,45,50,53,56],d1:68,d2:[65,68],d:[6,16,21,38,40,41,49,65,67,68],d_1:41,d_2:41,d_3:41,d_:[40,57],d_a:57,d_i:68,d_j:68,d_theta_deg_for_search:34,dare:16,darpa:64,data:[5,6,21,28,31,32,34,40,65,67,68,71],dataset:[6,70],ddot:[6,16,40],ddy:40,de:36,dead:[20,23,65,67,68],deal:6,decomposit:69,decreas:[7,34,65],deepcopi:[65,68],def:[5,6,12,28,36,65,67,68],defect:2,defin:[6,7,12,16,17,40,67,68],definit:[2,16],deg2rad:[41,65,67,68],deg:[34,40],degre:[17,36,60,68],delta:[20,38,57,60,65,67,68],delta_i:17,delta_x:17,deltax:68,den:65,denomin:7,denot:[6,7,68],densiti:30,depend:[0,1,2,8,12,40,68],depth:49,dequ:28,deriv:[12,34,53,68],describ:[1,17,65,67],descript:[1,65],design:[14,22],desir:12,det:[6,65,68],detail:[0,1,2,7,16,55,68],detect:[7,34],determin:[12,17,28,32,34,40,67,68],determinist:[6,65],develop:[0,1],deviat:[6,68],diag:[5,65,67,68],diagon:6,diagram:[7,12],dict:12,didn:7,die:65,dieter:[6,7],diff:17,differ:[7,17,21,34,35,38,65,67,68],differenti:[40,51,57,68],difficult:6,dijkstra:[49,50,56],dimens:[6,17,23,36,40],dimension:[6,32,40,49],dir:[0,1],dir_cosin:5,direct:[12,17,21,34,53,68],directli:[12,68],directori:0,discrep:68,discret:[16,21,28,60],discretis:28,discrimin:24,discuss:68,disk:29,displac:12,displai:[5,7,28],displaystyl:7,dist:28,distanc:[2,12,17,21,23,28,29,31,32,34,35,38,40,41,45,65,67,68,71],distribut:[21,29,32,34,65,68],div:5,dn:[65,67,68],doc:1,docstr:1,document:[0,6,31,60],doe:[6,15,21,31,32,55],doesn:[7,67],dof:[5,17],domain:53,donat:1,done:[30,65,67,68],door:7,doorwai:7,dot:[16,20,57,60,65,67],doubl:[47,53],down:[12,17],download:68,dr:67,draw:12,draw_angl:12,drawn:65,drewtu2:67,drive:[6,17,53,58,59,62,63,67],drone:[2,3],ds:40,dt:[17,60,65,67,68],dta:60,dtb:60,dtype:[32,67],du:16,du_:60,dubin:[2,49],dubins_path_plann:41,dubinspath:41,due:[7,21],dure:[1,12,65,68],dx:[6,7,12,65,67,68],dy:[40,65,67],dynam:[2,44,49,60],dz:65,e:[6,7,17,28,41,65,67,68],e_:68,e_j:68,e_x:6,each:[0,1,2,6,7,20,21,28,30,32,34,36,40,41,45,51,55,65,67,68],earlier:[65,68],eas:0,easi:[1,2,7,28],easier:[12,67],easili:[17,68],east:28,eb6d1cbe6fc90c7be9210bf153b3a04f177cc138:60,ece452:37,edg:[34,68],edges_odom:68,edges_scan:68,effect:[17,65,68],effector:[10,12],effici:34,eig_valu:30,eig_vec:30,eigen:30,einsum:6,either:68,ekf:[2,20,24,65,68,70],ekf_slam:67,elbow:12,elbow_i:12,elbow_x:12,element:[6,23,28,36,67],elf:28,ellips:[6,20,30,53],ellipsoid:53,els:[36,65,68],embersarc:5,empti:[6,28,68],encompass:34,end:[5,6,7,10,12,16,20,35,36,38,40,41,51,57,60,65,67,68],end_i:41,end_x:41,end_yaw:41,endpoint:36,enkf:19,ensambl:[2,22],ensur:40,enter:68,entri:68,enumer:34,env:0,environ:[0,1,6,7,12,21,28,39,53,54,68],eq:[51,68],equal:[7,12,17,28,40,41,67,68],equat:[3,7,12,16,17,20,23,36,40,51,57,60,65,67,68],equival:36,error:[1,6,7,17,21,34,67,68],estim:[2,7,20,23,25,29,65,67,68],eta:[2,7,49],eta_j:68,etc:[7,32],euclid:45,euler:[60,68],evalu:34,event:7,everi:[6,67],evid:7,evolv:65,ex:20,exactli:7,exampl:[2,6,8,12,21,26,28,30,31,33,34,36,40,41,65,66,67,69,70],except:65,execut:[0,1,65],exist:[2,21,67],exp:[6,21,30,65,68],expand:55,expans:28,expant:60,expect:[1,67,68],experi:65,experienc:67,explain:[1,17,32],explor:[2,49,68],express:[12,36,40],extend:[2,12,17,22,67],extended_kalman_filt:20,extens:53,extent:68,extern:32,extract:32,ey:67,f1:67,f2:67,f51a73f47cb922a12659f8ce2d544c347a2a8156:60,f:[0,5,6,7,20,28,36,60,65,67,68],f_:7,f_x:7,fac:6,facecolor:12,facilit:68,fact:68,factor:71,fair:7,fake:7,fals:[7,28,36],famili:65,far:[32,67],farther:32,farthest:29,farthest_point_sampl:32,fashion:68,fast:[32,38,45,65],fastslam1:[2,70],fastslam:[2,65,70],feasibl:54,featur:[2,65,66,68],feedback:[2,16,61],field:49,fig:[6,65],figsiz:28,figtext:65,figur:[5,6,28,36,40,55,65,68],file:[1,28,68],file_read:28,filenam:[5,7],fill:[6,28,68],filter:[2,6,8,22,65,67,68],financi:1,find:[1,12,17,45,55,67,68],first:[1,7,12,28,30,34,49,60,68],firstli:68,fit:[2,29,30,38],five:40,fix:[2,68],flip:7,flood:28,flood_fil:28,focu:[0,1],focus:53,folder:68,follow:[1,2,3,6,7,23,31,36,38,40,51,65,67,68],fontsiz:68,footstep:14,forc:16,form:[6,7,12,30,67],format:[28,68],formul:[7,51,61,70],formula:[21,36,68],forward:[7,12,52,60,68],forward_kinemat:12,found:68,fox:[6,7],fpf:7,frac:[5,6,7,12,16,20,21,23,30,31,36,38,40,57,60,65,67,68],frame:[2,49,67],free:[1,5,28,55],freedom:[17,68],frenet:[2,49],fresnel:49,fring:28,frm:55,from:[1,5,6,7,12,16,17,20,22,25,28,31,32,34,35,36,40,41,51,55,60,65,67,68,71],fuel:5,full:68,fulli:[58,68],funciton:65,further:68,fusion:[19,20,23,24],futur:7,fx:[7,65,67],fx_t:20,g1:38,g2o:68,g:[6,16,17,20,28,41,49,67,68],g_i:5,g_odom:68,g_scan:68,g_x:41,g_y:41,g_yaw:41,gain:[8,17,67],gamma:12,gather:6,gaussian:[2,7,8,21,29,65,68],gca:[6,28],gco:16,gener:[0,2,3,6,8,20,28,32,34,35,36,40,41,43,44,49,54,68],generate_ray_casting_grid_map:28,geometr:53,geometri:12,get:[1,2,6,7,12,17,20,21,35,40,51,60,65,68],get_h_b:68,get_lm_pos_from_st:67,ghliu:52,gif:[1,65],gird:21,git:0,github:[0,1,2,5],give:[6,7,12,28,68],given:[6,7,12,17,28,40,65,67,68],gm:16,gmre:[2,61],gnss:20,go:[0,1,12,17,67,68],goal:[0,1,10,12,17,38,41,53,55,68],goe:[12,52],good:[32,57],govern:17,gp:[20,67,68],grand:64,graph:[1,2,36,50,53,56,70],graph_based_slam:68,graphics_radiu:68,graphslam:68,graviti:5,great:1,greater:[31,32],green:[20,28,53,62,67],grid:[2,21,29,30,32,40,41,49,65,67,68],grid_index_map:30,grind:21,gripper:12,grisetti2010tutori:68,grisetti:68,ground:[65,67,68],grow:67,guess:7,guid:53,gyro:20,gz:65,h:[7,20,21,57,67,68],h_:40,h_i:40,h_t:67,ha:[6,7,20,21,28,40,41,65,66,68],had:[7,65],hallwai:7,hamiltonian:57,han:28,hand:7,handi:28,happen:1,hat:[65,68],have:[1,6,7,12,30,32,34,65,67,68],head:[7,17,36,40,71],header:1,heading_list:71,heat:45,height:6,help:[1,7,12,17,68],henc:[6,7,12],here:[6,7,28,36,67],hermit:38,heurist:[45,53],hf:65,hi:68,high:[23,28,54,67],higher:[1,17,21],highest:[6,65],hist:[6,65],histogram:[2,7,22],histori:[2,65,67,68],hline:7,hobbi:1,hold:[7,65,68],holonom:15,horizont:[16,34,68],how:[2,6,7,15,22,28,55,65,67,68],howev:[7,10,31,65,67,68],hphantom:68,hspace:65,hstack:[65,67,68],html:[5,65],http:[0,5,6,12],hv:65,hx_:20,hxdr:[67,68],hxest:67,hxtrue:[67,68],hybrid:[2,49],hz:68,i:[1,2,7,8,12,17,20,23,36,40,60,65,67,68],i_:40,i_j:23,i_k:23,icp:[2,70],id1:68,id2:68,id:[1,21,34,67,68],id_set:34,idea:[0,2,17,68],ident:[12,67,68],ieee:[28,68],ignor:12,ii:6,ij:[6,68],illustr:68,imag:[5,7,28],immateri:7,implement:[5,12,20,52,57,60,65,67,68],implicit:53,imposs:6,improv:[7,23,45,68],imshow:[28,68],imu:68,includ:[0,1,12,17,20,31,34,55,65,67,68],incorpor:[7,67,68],increas:[6,7,34,40,65],increment:[17,53],ind:[65,67],independ:[6,8,17,65,68],index:[2,30,65,67],indic:[65,68],industri:0,inertia:5,inf:[6,68],infom:20,inform:[1,6,7,21,23,41,49,55,67,68],infti:6,init_print:5,initi:[7,16,28,34,41,60,65,67,68],initp:67,inlier:31,inlier_dist:31,inlier_radio_th:31,inlin:[12,65,68],innermost:6,innov:67,input:[6,7,20,21,36,40,57,60,65,67,68],input_intel:68,insert:68,insight:65,inspir:5,instal:0,instanc:7,instanti:17,instead:[12,68],institut:34,int_:[6,38],integr:[47,49,53],intellig:68,interact:[10,12,65],interest:65,interpol:[38,40,49],interpolate_b_spline_path:36,interv:[20,34,40],intial:65,introduc:7,introduct:[2,8,69,70],intslid:65,intuit:[0,65,67],inv:[6,65,67,68],invers:[9,68],invert:[2,15],involv:[6,12],ip:65,ipython:[5,7,65],ipywidget:65,irion:68,issu:[1,32],item:1,iter:[2,7,28,31,32,67,68,70],itertool:68,ith:6,its:[6,7,12,17,32,38,41,45,53,67,68],itself:[7,12],ix:[36,40],iy:40,iyaw:40,iz1:68,iz2:68,iz:[65,67],j:[6,16,23,40,57,68],j_b:5,j_f:20,j_g:20,jacob_mot:67,jacobh:67,jacobian:[5,20,67,68],jame:40,jeff:68,jetbrain:1,jf:67,join:41,joint:[2,9],joint_angl:12,jth:6,judg:34,just:[1,6,7,12],k:[2,6,7,12,16,20,23,29,36,40,60,67,68,71],k_p:17,k_th_t:67,kalman:[2,6,8,22,67],kappa:[38,40],keep:[1,32],keesl:40,kei:7,kf:[2,8],kh:7,kick:7,kind:40,kinemat:9,kl:6,knot:36,know:[7,12,67],known:[12,21,40,51,65,67,68],ko:12,kp_alpha:17,kp_beta:17,kp_rho:17,kp_x:17,kp_y:17,kummerl:68,ky:[7,20],l0:12,l1:12,l:[6,7,16,28,34,38,41,60,68],l_0:12,l_1:12,l_1co:12,l_1sin:12,lab:60,labb:[6,7],label:[1,6,36,40,41,68,71],label_diagram:12,lagrang:16,lambda_1co:57,lambda_1vco:57,lambda_1vsin:57,lambda_2sin:57,lambda_2vco:57,lambda_2vsin:57,lambda_3:57,lambda_4:57,lambda_4u_a:57,land:[2,3],landmark:[23,65,67,68],lane:49,languag:[1,12],larg:[31,32,68],larger:[6,31,68],largest:65,laser:[28,34],last:67,lastli:17,later:71,lattic:[2,48,49],law:[6,8,12,16],lco:60,ldot:68,lead:[12,68],learn:[2,32],least:[1,31,41,67,68],lectur:65,left:[5,6,7,10,12,21,30,38,40,41,67,68,71],legend:[6,40,41,68],len:[36,65,67,68],length:[12,16,38,40,41],leq:[36,57],less:[31,32,36,65],lesson:12,let:[1,7,12,28,68],lg:28,li:68,librari:[0,1,12],licens:1,lidar01:28,lidar:[2,29,32,68],lidar_to_grid_map:28,lie:7,lieu:68,like:[0,1,6,7,21,30,32,55,65],likelihood:[7,68],linalg:[6,65,67,68],linalgerror:65,line:[17,20,23,24,28,39,40,41,44,50,53,55,56,62,65,67,68,71],linear:[2,16,17,40,51,61,65,67,68],linearli:[38,60],linewidth:[6,12,28],link:[9,16],link_length:12,linspac:[6,36,40,65],list:[6,30,36,40,41,68,71],lite:49,literatur:68,littl:67,ll:[6,12],lm:[65,67],lm_id:65,lm_size:[65,67],lmid:[65,67],lmp:65,load:[16,68],load_g2o_se2:68,loc:6,local:[1,2,8,51,53,68,70],locat:[6,7,65,67,68],logic:17,look:[7,21,65,68],lookup:49,loop:[49,68],lose:7,lost:7,low:[65,67],lower:[6,65,68],lowest:65,lqr:[2,15,49,61],lrl:41,lshapefit:34,lsl:41,lsr:41,luca:68,m:[5,16,30,32,34,40,41,65,67,68],m_dist_th:[65,67],m_k:68,machin:32,made:67,magazin:68,magic:7,magnitud:71,mahalanobi:[65,67],mahanfathi:53,mai:6,main:[1,36,67,68],maintain:65,maintoolbar:5,major:[6,7],make:[1,6,12,40,68],malaga:68,mammogram:7,manag:21,maneuv:51,manifold:68,manipul:12,manual:1,map1:28,map:[2,7,17,21,31,32,34,45,49,65,67,68,70],map_float:28,mar:5,margin:7,mark:28,markers:[65,68],mass:16,master:[20,60],match:[2,28,68,70],materi:60,math:[6,12,28,65,67,68],mathbb:[6,68],mathbf:[7,30,36,68],mathcal:[7,30,68],mathemat:[1,7,61],mathit:6,mathjax:5,mathop:68,mathrm:[7,38,68],mathsf:[7,68],mathtt:6,matmul:12,matplotlib:[0,6,12,28,40,65,67,68],matri:68,matric:[7,68],matrix:[5,6,20,22,30,40,67,68,69],max:[36,57,60,65,68],max_angular_spe:17,max_it:32,max_itr:68,max_linear_spe:17,max_rang:[65,67,68],maxi:28,maximum:[6,17,21,32,34,41,60,65,67,68],maxx:28,mayb:12,mco:16,mdist:67,mean:[2,6,7,20,21,23,24,28,29,30,32,36,50,56,62,66,68],mean_i:30,mean_x:30,measur:[6,7,23,28,65,67,68],measure_constraint:68,mechan:12,mellon:34,mention:65,menubar:5,merg:[17,32],mesh:6,meshgrid:6,method:[12,28,31,32,34,40,50,53,55,56,64,67],mgrid:6,michael:5,mid:6,might:[1,12,21,32,36],min:[36,60,67,68],min_dist:32,min_dist_of_closeness_criteria:34,min_n_point:30,mind:1,mini:28,minid:67,minim:[16,41,60],minimum:[2,30,32,34,41,60],minor:28,minx:28,miss:[2,7],mit:[6,7],mix:7,ml:16,mlab:6,mm:67,mobil:[24,39,43,48,54,68,69],mod:[17,41],mode:[6,41,57,60],model:[2,6,7,15,21,22,30,34,40,41,46,47,49,53,54,61,65,67,68],model_predictive_speed_and_steer_control:60,modifi:14,modul:[0,1,2,71],moment:5,mono:40,moravec:28,more:[0,2,7,16,34,36,65,67,68],most:12,mostli:68,motion:[6,7,22,45,49,53,54,56,57,62,63,65,67,68],motion_model:[65,67,68],move:[2,6,7,12,15,16,21,68],move_to_pos:17,move_to_pose_robot:17,movement:21,mpc:[15,61],mpl_toolkit:6,mplot3d:6,mu1:6,mu2:6,mu:[6,7,30,65],mu_1:6,mu_2:6,mu_:[6,7],mu_i:6,mu_j:23,mu_k:23,mu_n:6,mu_new:6,mu_p:6,mu_x:6,mu_z:[6,7],much:[6,34,65,67],multipl:[6,7,40,67,68],multipli:[6,7,17,68],multivari:[6,8,67],multivariate_gaussian:6,multivariate_norm:6,must:[12,36,40,67],mx:67,my:1,mypi:0,n1:6,n2:6,n:[2,6,9,28,30,31,32,36,40,65,67,68],n_:23,n_lm:65,n_particl:65,n_path_point:36,n_point:[12,30,32],name:[12,17,68],natur:40,navig:[0,2,17,42,45,54],ncol:65,ndarrai:32,ndt:[2,29],ndt_map:30,ndtgrid:30,ndtmap:30,nearbi:67,nearest:34,necessari:28,need:[12,17,21,28,30,40,68],neff:65,neg:7,neighbour:28,net:12,new_indic:65,next:[1,7,21,28,67,68],ngraphslam:68,nlm:67,node:[40,45,68],nois:[7,20,23,31,65,67,68],noisi:67,noiz:21,non:[15,68],none:[36,40,41],nonlinear:[2,38,61],nonlinear_control:57,norm:[5,6],normal:[2,6,7,17,29,65,68],normal_vector:31,normal_vector_estim:31,normalize_weight:65,north:28,notag:68,notat:68,note:[1,6,7,17,36,67,68],notebook:[5,65,67,68],noth:[6,7],notic:[1,12,65,67],now:[7,12,51,68],np:[5,6,12,28,31,36,40,41,65,67,68],nrow:65,nt:6,nth:65,num:65,num_landmark:67,number:[7,17,28,30,31,32,36,40,65,67,68,71],number_of_nod:68,numer:[6,68],numpi:[0,5,6,12,28,32,40,65,67,68],nx1:67,nx:28,nxn:67,ny:28,o:68,ob:[60,68],object:[2,6,12,16,29,67,68],observ:[22,25,30,31,65,68,70],obstacl:[2,9,28,30,44,45,53],obtain:[32,68],obvious:6,occup:[28,65],occupi:28,occur:7,od:60,odom:68,odometri:68,off:6,offlin:68,offset:[6,12,17,35],offset_yawrate_nois:65,often:68,ok:1,okai:12,old:65,omega:[5,17,20,68],omega_i:65,omega_j:68,omega_t:20,ominu:68,one:[6,7,12,17,31,34,40,41,49,66,68],ones:[28,65,67],onli:[0,1,6,7,16,17,32,65,67,68],onlin:68,ons:68,open:[0,28,59],oper:[32,68],optim:[2,38,49,52,53,54,57,60],optimz:68,option:[36,41],order:[28,40,68],org:[0,20],orient:[17,20,21,28,38,51,68],orig_point:32,origin:[12,34,67,68,71],original_point:32,os:1,oss:[0,1],other:[1,6,7,12,32,38,68],otherwis:36,our:[1,6,7,12,40],out:[17,65,68],outlier:31,output:[6,17,41,65,68],outsid:[40,67],outward:55,over:[6,67,68],overdetermin:68,overlin:7,overview:1,ox:[28,30,34],oy:[28,30,34],p102:17,p1:31,p2:31,p3:31,p:[6,7,12,16,17,31,36,41,65,67,68],p_1:31,p_2:31,p_3:31,p_:[20,21,67],p_ix_i:6,p_t:[20,21,67],pack:6,packag:28,page:2,pair:68,panel:1,paper:[0,1,2,5,34],parallel:[0,17],param:67,paramet:[5,6,17,30,31,32,34,36,41,49,51,65,67,68,71],parameter:68,pars:68,parse_edg:68,part:[2,8,40,67,68],partial:[20,57,60,68],particl:[2,22,65,68],particular:[67,68],partwai:7,pass:[36,40,65,67],past:7,patch:6,path:[2,12,17,36,40,44,45,46,50,51,52,54,56,58,59,60,62,63,64,68,71],path_finder_control:17,path_i:41,path_x:41,path_yaw:41,pathfindercontrol:15,pathplan:[36,40,41],patienc:1,patreon:1,paug:67,paus:67,paypal:1,peak:[6,7],peaki:21,pendulum:[2,15,53],peopl:1,percent:67,perfect:68,perform:[32,54,67,68],permiss:68,pest:67,pf:[23,65],phase:[53,67],phi1:68,phi2:68,phi:[20,60],phi_:[38,57],phi_a:57,phi_t:20,philosophi:1,pht:65,pi:[6,12,17,21,28,30,41,65,67],pi_2_pi:[65,67,68],pick:[12,65],pid:[53,59,62,63,64],piecewis:36,pip:0,pivot:16,piyg_r:28,plan:[2,12,37,39,45,53,62,63],plan_dubins_path:41,planar:9,plane:[17,31,51],planner:[2,13,41,46,48,49,50,53,56],platform:59,playback:65,pleas:[1,68],plot:[2,6,10,12,28,36,40,41,53,65,67,68,72],plot_arrow:41,plot_curvatur:[36,72],plot_particl:65,plot_surfac:6,plt:[6,12,28,36,40,41,65,67,68],plug:68,pmap:28,png:[5,7],po:6,point:[2,5,9,20,21,24,28,29,30,31,34,35,36,41,45,50,55,56,62,65,68,70],point_cloud_sampl:32,points_3d:31,poisson:29,poisson_disk_sampl:32,polar:49,pole:16,polici:32,polynomi:[2,36,40,49],pop:28,popul:[6,68],popular:28,pose:[2,15,38,41,65,67],pose_start:17,pose_target:17,posit:[1,6,7,10,12,15,16,20,30,34,36,40,41,51,53,60,65,67,68,71],possibl:[6,7,12,32,34,41,68],post:1,posterior:[6,7,68],postion:17,potenti:49,power:[2,3,65],pr:[1,7],practic:[0,1,2,6,68],pragma:67,precis:68,pred:20,predict:[2,7,20,44,49,53,54,61,70],predict_particl:65,prescrib:41,presenc:7,present:[7,17,68],press:[6,7],previou:[7,34,68],primarili:67,prime2:40,prime:[38,40],print:[6,65,67,68],prior:[6,7],prm:[2,49],probabilist:[2,6,8,20,21,28,49,65,66,67,68],probabilit:6,probabl:[6,7,30,31,65,68],problem:[1,12,31,54,67],proc:28,proceed:68,process:[1,7,20,21,23,32,65,67],prod_:68,profil:51,program:[12,17],project:[0,2,6,68],prone:6,propag:68,proper:17,properti:[7,8],proport:[17,34],propos:1,propto:68,provid:[1,17,21,36,55,65,67],psi_i:68,psi_j:68,pure:[2,53,61],pursuit:[2,53,61],put:6,pw:65,px:65,py:[1,17,20,28,60,68],pyplot:[6,12,28,40,65,67,68],pyreedsshepp:52,pytest:0,pythagorean:12,python3:0,python:[0,1,2,5,68],pythonrobot:[0,20,32,60,72],q0:5,q1:5,q2:5,q3:5,q:[5,7,16,20,60,65,67,68],q_:5,q_f:60,q_t:67,q_w:68,q_x:68,q_y:68,q_z:68,qiita:57,qquad:17,qsim:[65,67,68],quad:36,quadcontourset:6,quadrat:[2,61],quadrotor:4,quaternion:68,question:67,queue:28,quintic:[2,49],quit:17,r0:34,r:[0,7,12,16,20,38,40,41,60,65,67,68],r_0:34,r_:[34,36],r_d:[34,60],r_t_b:5,rad2deg:40,rad:[28,41,65,67],radian:28,rai:[2,28,29],rajamani:60,rajesh:60,rand:[6,65],randam:29,randn:[6,65,67],random:[2,7,31,32,49,65,67,68],randomli:[31,32],rang:[6,12,17,21,25,28,29,36,40,65,67,68],ransac:29,ransac_normal_vector_estim:31,rapidli:[2,49],rate:38,ratio:[31,67],raw:67,rd:[21,34],re:[12,65],read:[2,7,28,67,68],readabl:0,readi:1,readm:1,real:[5,6,28,32,53,67,70],realist:68,realli:6,rear:[2,61],reckon:[20,23,65,67,68],recognit:[2,29],record:65,recov:68,rect:34,rectangl:[2,29],rectangle_fit:34,rectangular:34,rectilinear:68,recurs:36,red:[20,21,23,25,28,44,50,53,55,56,62,65,67,68],reduc:32,reed:[2,49],ref:[22,35,43,44,45,48,50,52,54,56,60,61],refer:[3,8,15,22,29,49,61,70],reflect:21,regard:68,regul:[2,61],regular:[7,21,32],rel:[12,65,67,68],relat:[1,6,12,68],relationship:17,remain:[7,12],remov:55,rep:68,repeat:[6,31,68],replac:32,replan:45,repo:[0,6,7,68],report:2,repres:[7,21,28,32,36,40,65,67,68],represent:[7,28,30,40],requir:[1,2,68],resampl:70,resampleid:65,rescal:28,reseampl:65,reshap:[6,65],residu:[7,68],resolut:[28,30],respect:[7,17,51,65,67],respons:12,restructuredtext:1,result:[7,30,36,41,65,67,68],rf:21,rfid:[21,23,65,67,68],rh:7,rho:17,rho_1:57,rho_1d_a:57,rho_1u_a:57,rho_2:57,rho_2d_:57,rho_2u_:57,riccati:16,right:[5,7,12,21,30,34,38,40,41,68,71],rightarrow:7,rk:40,rlr:41,ro:28,road:[2,17,49],roadmap:50,robot:[0,1,2,6,7,9,15,20,21,23,24,28,34,39,43,45,48,49,53,54,56,64,65,66,67,68,69],robotacademi:12,robust:[21,31],rocket:[2,3],rod:16,roger:[6,7],rotat:[17,34,68,69],rough:[48,54],routin:36,row:23,rrt:[2,49],rrtstar:53,rsim:[65,67,68],rsl:41,rsr:41,rst:1,rstride:6,rt1:68,rt2:68,ruff:0,rule:[8,68],run:[1,65,68],runtest:1,rv:6,rx:[5,40],ry:[5,40],ryaw:40,rz:5,s:[1,7,12,16,20,21,28,34,36,38,40,41,45,65,67,68],s_:40,s_j:40,s_x:41,s_y:41,s_yaw:41,sai:7,sakai:67,same:[6,7,12,21,24,32,34,66,68],sampl:[0,1,2,6,29,35,38,40,41,42,47,49,50,52,53,60,65],save:68,scale:[7,68,71],scan:68,scanner:34,scenario:[7,44],schol:65,scholinv:65,scipi:[0,6,36],scipython:6,scope:68,screenshot:1,script:[0,1,54],scriptstyl:68,se:70,search:[2,29,49,50,53,56],search_correspond_lm_id:67,sebastian:[6,7],second:[7,12,34],secondli:68,section:[17,32],see:[0,2,7,16,17,28,65,67,68],seed:[32,68],seek:68,seen:68,segment:[29,38,41],select:[0,2,7,31,32,34,41],selected_typ:41,self:[12,62,63,65],sens:[6,7],sensor:[6,7,19,20,21,23,24,25,32,34,65,67],separ:[28,40],seper:6,sequenc:68,seri:7,set:[6,10,12,17,21,31,34,65,67,68],set_printopt:68,set_start_target_pos:17,set_titl:65,set_xtick:28,set_ytick:28,set_zlim:6,set_ztick:6,setup:1,sever:12,sf:65,sh:1,shall:68,shape:[2,6,28,29,65,67,68],share:1,sheep:49,shepp:[2,49],shift:[7,21],shortest:[41,45],should:[1,67,68],shoulder:12,show:[1,6,7,12,21,28,30,36,40,41,45,65,67,68,71],show_anim:67,shown:[7,12,16,40,65,67],shrink:12,shunichi09:57,side:[6,34,67,71],sig:65,sigma:[6,7,30,60,67,68],sigma_1:[6,7],sigma_2:[6,7],sigma_:[6,7,67],sigma_det:6,sigma_inv:6,sigma_n:6,sigma_p:6,sigma_v:[65,67],sigma_w:[65,67],sigma_x:[6,7],sigma_z:[6,7],sign:12,signal:67,signifi:34,sim:30,sim_tim:[65,67,68],similar:68,similarli:[6,68],simpl:[1,28,36,41,46,53,68],simpli:[6,12,67],simplic:68,simplifi:[5,67,68],simul:[0,1,3,4,10,11,12,17,20,21,24,39,58,59,60,62,63,64,66,68,70],simultan:[17,70],sin:[12,16,20,28,38,41,57,60,65,67,68],sinc:[6,7,12,68],singl:[6,38,65,67,68],singul:65,singular:[68,69],situat:32,six:[41,68],size:[6,12,28,32,41,65,67,68],skew:5,slam:[2,65,66],slide:57,slipperi:17,slow:17,small:[16,68],smaller:[7,31,34,36],smallest:34,smooth:[17,36,40,43],smoother:36,smoothli:[36,40],snippet:[65,68],so:[6,12,16,20,21,32,36,40,51,57,60,68],softwar:[0,2],solut:[12,16,68],solv:[1,12,32,40,49,51,54,68],solver:[38,68],some:[0,1,6,7,28,34,68],someon:1,someth:6,sonar:28,sort:40,sourc:[0,30,31,32,34,36,40,41,68,71],south:28,sp:[5,40],space:[6,16,21,31,32,40,54,68],span:49,sparser:34,specif:[17,67],specifi:[12,32],speed:[2,17,20,21,53,59,61,62,63,64],sphinx:[0,1],spiral:49,spline:[2,49],split:[28,68],spread:[6,65],springer:60,springerlink:17,sq:67,sqrt:[5,6,12,17,21,30,65,67],squar:[31,34,67,68],squre:34,srate:65,stachniss:68,stage:67,standard:[0,6,68],stanlei:[2,61],star:[0,2,45,49,67,68],start:[1,2,17,28,35,38,40,41,51,53,55,65,67],start_i:41,start_x:41,start_yaw:41,stat:6,state:[2,7,16,20,23,48,49,51,60,65,67,68],state_s:[65,67,68],stc:39,steelblu:68,steer:[2,53,61,62,63,64],step1:[29,49],step2:[29,49],step3:49,step:[6,7,17,20,21,29,31,34,41,65,68,70],step_siz:41,still:51,stochast:6,stop:[28,31],store:67,stori:6,str:65,straight:[28,41,71],straightforward:[17,68],street:44,string:[17,41,71],structur:[8,68],style:[0,1,5],subdirectori:1,subject:[16,60,68],subplot:[28,40,65,68],subplots_adjust:65,subset:68,succeed:1,success:[1,5],successiveconvexificationfreefinaltim:5,suffici:68,suggest:7,suit:1,suitabl:32,sum:[6,7,23,30,65,68],sum_:[6,36,68],summat:36,sumw:65,support:2,suppress:68,surfac:6,surpris:68,surround:67,survei:[62,63],surviv:65,sw:65,sweep:49,sx:28,sy:28,symbol:5,symmetr:67,sympi:5,system:[7,15,16,58,65,67,68],szmuk:5,t1:68,t2:68,t:[6,7,12,16,20,21,30,36,41,51,60,65,67,68],t_:36,t_i:[36,68],tabl:49,take:[6,7,17,34,67],taken:67,tan:[12,40,57,60],tangent:[12,41],tangle1:68,tangle2:68,target:[17,44,60,62],task:68,tau:38,tayer:60,tbd:18,tech:68,techniqu:[32,62,63,68],tell:[6,12],tend:[6,34],term:[7,12,17],termin:[41,51],terrain:[45,48,54],tesla:6,test:[0,1,7,67],test_a_star:1,test_codestyl:1,text:[7,36,68],textbf:20,th:[23,34,40],than:[7,12,31,32],thei:[1,6,7,28,65],them:[6,68],theorem:[7,12],theoret:67,theori:68,therefor:[6,7,67,68],theta0:12,theta1:12,theta:[12,16,17,40,41,57,65,67,68],theta_0:12,theta_1:12,theta_:[17,51,65,67],theta_engin:17,theta_go:17,theta_i:68,theta_j:68,theta_steering_wheel:17,thi:[2,4,6,7,10,12,16,17,19,20,21,23,24,25,26,27,28,30,31,32,33,34,36,37,38,39,40,42,43,44,45,46,48,50,53,54,55,56,60,65,66,67,68,69,71],thin:32,thr:30,threashold:34,three:[7,17,32,34,68],threshold:[31,34,65,67],through:[7,36,40,68,70],throught:68,thrun:[6,7],thrust:5,thu:[17,68],thumb:1,tick:[6,65,67,68],tight_layout:65,till:68,tim:66,time:[1,5,6,7,12,16,17,20,21,31,32,51,53,60,65,67,68],timestep:68,titl:[36,68],tmp1:68,tmp2:68,tmp3:68,tmp4:68,todo:68,togeth:[6,36],too:[6,7],tool:60,top:[1,16,28,34],toss:7,total:[6,7],toward:[17,21,58,68],tparticl:65,tpg:67,trace:28,track:[2,7,58,59,60,63,64,67],train:24,trajectori:[2,3,20,23,49,54,65,67,68],transform:[2,29,68],transform_point:12,transit:[7,67],translat:[17,69],transport:68,transpos:5,travel:68,travers:68,tree:[2,49],triangl:[12,29],trigonometr:12,trust:[65,67],truth:[65,67,68],tu:67,tune:17,tupl:36,turn:[7,17,41,71],tutori:[28,41,68],twice:7,two:[2,6,7,9,17,21,28,30,32,36,38,40,41,49,55,67,68],twolinkarm:12,txt:0,type:[0,31,32,36,40,41,68],typic:68,u:[5,7,16,17,20,57,60,65,67,68],u_:[7,57,60],u_a:57,u_k:60,u_t:[7,60,68],ubar:60,uc:60,uco:16,ud1:65,ud2:65,ud:[65,67,68],ukf:[24,68],unari:68,uncertain:21,uncertainti:[65,67],under:[1,6],underbrac:68,underset:68,understand:[0,1,2,67],uniform:[6,49,65],uniformli:[65,68],uniqu:[16,68],unit:[0,1,67],univari:8,univariatesplin:36,univers:[34,68],unknown:[21,28,45,51,68],unlik:[7,65],unobserv:28,unscent:[2,22],unstructur:39,until:[31,32,68],up:[1,6,12,65,67],updat:[6,7,20,68,70],update_joint:12,update_kf_with_choleski:65,update_landmark:65,update_with_observ:65,upper:[6,36],urban:[21,53,62,63,68],us:[1,2,6,7,12,16,17,20,21,23,28,29,30,31,32,36,38,40,41,45,47,48,50,51,53,54,56,60,65,66,67,68],usag:1,use_latex:5,user:[1,36],usual:68,util:[2,36],ux:5,uy:5,uz:5,v1:31,v2:31,v:[5,17,20,57,60,65,67,68],v_:[51,60,65,67],v_e:51,v_eco:51,v_esin:51,v_i:68,v_sco:51,v_ssin:51,v_st:51,v_t:[20,60,67],valid:68,valu:[6,7,12,16,17,21,28,30,31,34,36,45,65,67,68,69],var_new:6,vari:6,variabl:68,varianc:[7,8,65],variance1:6,variance2:6,variant:68,variat:[6,67],vartheta_:38,vco:[57,60],vdot:[6,40,67,68],vec:17,vecor:20,vector:[2,5,6,7,12,20,29,30,36,55,60,67,68,69],vehicl:[21,34,35,46,53,61,62,63],veloc:[7,17,20,51,60,65,67],veri:[32,65],versa:67,version:1,vertex:[55,68],vertex_id:68,vertic:[16,17,34,55,68],via:[1,53,68],vice:67,vicin:17,view:[6,68],view_init:6,viridi:6,virtual:68,visibl:[2,49],vision:17,visual:[6,68],vornoi:56,voronoi:[2,49],voxel:29,voxel_point_sampl:32,voxel_s:32,vsin:[57,60],vstack:67,vtan:60,vulner:31,vx:5,vy:5,vz:5,w1:65,w:[5,17,20,23,28,65,67,68],w_1:7,w_2:7,w_:[65,67],w_i:65,w_t:67,wa:[1,7,68],wai:[1,6,7,21,31,68],walk:[14,68,70],want:[1,7,12,21,32,68],wavefront:49,waypoint:[36,40],wb:57,wcum:65,we:[7,12,17,21,28,30,31,34,38,40,51,55,65,67,68],websit:68,weight:[6,7,21,23,36,65],welcom:60,well:[17,40,55,67,68],were:[7,67],west:28,what:[1,2,7,12,68],wheel:[2,17,43,48,54,61],when:[1,7,16,17,21,30,31,36,40,51,55,68],where:[7,12,16,20,31,32,34,38,40,41,60,65,67,68],wherea:68,whereabout:7,which:[0,6,7,12,28,31,32,34,36,38,40,41,65,67,68],why:68,wide:[0,1,2,28],widget:65,width:[5,7],wikipedia:[36,41,45,50,55],wildcard:68,window:[2,49],wise:6,within:[34,67],without:[1,7,21,32],wolfram:[6,7],women:7,won:64,word:[38,68],work:[1,7,15,28],world:[6,67,70],would:[1,6,7,12],wrist:12,wrist_i:12,wrist_x:12,write:[12,28,68],written:[0,1,12],wrong:[12,65],wx:5,wy:5,wz:5,x1:[6,68],x2:[6,68],x3:6,x:[0,5,6,7,12,16,17,20,21,23,28,30,31,34,36,38,40,41,49,57,60,65,67,68,71],x_1:[6,65,67],x_2:[6,65,67],x_3:6,x_:[7,17,20,38,40,41,51,65,67,68],x_cor:6,x_diff:17,x_e:[41,51],x_end:12,x_i:[6,68],x_j:[40,68],x_k:6,x_list:[41,71],x_n:[65,67],x_nx:67,x_nx_1:67,x_nx_2:67,x_nx_n:67,x_ny:67,x_ny_1:67,x_ny_2:67,x_ny_n:67,x_opt:68,x_po:65,x_pos2:65,x_start:12,x_state:67,x_t:[6,7,20],x_true:68,x_y:67,xaug:67,xb:40,xd:[65,67],xdist:0,xdr:[65,67,68],xe:51,xest:[20,67],xf:65,xg:67,xi:[23,40,68],xi_:23,xlabel:[40,65],xlim:6,xlist:68,xm:67,xs:51,xtrue:[65,67,68],xupdat:67,xx:67,xx_1:67,xx_2:67,xx_n:67,xy:[6,12,67],xy_1:67,xy_2:67,xy_n:67,xyre:28,xyreso:28,xytext:12,y1:68,y2:68,y:[6,7,12,16,17,20,21,28,30,31,34,36,38,40,41,49,57,60,65,67,68,71],y_1:[65,67],y_2:[65,67],y_:[17,38,40,51,65,67],y_cor:6,y_diff:17,y_e:51,y_i:[40,68],y_j:[40,68],y_list:[41,71],y_n:[65,67],y_t:20,yaw1:68,yaw2:68,yaw:[21,28,38,40,41,60,65,67,68],yaw_list:41,yawrat:[65,67],yawreso:28,ye:[12,51],yellow:28,yet:7,yi:40,yield:68,ylabel:[40,65],ylim:28,yml:0,you:[0,1,7,10,12,17,32,34,35,36,40,41,51,55,60],your:[1,7,17],ys:51,yx:67,yx_1:67,yx_2:67,yx_n:67,yy:67,yy_1:67,yy_2:67,yy_n:67,z1:68,z2:68,z:[6,7,20,21,31,60,65,67,68],z_0:60,z_:[7,20,60],z_i:65,z_k:60,z_ref:60,z_t:[6,7,65,67],zangl:67,zbar:60,zdir:6,zero:[5,6,28,65,67,68],zerodivisionerror:65,zeros_lik:36,zi:[65,67],zid:68,zlist:68,zp:[65,67]},titles:["Getting Started","How To Contribute","Welcome to PythonRobotics\u2019s documentation!","Aerial Navigation","Drone 3d trajectory following","Rocket powered landing","KF Basics - Part I","KF Basics - Part 2","Appendix","Arm Navigation","N joint arm to point control","Arm navigation with obstacle avoidance","Two joint arm to point control","Bipedal","Bipedal Planner","Control","Inverted Pendulum Control","Move to a Pose Control","Introduction","Ensamble Kalman Filter Localization","Extended Kalman Filter Localization","Histogram filter localization","Localization","Particle filter localization","Unscented Kalman Filter localization","Object shape recognition using circle fitting","Gaussian grid map","k-means object clustering","Lidar to grid map","Mapping","Normal Distance Transform (NDT) map","Normal vector estimation","Point cloud Sampling","Ray casting grid map","Object shape recognition using rectangle fitting","Bezier path planning","B-Spline planning","Bug planner","Clothoid path planning","Coverage path planner","Cubic spline planning","Dubins path planning","Dynamic Window Approach","Eta^3 Spline path planning","Optimal Trajectory in a Frenet Frame","Grid based search","Hybrid a star","LQR based path planning","Model Predictive Trajectory Generator","Path Planning","Probabilistic Road-Map (PRM) planning","Quintic polynomials planning","Reeds Shepp planning","Rapidly-Exploring Random Trees (RRT)","State Lattice Planning","Visibility Road-Map planner","Voronoi Road-Map planning","Nonlinear Model Predictive Control with C-GMRES","Linear\u2013quadratic regulator (LQR) speed and steering control","Linear\u2013quadratic regulator (LQR) steering control","Model predictive speed and steering control","Path Tracking","Pure pursuit tracking","Rear wheel feedback control","Stanley control","FastSLAM1.0","FastSLAM 2.0","EKF SLAM","Graph based SLAM","Iterative Closest Point (ICP) Matching","SLAM","Plotting Utilities","Utilities"],titleterms:{"0":[65,66],"1":[1,34,40,65,67],"1d":40,"1st":7,"2":[1,7,34,40,65,66,67,68],"2d":40,"2nd":7,"3":[1,34,40,43,65],"3d":[4,31],"4":[1,40],"5":[1,40],"class":17,"do":6,"function":[17,38],"new":1,A:45,Of:18,The:68,To:1,a_j:40,about:1,ad:1,adapt:34,add:1,advantag:6,aerial:3,algorithm:[1,7,17,18,21,45,55,65,67,68],an:1,anim:1,api:[30,31,32,34,36,40,41],appendix:8,applic:18,approach:42,approxim:36,area:34,arm:[9,10,11,12],avoid:11,b:36,b_j:40,base:[1,39,45,47,55,68],basic:[6,7,36,53],batch:53,bay:7,belief:6,bezier:35,bias:54,bidirect:45,biped:[13,14],breadth:45,bspline:36,bug:37,c:57,c_j:40,calcul:[23,31,38,40],cast:33,central:6,choos:1,circl:25,close:[34,53],closest:69,clothoid:38,cloud:32,cluster:27,code:1,condit:7,connect:55,consensu:31,constraint:40,construct:38,constructor:17,content:[2,3,8,9,13,15,22,29,49,61,70,72],continu:40,contribut:1,control:[10,12,15,16,17,57,58,59,60,63,64],correl:6,covari:[6,23],coverag:39,criteria:34,cubic:40,curv:40,curvatur:40,d:45,d_j:40,dataset:68,defect:1,definit:18,depend:7,depth:45,deriv:40,describ:6,design:20,dijkstra:[45,55],dimension:[51,68],disk:32,distanc:30,distribut:[6,30],document:[1,2],doe:17,drone:4,dubin:[41,53],dynam:42,ekf:67,ensambl:19,equat:5,estim:[21,31],eta:43,exampl:[1,7,68],exist:1,expect:6,explor:53,extend:20,farthest:32,fastslam1:65,fastslam:66,feedback:63,field:45,filter:[7,19,20,21,23,24],first:[40,45],fit:[25,34],fix:1,follow:4,formul:[57,68],frame:44,frenet:44,fresnel:38,from:[21,23],g:38,gain:7,gaussian:[6,26],gener:[5,7,48,55],get:0,gmre:57,graph:[55,68],grid:[26,28,33,39,45],histogram:21,histori:18,holonom:17,how:[0,1,17,23,40],hybrid:46,i:6,icp:69,implement:1,independ:7,indic:2,inform:53,initi:21,integr:38,interpol:36,introduct:[6,18,65,67,68],invers:12,invert:16,iter:69,joint:[10,12],k:27,kalman:[7,19,20,24],kf:[6,7],kinemat:12,land:5,lane:54,lattic:54,law:7,learn:18,lidar:28,limit:6,linear:[58,59,60],link:12,lite:45,local:[7,19,20,21,22,23,24],lookup:48,loop:53,lqr:[16,47,53,58,59],map:[26,28,29,30,33,50,55,56],match:69,mathemat:57,matplotlib:1,matrix:23,mean:27,member:17,minim:[34,68],miss:1,model:[16,20,48,57,60],motion:[20,21,51],move:17,mpc:[16,60],multimod:6,multivari:7,n:10,navig:[3,9,11],ndt:30,need:6,node:55,non:17,nonlinear:57,normal:[30,31],object:[25,27,34],observ:[20,21,67],obstacl:[11,55],one:51,oppos:6,optim:[44,48,68],paramet:[38,40],part:[6,7],particl:23,path:[35,38,39,41,43,47,48,49,53,55,61],pathfindercontrol:17,pdf:6,pendulum:16,plan:[35,36,38,40,41,43,47,49,50,51,52,54,56],planar:[12,68],planner:[14,37,39,55],plot:71,plot_curvatur:71,point:[10,12,32,40,69],poisson:32,polar:54,polygon:55,polynomi:51,pose:[17,68],posit:[17,21],potenti:45,power:5,predict:[21,48,57,60,65,67],prm:50,probabilist:[7,50],probabl:21,problem:68,project:1,properti:6,pull:1,pure:62,pursuit:62,python:18,pythonrobot:2,quadrat:[58,59],quintic:51,rai:33,randam:31,random:[6,53],rang:34,ransac:31,rapidli:53,real:68,rear:63,recognit:[25,34],rectangl:34,reed:[52,53],ref:[20,53,57],refer:[5,6,7,17,21,23,24,34,36,38,40,41,51,55,58,59,60,62,63,64,65,66,67,68,69],regul:[58,59],report:1,repres:6,represent:68,request:1,requir:0,resampl:65,review:1,road:[50,55,56],robot:[12,17,18,51],rocket:5,rrt:53,rule:7,s:[2,6,17],sampl:[31,32,48,54],se:68,search:[34,45,55],second:40,segment:34,shape:[25,34],sheep:53,shepp:52,shortest:55,simul:[5,53,65,67],slam:[67,68,70],softwar:18,solv:38,span:39,speed:[58,60],spiral:39,spline:[36,40,43],sponsor:1,stanlei:64,star:46,start:0,state:54,steer:[58,59,60],step1:[21,34,38,40,55],step2:[21,34,38,40,55],step3:[21,38,40,55],step4:[21,40],step:[1,30,67],structur:7,submit:1,support:1,sweep:39,system:17,tabl:[2,48],tangent:40,term:6,termin:40,theorem:6,thi:[0,1],through:[65,67],track:[61,62],trajectori:[4,44,48],transform:30,tree:[39,53],triangl:31,two:[12,51],uniform:54,unimod:6,unittest:1,univari:7,unknown:40,unscent:24,updat:[21,65,67],us:[0,25,34,55],util:[71,72],variabl:6,varianc:[6,34],vector:[31,40],vehicl:60,visibl:55,voronoi:56,voxel:32,walk:[65,67],wavefront:39,we:6,welcom:2,what:[0,6],wheel:63,why:6,window:42,work:17,world:68,write:1,x:51,y:51}}) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index b707b22d00..0000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Path hack to make tests work.""" -import sys -import os -import pytest - -TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(TEST_DIR) # to import this file from test code. -ROOT_DIR = os.path.dirname(TEST_DIR) -sys.path.append(ROOT_DIR) - - -def run_this_test(file): - pytest.main(args=["-W", "error", "-Werror", "--pythonwarnings=error", os.path.abspath(file)]) diff --git a/tests/test_LQR_planner.py b/tests/test_LQR_planner.py deleted file mode 100644 index be12195eac..0000000000 --- a/tests/test_LQR_planner.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.LQRPlanner import lqr_planner as m - - -def test_1(): - m.SHOW_ANIMATION = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_a_star.py b/tests/test_a_star.py deleted file mode 100644 index 82f76401ad..0000000000 --- a/tests/test_a_star.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.AStar import a_star as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_a_star_searching_two_side.py b/tests/test_a_star_searching_two_side.py deleted file mode 100644 index ad90ebc509..0000000000 --- a/tests/test_a_star_searching_two_side.py +++ /dev/null @@ -1,16 +0,0 @@ -import conftest -from PathPlanning.AStar import a_star_searching_from_two_side as m - - -def test1(): - m.show_animation = False - m.main(800) - - -def test2(): - m.show_animation = False - m.main(5000) # increase obstacle number, block path - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_a_star_variants.py b/tests/test_a_star_variants.py deleted file mode 100644 index b9ef791402..0000000000 --- a/tests/test_a_star_variants.py +++ /dev/null @@ -1,44 +0,0 @@ -import PathPlanning.AStar.a_star_variants as a_star -import conftest - - -def test_1(): - # A* with beam search - a_star.show_animation = False - - a_star.use_beam_search = True - a_star.main() - reset_all() - - # A* with iterative deepening - a_star.use_iterative_deepening = True - a_star.main() - reset_all() - - # A* with dynamic weighting - a_star.use_dynamic_weighting = True - a_star.main() - reset_all() - - # theta* - a_star.use_theta_star = True - a_star.main() - reset_all() - - # A* with jump point - a_star.use_jump_point = True - a_star.main() - reset_all() - - -def reset_all(): - a_star.show_animation = False - a_star.use_beam_search = False - a_star.use_iterative_deepening = False - a_star.use_dynamic_weighting = False - a_star.use_theta_star = False - a_star.use_jump_point = False - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_batch_informed_rrt_star.py b/tests/test_batch_informed_rrt_star.py deleted file mode 100644 index cf0a9827a8..0000000000 --- a/tests/test_batch_informed_rrt_star.py +++ /dev/null @@ -1,14 +0,0 @@ -import random - -import conftest -from PathPlanning.BatchInformedRRTStar import batch_informed_rrt_star as m - - -def test_1(): - m.show_animation = False - random.seed(12345) - m.main(maxIter=10) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_behavior_tree.py b/tests/test_behavior_tree.py deleted file mode 100644 index 0898690448..0000000000 --- a/tests/test_behavior_tree.py +++ /dev/null @@ -1,207 +0,0 @@ -import pytest -import conftest - -from MissionPlanning.BehaviorTree.behavior_tree import ( - BehaviorTreeFactory, - Status, - ActionNode, -) - - -def test_sequence_node1(): - xml_string = """ - - - - - - - - """ - bt_factory = BehaviorTreeFactory() - bt = bt_factory.build_tree(xml_string) - bt.tick() - assert bt.root.status == Status.RUNNING - assert bt.root.children[0].status == Status.SUCCESS - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - bt.tick() - bt.tick() - assert bt.root.status == Status.FAILURE - assert bt.root.children[0].status is None - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - - -def test_sequence_node2(): - xml_string = """ - - - - - - - - """ - bt_factory = BehaviorTreeFactory() - bt = bt_factory.build_tree(xml_string) - bt.tick_while_running() - assert bt.root.status == Status.SUCCESS - assert bt.root.children[0].status is None - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - - -def test_selector_node1(): - xml_string = """ - - - - - - - - """ - bt_factory = BehaviorTreeFactory() - bt = bt_factory.build_tree(xml_string) - bt.tick() - assert bt.root.status == Status.RUNNING - assert bt.root.children[0].status == Status.FAILURE - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - bt.tick() - assert bt.root.status == Status.SUCCESS - assert bt.root.children[0].status is None - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - - -def test_selector_node2(): - xml_string = """ - - - - - - - - - """ - bt_factory = BehaviorTreeFactory() - bt = bt_factory.build_tree(xml_string) - bt.tick_while_running() - assert bt.root.status == Status.FAILURE - assert bt.root.children[0].status is None - assert bt.root.children[1].status is None - - -def test_while_do_else_node(): - xml_string = """ - - - - - - """ - - class CountNode(ActionNode): - def __init__(self, name, count_threshold): - super().__init__(name) - self.count = 0 - self.count_threshold = count_threshold - - def tick(self): - self.count += 1 - if self.count >= self.count_threshold: - return Status.FAILURE - else: - return Status.SUCCESS - - bt_factory = BehaviorTreeFactory() - bt_factory.register_node_builder( - "Count", - lambda node: CountNode( - node.attrib.get("name", CountNode.__name__), - int(node.attrib["count_threshold"]), - ), - ) - bt = bt_factory.build_tree(xml_string) - bt.tick() - assert bt.root.status == Status.RUNNING - assert bt.root.children[0].status == Status.SUCCESS - assert bt.root.children[1].status is Status.SUCCESS - assert bt.root.children[2].status is None - bt.tick() - assert bt.root.status == Status.RUNNING - assert bt.root.children[0].status == Status.SUCCESS - assert bt.root.children[1].status is Status.SUCCESS - assert bt.root.children[2].status is None - bt.tick() - assert bt.root.status == Status.SUCCESS - assert bt.root.children[0].status is None - assert bt.root.children[1].status is None - assert bt.root.children[2].status is None - - -def test_node_children(): - # ControlNode Must have children - xml_string = """ - - - """ - bt_factory = BehaviorTreeFactory() - with pytest.raises(ValueError): - bt_factory.build_tree(xml_string) - - # DecoratorNode Must have child - xml_string = """ - - - """ - with pytest.raises(ValueError): - bt_factory.build_tree(xml_string) - - # DecoratorNode Must have only one child - xml_string = """ - - - - - """ - with pytest.raises(ValueError): - bt_factory.build_tree(xml_string) - - # ActionNode Must have no children - xml_string = """ - - - - """ - with pytest.raises(ValueError): - bt_factory.build_tree(xml_string) - - # WhileDoElse Must have exactly 2 or 3 children - xml_string = """ - - - - """ - with pytest.raises(ValueError): - bt = bt_factory.build_tree(xml_string) - bt.tick() - - xml_string = """ - - - - - - - """ - with pytest.raises(ValueError): - bt = bt_factory.build_tree(xml_string) - bt.tick() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_bezier_path.py b/tests/test_bezier_path.py deleted file mode 100644 index 67a5d520de..0000000000 --- a/tests/test_bezier_path.py +++ /dev/null @@ -1,16 +0,0 @@ -import conftest -from PathPlanning.BezierPath import bezier_path as m - - -def test_1(): - m.show_animation = False - m.main() - - -def test_2(): - m.show_animation = False - m.main2() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_bipedal_planner.py b/tests/test_bipedal_planner.py deleted file mode 100644 index 818957bcdc..0000000000 --- a/tests/test_bipedal_planner.py +++ /dev/null @@ -1,18 +0,0 @@ -import conftest -from Bipedal.bipedal_planner import bipedal_planner as m - - -def test_1(): - bipedal_planner = m.BipedalPlanner() - - footsteps = [[0.0, 0.2, 0.0], - [0.3, 0.2, 0.0], - [0.3, 0.2, 0.2], - [0.3, 0.2, 0.2], - [0.0, 0.2, 0.2]] - bipedal_planner.set_ref_footsteps(footsteps) - bipedal_planner.walk(plot=False) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_breadth_first_search.py b/tests/test_breadth_first_search.py deleted file mode 100644 index bfc63e39d6..0000000000 --- a/tests/test_breadth_first_search.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.BreadthFirstSearch import breadth_first_search as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_bspline_path.py b/tests/test_bspline_path.py deleted file mode 100644 index 4918c9810f..0000000000 --- a/tests/test_bspline_path.py +++ /dev/null @@ -1,74 +0,0 @@ -import conftest -import numpy as np -import pytest -from PathPlanning.BSplinePath import bspline_path - - -def test_list_input(): - way_point_x = [-1.0, 3.0, 4.0, 2.0, 1.0] - way_point_y = [0.0, -3.0, 1.0, 1.0, 3.0] - n_course_point = 50 # sampling number - - rax, ray, heading, curvature = bspline_path.approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5) - - assert len(rax) == len(ray) == len(heading) == len(curvature) - - rix, riy, heading, curvature = bspline_path.interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point) - - assert len(rix) == len(riy) == len(heading) == len(curvature) - - -def test_array_input(): - way_point_x = np.array([-1.0, 3.0, 4.0, 2.0, 1.0]) - way_point_y = np.array([0.0, -3.0, 1.0, 1.0, 3.0]) - n_course_point = 50 # sampling number - - rax, ray, heading, curvature = bspline_path.approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5) - - assert len(rax) == len(ray) == len(heading) == len(curvature) - - rix, riy, heading, curvature = bspline_path.interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point) - - assert len(rix) == len(riy) == len(heading) == len(curvature) - - -def test_degree_change(): - way_point_x = np.array([-1.0, 3.0, 4.0, 2.0, 1.0]) - way_point_y = np.array([0.0, -3.0, 1.0, 1.0, 3.0]) - n_course_point = 50 # sampling number - - rax, ray, heading, curvature = bspline_path.approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5, degree=4) - - assert len(rax) == len(ray) == len(heading) == len(curvature) - - rix, riy, heading, curvature = bspline_path.interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point, degree=4) - - assert len(rix) == len(riy) == len(heading) == len(curvature) - - rax, ray, heading, curvature = bspline_path.approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5, degree=2) - - assert len(rax) == len(ray) == len(heading) == len(curvature) - - rix, riy, heading, curvature = bspline_path.interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point, degree=2) - - assert len(rix) == len(riy) == len(heading) == len(curvature) - - with pytest.raises(ValueError): - bspline_path.approximate_b_spline_path( - way_point_x, way_point_y, n_course_point, s=0.5, degree=1) - - with pytest.raises(ValueError): - bspline_path.interpolate_b_spline_path( - way_point_x, way_point_y, n_course_point, degree=1) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_bug.py b/tests/test_bug.py deleted file mode 100644 index f94794a45e..0000000000 --- a/tests/test_bug.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.BugPlanning import bug as m - - -def test_1(): - m.show_animation = False - m.main(bug_0=True, bug_1=True, bug_2=True) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_catmull_rom_spline.py b/tests/test_catmull_rom_spline.py deleted file mode 100644 index 41a73588c3..0000000000 --- a/tests/test_catmull_rom_spline.py +++ /dev/null @@ -1,16 +0,0 @@ -import conftest -from PathPlanning.Catmull_RomSplinePath.catmull_rom_spline_path import catmull_rom_spline - -def test_catmull_rom_spline(): - way_points = [[0, 0], [1, 2], [2, 0], [3, 3]] - num_points = 100 - - spline_x, spline_y = catmull_rom_spline(way_points, num_points) - - assert spline_x.size > 0, "Spline X coordinates should not be empty" - assert spline_y.size > 0, "Spline Y coordinates should not be empty" - - assert spline_x.shape == spline_y.shape, "Spline X and Y coordinates should have the same shape" - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_cgmres_nmpc.py b/tests/test_cgmres_nmpc.py deleted file mode 100644 index db3ada5065..0000000000 --- a/tests/test_cgmres_nmpc.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathTracking.cgmres_nmpc import cgmres_nmpc as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_circle_fitting.py b/tests/test_circle_fitting.py deleted file mode 100644 index b3888d7cc3..0000000000 --- a/tests/test_circle_fitting.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from Mapping.circle_fitting import circle_fitting as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_closed_loop_rrt_star_car.py b/tests/test_closed_loop_rrt_star_car.py deleted file mode 100644 index c33e413e92..0000000000 --- a/tests/test_closed_loop_rrt_star_car.py +++ /dev/null @@ -1,13 +0,0 @@ -import conftest -from PathPlanning.ClosedLoopRRTStar import closed_loop_rrt_star_car as m -import random - - -def test_1(): - random.seed(12345) - m.show_animation = False - m.main(gx=1.0, gy=0.0, gyaw=0.0, max_iter=5) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_clothoidal_paths.py b/tests/test_clothoidal_paths.py deleted file mode 100644 index 0b038c0338..0000000000 --- a/tests/test_clothoidal_paths.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.ClothoidPath import clothoid_path_planner as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_codestyle.py b/tests/test_codestyle.py deleted file mode 100644 index 55e558c184..0000000000 --- a/tests/test_codestyle.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Diff code style checker with ruff - -This code come from: -https://github.com/scipy/scipy/blob/main/tools/lint.py - -Scipy's licence: https://github.com/scipy/scipy/blob/main/LICENSE.txt -Copyright (c) 2001-2002 Enthought, Inc. 2003-2022, SciPy Developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" -import conftest -import os -import subprocess - - -CONFIG = os.path.join( - os.path.abspath(os.path.dirname(os.path.dirname(__file__))), - 'ruff.toml', -) - -ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - - -def run_ruff(files, fix): - if not files: - return 0, "" - args = ['--fix'] if fix else [] - res = subprocess.run( - ['ruff', 'check', f'--config={CONFIG}'] + args + files, - stdout=subprocess.PIPE, - encoding='utf-8' - ) - return res.returncode, res.stdout - - -def rev_list(branch, num_commits): - """List commits in reverse chronological order. - Only the first `num_commits` are shown. - """ - res = subprocess.run( - [ - 'git', - 'rev-list', - '--max-count', - f'{num_commits}', - '--first-parent', - branch - ], - stdout=subprocess.PIPE, - encoding='utf-8', - ) - res.check_returncode() - return res.stdout.rstrip('\n').split('\n') - - -def find_branch_point(branch): - """Find when the current branch split off from the given branch. - It is based off of this Stackoverflow post: - https://stackoverflow.com/questions/1527234/finding-a-branch-point-with-git#4991675 - """ - branch_commits = rev_list('HEAD', 1000) - main_commits = set(rev_list(branch, 1000)) - for branch_commit in branch_commits: - if branch_commit in main_commits: - return branch_commit - - # If a branch split off over 1000 commits ago we will fail to find - # the ancestor. - raise RuntimeError( - 'Failed to find a common ancestor in the last 1000 commits') - - -def find_diff(sha): - """Find the diff since the given sha.""" - files = ['*.py'] - res = subprocess.run( - ['git', 'diff', '--unified=0', sha, '--'] + files, - stdout=subprocess.PIPE, - encoding='utf-8' - ) - res.check_returncode() - return res.stdout - - -def diff_files(sha): - """Find the diff since the given SHA.""" - res = subprocess.run( - ['git', 'diff', '--name-only', '--diff-filter=ACMR', '-z', sha, '--', - '*.py', '*.pyx', '*.pxd', '*.pxi'], - stdout=subprocess.PIPE, - encoding='utf-8' - ) - res.check_returncode() - return [os.path.join(ROOT_DIR, f) for f in res.stdout.split('\0') if f] - - -def test(): - branch_commit = find_branch_point("origin/master") - files = diff_files(branch_commit) - print(files) - rc, errors = run_ruff(files, fix=True) - if errors: - print(errors) - else: - print("No lint errors found.") - assert rc == 0 - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_cubature_kalman_filter.py b/tests/test_cubature_kalman_filter.py deleted file mode 100644 index 00f9d8bc5f..0000000000 --- a/tests/test_cubature_kalman_filter.py +++ /dev/null @@ -1,13 +0,0 @@ -import conftest -from Localization.cubature_kalman_filter import cubature_kalman_filter as m - - -def test1(): - m.show_final = False - m.show_animation = False - m.show_ellipse = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_d_star_lite.py b/tests/test_d_star_lite.py deleted file mode 100644 index b60a140a89..0000000000 --- a/tests/test_d_star_lite.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.DStarLite import d_star_lite as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_depth_first_search.py b/tests/test_depth_first_search.py deleted file mode 100644 index 8dd009278f..0000000000 --- a/tests/test_depth_first_search.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.DepthFirstSearch import depth_first_search as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_dijkstra.py b/tests/test_dijkstra.py deleted file mode 100644 index 40404153d8..0000000000 --- a/tests/test_dijkstra.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.Dijkstra import dijkstra as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_distance_map.py b/tests/test_distance_map.py deleted file mode 100644 index df6e394e2c..0000000000 --- a/tests/test_distance_map.py +++ /dev/null @@ -1,118 +0,0 @@ -import conftest # noqa -import numpy as np -from Mapping.DistanceMap import distance_map as m - - -def test_compute_sdf(): - """Test the computation of Signed Distance Field (SDF)""" - # Create a simple obstacle map for testing - obstacles = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) - - sdf = m.compute_sdf(obstacles) - - # Verify basic properties of SDF - assert sdf.shape == obstacles.shape, "SDF should have the same shape as input map" - assert np.all(np.isfinite(sdf)), "SDF should not contain infinite values" - - # Verify SDF value is negative at obstacle position - assert sdf[1, 1] < 0, "SDF value should be negative at obstacle position" - - # Verify SDF value is positive in free space - assert sdf[0, 0] > 0, "SDF value should be positive in free space" - - -def test_compute_udf(): - """Test the computation of Unsigned Distance Field (UDF)""" - # Create obstacle map for testing - obstacles = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) - - udf = m.compute_udf(obstacles) - - # Verify basic properties of UDF - assert udf.shape == obstacles.shape, "UDF should have the same shape as input map" - assert np.all(np.isfinite(udf)), "UDF should not contain infinite values" - assert np.all(udf >= 0), "All UDF values should be non-negative" - - # Verify UDF value is 0 at obstacle position - assert np.abs(udf[1, 1]) < 1e-10, "UDF value should be 0 at obstacle position" - - # Verify UDF value is 1 for adjacent cells - assert np.abs(udf[0, 1] - 1.0) < 1e-10, ( - "UDF value should be 1 for cells adjacent to obstacle" - ) - assert np.abs(udf[1, 0] - 1.0) < 1e-10, ( - "UDF value should be 1 for cells adjacent to obstacle" - ) - assert np.abs(udf[1, 2] - 1.0) < 1e-10, ( - "UDF value should be 1 for cells adjacent to obstacle" - ) - assert np.abs(udf[2, 1] - 1.0) < 1e-10, ( - "UDF value should be 1 for cells adjacent to obstacle" - ) - - -def test_dt(): - """Test the computation of 1D distance transform""" - # Create test data - d = np.array([m.INF, 0, m.INF]) - m.dt(d) - - # Verify distance transform results - assert np.all(np.isfinite(d)), ( - "Distance transform result should not contain infinite values" - ) - assert d[1] == 0, "Distance at obstacle position should be 0" - assert d[0] == 1, "Distance at adjacent position should be 1" - assert d[2] == 1, "Distance at adjacent position should be 1" - - -def test_compute_sdf_empty(): - """Test SDF computation with empty map""" - # Test with empty map (no obstacles) - empty_map = np.zeros((5, 5)) - sdf = m.compute_sdf(empty_map) - - assert np.all(sdf > 0), "All SDF values should be positive for empty map" - assert sdf.shape == empty_map.shape, "Output shape should match input shape" - - -def test_compute_sdf_full(): - """Test SDF computation with fully occupied map""" - # Test with fully occupied map - full_map = np.ones((5, 5)) - sdf = m.compute_sdf(full_map) - - assert np.all(sdf < 0), "All SDF values should be negative for fully occupied map" - assert sdf.shape == full_map.shape, "Output shape should match input shape" - - -def test_compute_udf_invalid_input(): - """Test UDF computation with invalid input values""" - # Test with invalid values (not 0 or 1) - invalid_map = np.array([[0, 2, 0], [0, -1, 0], [0, 0.5, 0]]) - - try: - m.compute_udf(invalid_map) - assert False, "Should raise ValueError for invalid input values" - except ValueError: - pass - - -def test_compute_udf_empty(): - """Test UDF computation with empty map""" - # Test with empty map - empty_map = np.zeros((5, 5)) - udf = m.compute_udf(empty_map) - - assert np.all(udf > 0), "All UDF values should be positive for empty map" - assert np.all(np.isfinite(udf)), "UDF should not contain infinite values" - - -def test_main(): - """Test the execution of main function""" - m.ENABLE_PLOT = False - m.main() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_drone_3d_trajectory_following.py b/tests/test_drone_3d_trajectory_following.py deleted file mode 100644 index e3fd417024..0000000000 --- a/tests/test_drone_3d_trajectory_following.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest -from AerialNavigation.drone_3d_trajectory_following \ - import drone_3d_trajectory_following as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_dstar.py b/tests/test_dstar.py deleted file mode 100644 index f8f40fecef..0000000000 --- a/tests/test_dstar.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.DStar import dstar as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_dubins_path_planning.py b/tests/test_dubins_path_planning.py deleted file mode 100644 index aceb0b752a..0000000000 --- a/tests/test_dubins_path_planning.py +++ /dev/null @@ -1,92 +0,0 @@ -import numpy as np - -import conftest -from PathPlanning.DubinsPath import dubins_path_planner - -np.random.seed(12345) - - -def check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, - end_y, end_yaw): - assert (abs(px[0] - start_x) <= 0.01) - assert (abs(py[0] - start_y) <= 0.01) - assert (abs(pyaw[0] - start_yaw) <= 0.01) - assert (abs(px[-1] - end_x) <= 0.01) - assert (abs(py[-1] - end_y) <= 0.01) - assert (abs(pyaw[-1] - end_yaw) <= 0.01) - - -def check_path_length(px, py, lengths): - path_len = sum( - [np.hypot(dx, dy) for (dx, dy) in zip(np.diff(px), np.diff(py))]) - assert (abs(path_len - sum(lengths)) <= 0.1) - - -def test_1(): - start_x = 1.0 # [m] - start_y = 1.0 # [m] - start_yaw = np.deg2rad(45.0) # [rad] - - end_x = -3.0 # [m] - end_y = -3.0 # [m] - end_yaw = np.deg2rad(-45.0) # [rad] - - curvature = 1.0 - - px, py, pyaw, mode, lengths = dubins_path_planner.plan_dubins_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature) - - check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, - end_y, end_yaw) - check_path_length(px, py, lengths) - - -def test_2(): - dubins_path_planner.show_animation = False - dubins_path_planner.main() - - -def test_3(): - N_TEST = 10 - - for i in range(N_TEST): - start_x = (np.random.rand() - 0.5) * 10.0 # [m] - start_y = (np.random.rand() - 0.5) * 10.0 # [m] - start_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad] - - end_x = (np.random.rand() - 0.5) * 10.0 # [m] - end_y = (np.random.rand() - 0.5) * 10.0 # [m] - end_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad] - - curvature = 1.0 / (np.random.rand() * 5.0) - - px, py, pyaw, mode, lengths = \ - dubins_path_planner.plan_dubins_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature) - - check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, - end_y, end_yaw) - check_path_length(px, py, lengths) - - -def test_path_plannings_types(): - dubins_path_planner.show_animation = False - start_x = 1.0 # [m] - start_y = 1.0 # [m] - start_yaw = np.deg2rad(45.0) # [rad] - - end_x = -3.0 # [m] - end_y = -3.0 # [m] - end_yaw = np.deg2rad(-45.0) # [rad] - - curvature = 1.0 - - _, _, _, mode, _ = dubins_path_planner.plan_dubins_path( - start_x, start_y, start_yaw, end_x, end_y, end_yaw, curvature, - selected_types=["RSL"]) - - assert mode == ["R", "S", "L"] - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_dynamic_movement_primitives.py b/tests/test_dynamic_movement_primitives.py deleted file mode 100644 index 3ab1c8a32c..0000000000 --- a/tests/test_dynamic_movement_primitives.py +++ /dev/null @@ -1,49 +0,0 @@ -import conftest -import numpy as np -from PathPlanning.DynamicMovementPrimitives import \ - dynamic_movement_primitives - - -def test_1(): - # test that trajectory can be learned from user-passed data - T = 5 - t = np.arange(0, T, 0.01) - sin_t = np.sin(t) - train_data = np.array([t, sin_t]).T - - DMP_controller = dynamic_movement_primitives.DMP(train_data, T) - DMP_controller.recreate_trajectory(train_data[0], train_data[-1], 4) - - -def test_2(): - # test that length of trajectory is equal to desired number of timesteps - T = 5 - t = np.arange(0, T, 0.01) - sin_t = np.sin(t) - train_data = np.array([t, sin_t]).T - - DMP_controller = dynamic_movement_primitives.DMP(train_data, T) - t, path = DMP_controller.recreate_trajectory(train_data[0], - train_data[-1], 4) - - assert(path.shape[0] == DMP_controller.timesteps) - - -def test_3(): - # check that learned trajectory is close to initial - T = 3*np.pi/2 - A_noise = 0.02 - t = np.arange(0, T, 0.01) - noisy_sin_t = np.sin(t) + A_noise*np.random.rand(len(t)) - train_data = np.array([t, noisy_sin_t]).T - - DMP_controller = dynamic_movement_primitives.DMP(train_data, T) - t, pos = DMP_controller.recreate_trajectory(train_data[0], - train_data[-1], T) - - diff = abs(pos[:, 1] - noisy_sin_t) - assert(max(diff) < 5*A_noise) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_dynamic_window_approach.py b/tests/test_dynamic_window_approach.py deleted file mode 100644 index fdb452b4a7..0000000000 --- a/tests/test_dynamic_window_approach.py +++ /dev/null @@ -1,48 +0,0 @@ -import conftest -import numpy as np - -from PathPlanning.DynamicWindowApproach import dynamic_window_approach as m - - -def test_main1(): - m.show_animation = False - m.main(gx=1.0, gy=1.0) - - -def test_main2(): - m.show_animation = False - m.main(gx=1.0, gy=1.0, robot_type=m.RobotType.rectangle) - - -def test_stuck_main(): - m.show_animation = False - # adjust cost - m.config.to_goal_cost_gain = 0.2 - m.config.obstacle_cost_gain = 2.0 - # obstacles and goals for stuck condition - m.config.ob = -1 * np.array([[-1.0, -1.0], - [0.0, 2.0], - [2.0, 6.0], - [2.0, 8.0], - [3.0, 9.27], - [3.79, 9.39], - [7.25, 8.97], - [7.0, 2.0], - [3.0, 4.0], - [6.0, 5.0], - [3.5, 5.8], - [6.0, 9.0], - [8.8, 9.0], - [5.0, 9.0], - [7.5, 3.0], - [9.0, 8.0], - [5.8, 4.4], - [12.0, 12.0], - [3.0, 2.0], - [13.0, 13.0] - ]) - m.main(gx=-5.0, gy=-7.0) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_ekf_slam.py b/tests/test_ekf_slam.py deleted file mode 100644 index 4181bb64ba..0000000000 --- a/tests/test_ekf_slam.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from SLAM.EKFSLAM import ekf_slam as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_elastic_bands.py b/tests/test_elastic_bands.py deleted file mode 100644 index ad4e13af1a..0000000000 --- a/tests/test_elastic_bands.py +++ /dev/null @@ -1,23 +0,0 @@ -import conftest -import numpy as np -from PathPlanning.ElasticBands.elastic_bands import ElasticBands - - -def test_1(): - path = np.load("PathPlanning/ElasticBands/path.npy") - obstacles_points = np.load("PathPlanning/ElasticBands/obstacles.npy") - obstacles = np.zeros((500, 500)) - for x, y in obstacles_points: - size = 30 # Side length of the square - half_size = size // 2 - x_start = max(0, x - half_size) - x_end = min(obstacles.shape[0], x + half_size) - y_start = max(0, y - half_size) - y_end = min(obstacles.shape[1], y + half_size) - obstacles[x_start:x_end, y_start:y_end] = 1 - elastic_bands = ElasticBands(path, obstacles) - elastic_bands.update_bubbles() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_eta3_spline_path.py b/tests/test_eta3_spline_path.py deleted file mode 100644 index 7fa3883ea5..0000000000 --- a/tests/test_eta3_spline_path.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.Eta3SplinePath import eta3_spline_path as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_extended_kalman_filter.py b/tests/test_extended_kalman_filter.py deleted file mode 100644 index d9ad6437a8..0000000000 --- a/tests/test_extended_kalman_filter.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from Localization.extended_kalman_filter import extended_kalman_filter as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_fast_slam1.py b/tests/test_fast_slam1.py deleted file mode 100644 index b72ab6b9ef..0000000000 --- a/tests/test_fast_slam1.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest -from SLAM.FastSLAM1 import fast_slam1 as m - - -def test1(): - m.show_animation = False - m.SIM_TIME = 3.0 - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_fast_slam2.py b/tests/test_fast_slam2.py deleted file mode 100644 index 95cdc69d42..0000000000 --- a/tests/test_fast_slam2.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest -from SLAM.FastSLAM2 import fast_slam2 as m - - -def test1(): - m.show_animation = False - m.SIM_TIME = 3.0 - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_flow_field.py b/tests/test_flow_field.py deleted file mode 100644 index d049731fe5..0000000000 --- a/tests/test_flow_field.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -import PathPlanning.FlowField.flowfield as flow_field - - -def test(): - flow_field.show_animation = False - flow_field.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_frenet_optimal_trajectory.py b/tests/test_frenet_optimal_trajectory.py deleted file mode 100644 index b8761ff4a6..0000000000 --- a/tests/test_frenet_optimal_trajectory.py +++ /dev/null @@ -1,48 +0,0 @@ -import conftest -from PathPlanning.FrenetOptimalTrajectory import frenet_optimal_trajectory as m -from PathPlanning.FrenetOptimalTrajectory.frenet_optimal_trajectory import ( - LateralMovement, - LongitudinalMovement, -) - - -def default_scenario_test(): - m.show_animation = False - m.SIM_LOOP = 5 - m.main() - - -def high_speed_and_merging_and_stopping_scenario_test(): - m.show_animation = False - m.LATERAL_MOVEMENT = LateralMovement.HIGH_SPEED - m.LONGITUDINAL_MOVEMENT = LongitudinalMovement.MERGING_AND_STOPPING - m.SIM_LOOP = 5 - m.main() - - -def high_speed_and_velocity_keeping_scenario_test(): - m.show_animation = False - m.LATERAL_MOVEMENT = LateralMovement.HIGH_SPEED - m.LONGITUDINAL_MOVEMENT = LongitudinalMovement.VELOCITY_KEEPING - m.SIM_LOOP = 5 - m.main() - - -def low_speed_and_velocity_keeping_scenario_test(): - m.show_animation = False - m.LATERAL_MOVEMENT = LateralMovement.LOW_SPEED - m.LONGITUDINAL_MOVEMENT = LongitudinalMovement.VELOCITY_KEEPING - m.SIM_LOOP = 5 - m.main() - - -def low_speed_and_merging_and_stopping_scenario_test(): - m.show_animation = False - m.LATERAL_MOVEMENT = LateralMovement.LOW_SPEED - m.LONGITUDINAL_MOVEMENT = LongitudinalMovement.MERGING_AND_STOPPING - m.SIM_LOOP = 5 - m.main() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_gaussian_grid_map.py b/tests/test_gaussian_grid_map.py deleted file mode 100644 index af1e138721..0000000000 --- a/tests/test_gaussian_grid_map.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from Mapping.gaussian_grid_map import gaussian_grid_map as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_graph_based_slam.py b/tests/test_graph_based_slam.py deleted file mode 100644 index 67d75f0f85..0000000000 --- a/tests/test_graph_based_slam.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest -from SLAM.GraphBasedSLAM import graph_based_slam as m - - -def test_1(): - m.show_animation = False - m.SIM_TIME = 20.0 - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_greedy_best_first_search.py b/tests/test_greedy_best_first_search.py deleted file mode 100644 index e573ecf625..0000000000 --- a/tests/test_greedy_best_first_search.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.GreedyBestFirstSearch import greedy_best_first_search as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_grid_based_sweep_coverage_path_planner.py b/tests/test_grid_based_sweep_coverage_path_planner.py deleted file mode 100644 index 8cbe36eb49..0000000000 --- a/tests/test_grid_based_sweep_coverage_path_planner.py +++ /dev/null @@ -1,118 +0,0 @@ -import conftest -from PathPlanning.GridBasedSweepCPP \ - import grid_based_sweep_coverage_path_planner - -grid_based_sweep_coverage_path_planner.do_animation = False -RIGHT = grid_based_sweep_coverage_path_planner. \ - SweepSearcher.MovingDirection.RIGHT -LEFT = grid_based_sweep_coverage_path_planner. \ - SweepSearcher.MovingDirection.LEFT -UP = grid_based_sweep_coverage_path_planner. \ - SweepSearcher.SweepDirection.UP -DOWN = grid_based_sweep_coverage_path_planner. \ - SweepSearcher.SweepDirection.DOWN - - -def test_planning1(): - ox = [0.0, 20.0, 50.0, 100.0, 130.0, 40.0, 0.0] - oy = [0.0, -20.0, 0.0, 30.0, 60.0, 80.0, 0.0] - resolution = 5.0 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=LEFT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=UP, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=UP, - ) - assert len(px) >= 5 - - -def test_planning2(): - ox = [0.0, 50.0, 50.0, 0.0, 0.0] - oy = [0.0, 0.0, 30.0, 30.0, 0.0] - resolution = 1.3 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=LEFT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=UP, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - -def test_planning3(): - ox = [0.0, 20.0, 50.0, 200.0, 130.0, 40.0, 0.0] - oy = [0.0, -80.0, 0.0, 30.0, 60.0, 80.0, 0.0] - resolution = 5.1 - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=LEFT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=UP, - ) - assert len(px) >= 5 - - px, py = grid_based_sweep_coverage_path_planner.planning( - ox, oy, resolution, - moving_direction=RIGHT, - sweeping_direction=DOWN, - ) - assert len(px) >= 5 - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_grid_map_lib.py b/tests/test_grid_map_lib.py deleted file mode 100644 index 670b357ad3..0000000000 --- a/tests/test_grid_map_lib.py +++ /dev/null @@ -1,40 +0,0 @@ -from Mapping.grid_map_lib.grid_map_lib import GridMap -import conftest -import numpy as np - - -def test_position_set(): - grid_map = GridMap(100, 120, 0.5, 10.0, -0.5) - - grid_map.set_value_from_xy_pos(10.1, -1.1, 1.0) - grid_map.set_value_from_xy_pos(10.1, -0.1, 1.0) - grid_map.set_value_from_xy_pos(10.1, 1.1, 1.0) - grid_map.set_value_from_xy_pos(11.1, 0.1, 1.0) - grid_map.set_value_from_xy_pos(10.1, 0.1, 1.0) - grid_map.set_value_from_xy_pos(9.1, 0.1, 1.0) - - -def test_polygon_set(): - ox = [0.0, 4.35, 20.0, 50.0, 100.0, 130.0, 40.0] - oy = [0.0, -4.15, -20.0, 0.0, 30.0, 60.0, 80.0] - - grid_map = GridMap(600, 290, 0.7, 60.0, 30.5) - - grid_map.set_value_from_polygon(ox, oy, 1.0, inside=False) - grid_map.set_value_from_polygon(np.array(ox), np.array(oy), - 1.0, inside=False) - - -def test_xy_and_grid_index_conversion(): - grid_map = GridMap(100, 120, 0.5, 10.0, -0.5) - - for x_ind in range(grid_map.width): - for y_ind in range(grid_map.height): - grid_ind = grid_map.calc_grid_index_from_xy_index(x_ind, y_ind) - x_ind_2, y_ind_2 = grid_map.calc_xy_index_from_grid_index(grid_ind) - assert x_ind == x_ind_2 - assert y_ind == y_ind_2 - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_histogram_filter.py b/tests/test_histogram_filter.py deleted file mode 100644 index 4474ead097..0000000000 --- a/tests/test_histogram_filter.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest -from Localization.histogram_filter import histogram_filter as m - - -def test1(): - m.show_animation = False - m.SIM_TIME = 1.0 - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_hybrid_a_star.py b/tests/test_hybrid_a_star.py deleted file mode 100644 index dbcf3ba9f4..0000000000 --- a/tests/test_hybrid_a_star.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.HybridAStar import hybrid_a_star as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_informed_rrt_star.py b/tests/test_informed_rrt_star.py deleted file mode 100644 index 10974ecfcb..0000000000 --- a/tests/test_informed_rrt_star.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from PathPlanning.InformedRRTStar import informed_rrt_star as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_inverted_pendulum_lqr_control.py b/tests/test_inverted_pendulum_lqr_control.py deleted file mode 100644 index 62afda71c3..0000000000 --- a/tests/test_inverted_pendulum_lqr_control.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from InvertedPendulum import inverted_pendulum_lqr_control as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_inverted_pendulum_mpc_control.py b/tests/test_inverted_pendulum_mpc_control.py deleted file mode 100644 index 94859c2e0a..0000000000 --- a/tests/test_inverted_pendulum_mpc_control.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest - -from InvertedPendulum import inverted_pendulum_mpc_control as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_iterative_closest_point.py b/tests/test_iterative_closest_point.py deleted file mode 100644 index cdfa89cc55..0000000000 --- a/tests/test_iterative_closest_point.py +++ /dev/null @@ -1,16 +0,0 @@ -import conftest -from SLAM.ICPMatching import icp_matching as m - - -def test_1(): - m.show_animation = False - m.main() - - -def test_2(): - m.show_animation = False - m.main_3d_points() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_kmeans_clustering.py b/tests/test_kmeans_clustering.py deleted file mode 100644 index 5e39d64ae6..0000000000 --- a/tests/test_kmeans_clustering.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest -from Mapping.kmeans_clustering import kmeans_clustering as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_lqr_rrt_star.py b/tests/test_lqr_rrt_star.py deleted file mode 100644 index 444b4616b8..0000000000 --- a/tests/test_lqr_rrt_star.py +++ /dev/null @@ -1,14 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.LQRRRTStar import lqr_rrt_star as m -import random - -random.seed(12345) - - -def test1(): - m.show_animation = False - m.main(maxIter=5) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_lqr_speed_steer_control.py b/tests/test_lqr_speed_steer_control.py deleted file mode 100644 index cee9759a26..0000000000 --- a/tests/test_lqr_speed_steer_control.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.lqr_speed_steer_control import lqr_speed_steer_control as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_lqr_steer_control.py b/tests/test_lqr_steer_control.py deleted file mode 100644 index 24427a8ffd..0000000000 --- a/tests/test_lqr_steer_control.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.lqr_steer_control import lqr_steer_control as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_model_predictive_speed_and_steer_control.py b/tests/test_model_predictive_speed_and_steer_control.py deleted file mode 100644 index 9bc8ccf31c..0000000000 --- a/tests/test_model_predictive_speed_and_steer_control.py +++ /dev/null @@ -1,18 +0,0 @@ -import conftest # Add root path to sys.path - -from PathTracking.model_predictive_speed_and_steer_control \ - import model_predictive_speed_and_steer_control as m - - -def test_1(): - m.show_animation = False - m.main() - - -def test_2(): - m.show_animation = False - m.main2() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_move_to_pose.py b/tests/test_move_to_pose.py deleted file mode 100644 index e06d801555..0000000000 --- a/tests/test_move_to_pose.py +++ /dev/null @@ -1,90 +0,0 @@ -import itertools -import numpy as np -import conftest # Add root path to sys.path -from PathTracking.move_to_pose import move_to_pose as m - - -def test_random(): - m.show_animation = False - m.main() - - -def test_stability(): - """ - This unit test tests the move_to_pose.py program for stability - """ - m.show_animation = False - x_start = 5 - y_start = 5 - theta_start = 0 - x_goal = 1 - y_goal = 4 - theta_goal = 0 - _, _, v_traj, w_traj = m.move_to_pose( - x_start, y_start, theta_start, x_goal, y_goal, theta_goal - ) - - def v_is_change(current, previous): - return abs(current - previous) > m.MAX_LINEAR_SPEED - - def w_is_change(current, previous): - return abs(current - previous) > m.MAX_ANGULAR_SPEED - - # Check if the speed is changing too much - window_size = 10 - count_threshold = 4 - v_change = [v_is_change(v_traj[i], v_traj[i - 1]) for i in range(1, len(v_traj))] - w_change = [w_is_change(w_traj[i], w_traj[i - 1]) for i in range(1, len(w_traj))] - for i in range(len(v_change) - window_size + 1): - v_window = v_change[i : i + window_size] - w_window = w_change[i : i + window_size] - - v_unstable = sum(v_window) > count_threshold - w_unstable = sum(w_window) > count_threshold - - assert not v_unstable, ( - f"v_unstable in window [{i}, {i + window_size}], unstable count: {sum(v_window)}" - ) - assert not w_unstable, ( - f"w_unstable in window [{i}, {i + window_size}], unstable count: {sum(w_window)}" - ) - - -def test_reach_goal(): - """ - This unit test tests the move_to_pose.py program for reaching the goal - """ - m.show_animation = False - x_start = 5 - y_start = 5 - theta_start_list = [0, np.pi / 2, np.pi, 3 * np.pi / 2] - x_goal_list = [0, 5, 10] - y_goal_list = [0, 5, 10] - theta_goal = 0 - for theta_start, x_goal, y_goal in itertools.product( - theta_start_list, x_goal_list, y_goal_list - ): - x_traj, y_traj, _, _ = m.move_to_pose( - x_start, y_start, theta_start, x_goal, y_goal, theta_goal - ) - x_diff = x_goal - x_traj[-1] - y_diff = y_goal - y_traj[-1] - rho = np.hypot(x_diff, y_diff) - assert rho < 0.001, ( - f"start:[{x_start}, {y_start}, {theta_start}], goal:[{x_goal}, {y_goal}, {theta_goal}], rho: {rho} is too large" - ) - - -def test_max_speed(): - """ - This unit test tests the move_to_pose.py program for a MAX_LINEAR_SPEED and - MAX_ANGULAR_SPEED - """ - m.show_animation = False - m.MAX_LINEAR_SPEED = 11 - m.MAX_ANGULAR_SPEED = 5 - m.main() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_move_to_pose_robot.py b/tests/test_move_to_pose_robot.py deleted file mode 100644 index 7a82f98556..0000000000 --- a/tests/test_move_to_pose_robot.py +++ /dev/null @@ -1,14 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.move_to_pose import move_to_pose as m - - -def test_1(): - """ - This unit test tests the move_to_pose_robot.py program - """ - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_mypy_type_check.py b/tests/test_mypy_type_check.py deleted file mode 100644 index 6b933c1011..0000000000 --- a/tests/test_mypy_type_check.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -import subprocess - -import conftest - -SUBPACKAGE_LIST = [ - "AerialNavigation", - "ArmNavigation", - "Bipedal", - "Localization", - "Mapping", - "PathPlanning", - "PathTracking", - "SLAM", - "InvertedPendulum" -] - - -def run_mypy(dir_name, project_path, config_path): - res = subprocess.run( - ['mypy', - '--config-file', - config_path, - '-p', - dir_name], - cwd=project_path, - stdout=subprocess.PIPE, - encoding='utf-8') - return res.returncode, res.stdout - - -def test(): - project_dir_path = os.path.dirname( - os.path.dirname(os.path.abspath(__file__))) - print(f"{project_dir_path=}") - - config_path = os.path.join(project_dir_path, "mypy.ini") - print(f"{config_path=}") - - for sub_package_name in SUBPACKAGE_LIST: - rc, errors = run_mypy(sub_package_name, project_dir_path, config_path) - if errors: - print(errors) - else: - print("No lint errors found.") - assert rc == 0 - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_n_joint_arm_to_point_control.py b/tests/test_n_joint_arm_to_point_control.py deleted file mode 100644 index 1d886d3670..0000000000 --- a/tests/test_n_joint_arm_to_point_control.py +++ /dev/null @@ -1,15 +0,0 @@ -import conftest # Add root path to sys.path -from ArmNavigation.n_joint_arm_to_point_control\ - import n_joint_arm_to_point_control as m -import random - -random.seed(12345) - - -def test1(): - m.show_animation = False - m.animation() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_normal_vector_estimation.py b/tests/test_normal_vector_estimation.py deleted file mode 100644 index 7612f22aa7..0000000000 --- a/tests/test_normal_vector_estimation.py +++ /dev/null @@ -1,19 +0,0 @@ -import conftest # Add root path to sys.path -from Mapping.normal_vector_estimation import normal_vector_estimation as m -import random - -random.seed(12345) - - -def test_1(): - m.show_animation = False - m.main1() - - -def test_2(): - m.show_animation = False - m.main2() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_particle_filter.py b/tests/test_particle_filter.py deleted file mode 100644 index 13a20f602a..0000000000 --- a/tests/test_particle_filter.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from Localization.particle_filter import particle_filter as m - - -def test_1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_point_cloud_sampling.py b/tests/test_point_cloud_sampling.py deleted file mode 100644 index 8f6447c69c..0000000000 --- a/tests/test_point_cloud_sampling.py +++ /dev/null @@ -1,15 +0,0 @@ -import conftest # Add root path to sys.path -from Mapping.point_cloud_sampling import point_cloud_sampling as m - - -def test_1(capsys): - m.do_plot = False - m.main() - captured = capsys.readouterr() - assert "voxel_sampling_points.shape=(27, 3)" in captured.out - assert "farthest_sampling_points.shape=(20, 3)" in captured.out - assert "poisson_disk_points.shape=(20, 3)" in captured.out - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_potential_field_planning.py b/tests/test_potential_field_planning.py deleted file mode 100644 index ce178d793d..0000000000 --- a/tests/test_potential_field_planning.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.PotentialFieldPlanning import potential_field_planning as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_priority_based_planner.py b/tests/test_priority_based_planner.py deleted file mode 100644 index e2ff602d88..0000000000 --- a/tests/test_priority_based_planner.py +++ /dev/null @@ -1,39 +0,0 @@ -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - NodePath, - ObstacleArrangement, - Position, -) -from PathPlanning.TimeBasedPathPlanning.BaseClasses import StartAndGoal -from PathPlanning.TimeBasedPathPlanning import PriorityBasedPlanner as m -from PathPlanning.TimeBasedPathPlanning.SafeInterval import SafeIntervalPathPlanner -import numpy as np -import conftest - - -def test_1(): - grid_side_length = 21 - - start_and_goals = [StartAndGoal(i, Position(1, i), Position(19, 19-i)) for i in range(1, 16)] - obstacle_avoid_points = [pos for item in start_and_goals for pos in (item.start, item.goal)] - - grid = Grid( - np.array([grid_side_length, grid_side_length]), - num_obstacles=250, - obstacle_avoid_points=obstacle_avoid_points, - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - ) - - m.show_animation = False - - start_and_goals: list[StartAndGoal] - paths: list[NodePath] - start_and_goals, paths = m.PriorityBasedPlanner.plan(grid, start_and_goals, SafeIntervalPathPlanner, False) - - # All paths should start at the specified position and reach the goal - for i, start_and_goal in enumerate(start_and_goals): - assert paths[i].path[0].position == start_and_goal.start - assert paths[i].path[-1].position == start_and_goal.goal - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_probabilistic_road_map.py b/tests/test_probabilistic_road_map.py deleted file mode 100644 index 6c5eb540b1..0000000000 --- a/tests/test_probabilistic_road_map.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest # Add root path to sys.path -import numpy as np -from PathPlanning.ProbabilisticRoadMap import probabilistic_road_map - - -def test1(): - probabilistic_road_map.show_animation = False - probabilistic_road_map.main(rng=np.random.default_rng(1233)) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_pure_pursuit.py b/tests/test_pure_pursuit.py deleted file mode 100644 index 89c1829514..0000000000 --- a/tests/test_pure_pursuit.py +++ /dev/null @@ -1,15 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.pure_pursuit import pure_pursuit as m - - -def test1(): - m.show_animation = False - m.main() - -def test_backward(): - m.show_animation = False - m.is_reverse_mode = True - m.main() - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_quintic_polynomials_planner.py b/tests/test_quintic_polynomials_planner.py deleted file mode 100644 index 43f3c6bada..0000000000 --- a/tests/test_quintic_polynomials_planner.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.QuinticPolynomialsPlanner import quintic_polynomials_planner as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_ray_casting_grid_map.py b/tests/test_ray_casting_grid_map.py deleted file mode 100644 index 2d192c9310..0000000000 --- a/tests/test_ray_casting_grid_map.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from Mapping.ray_casting_grid_map import ray_casting_grid_map as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rear_wheel_feedback.py b/tests/test_rear_wheel_feedback.py deleted file mode 100644 index eccde52358..0000000000 --- a/tests/test_rear_wheel_feedback.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.rear_wheel_feedback_control import rear_wheel_feedback_control as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rectangle_fitting.py b/tests/test_rectangle_fitting.py deleted file mode 100644 index cb28b6035e..0000000000 --- a/tests/test_rectangle_fitting.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from Mapping.rectangle_fitting import rectangle_fitting as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_reeds_shepp_path_planning.py b/tests/test_reeds_shepp_path_planning.py deleted file mode 100644 index 34ccfe7730..0000000000 --- a/tests/test_reeds_shepp_path_planning.py +++ /dev/null @@ -1,57 +0,0 @@ -import numpy as np - -import conftest # Add root path to sys.path -from PathPlanning.ReedsSheppPath import reeds_shepp_path_planning as m - - -def check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, - end_y, end_yaw): - assert (abs(px[0] - start_x) <= 0.01) - assert (abs(py[0] - start_y) <= 0.01) - assert (abs(pyaw[0] - start_yaw) <= 0.01) - assert (abs(px[-1] - end_x) <= 0.01) - assert (abs(py[-1] - end_y) <= 0.01) - assert (abs(pyaw[-1] - end_yaw) <= 0.01) - - -def check_path_length(px, py, lengths): - sum_len = sum(abs(length) for length in lengths) - dpx = np.diff(px) - dpy = np.diff(py) - actual_len = sum( - np.hypot(dx, dy) for (dx, dy) in zip(dpx, dpy)) - diff_len = sum_len - actual_len - assert (diff_len <= 0.01) - - -def test1(): - m.show_animation = False - m.main() - - -def test2(): - N_TEST = 10 - np.random.seed(1234) - - for i in range(N_TEST): - start_x = (np.random.rand() - 0.5) * 10.0 # [m] - start_y = (np.random.rand() - 0.5) * 10.0 # [m] - start_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad] - - end_x = (np.random.rand() - 0.5) * 10.0 # [m] - end_y = (np.random.rand() - 0.5) * 10.0 # [m] - end_yaw = np.deg2rad((np.random.rand() - 0.5) * 180.0) # [rad] - - curvature = 1.0 / (np.random.rand() * 5.0) - - px, py, pyaw, mode, lengths = m.reeds_shepp_path_planning( - start_x, start_y, start_yaw, - end_x, end_y, end_yaw, curvature) - - check_edge_condition(px, py, pyaw, start_x, start_y, start_yaw, end_x, - end_y, end_yaw) - check_path_length(px, py, lengths) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rocket_powered_landing.py b/tests/test_rocket_powered_landing.py deleted file mode 100644 index 2f294c74cf..0000000000 --- a/tests/test_rocket_powered_landing.py +++ /dev/null @@ -1,20 +0,0 @@ -import conftest # Add root path to sys.path -import numpy as np -from numpy.testing import suppress_warnings - -from AerialNavigation.rocket_powered_landing import rocket_powered_landing as m - - -def test1(): - m.show_animation = False - with suppress_warnings() as sup: - sup.filter(UserWarning, - "You are solving a parameterized problem that is not DPP" - ) - sup.filter(UserWarning, - "Solution may be inaccurate") - m.main(rng=np.random.default_rng(1234)) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt.py b/tests/test_rrt.py deleted file mode 100644 index 14a8175931..0000000000 --- a/tests/test_rrt.py +++ /dev/null @@ -1,21 +0,0 @@ -import conftest -import random - -from PathPlanning.RRT import rrt as m -from PathPlanning.RRT import rrt_with_pathsmoothing as m1 - -random.seed(12345) - - -def test1(): - m.show_animation = False - m.main(gx=1.0, gy=1.0) - - -def test2(): - m1.show_animation = False - m1.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_dubins.py b/tests/test_rrt_dubins.py deleted file mode 100644 index 66130484bc..0000000000 --- a/tests/test_rrt_dubins.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.RRTDubins import rrt_dubins as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_star.py b/tests/test_rrt_star.py deleted file mode 100644 index 232995ecb4..0000000000 --- a/tests/test_rrt_star.py +++ /dev/null @@ -1,36 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.RRTStar import rrt_star as m - - -def test1(): - m.show_animation = False - m.main() - - -def test_no_obstacle(): - obstacle_list = [] - - # Set Initial parameters - rrt_star = m.RRTStar(start=[0, 0], - goal=[6, 10], - rand_area=[-2, 15], - obstacle_list=obstacle_list) - path = rrt_star.planning(animation=False) - assert path is not None - - -def test_no_obstacle_and_robot_radius(): - obstacle_list = [] - - # Set Initial parameters - rrt_star = m.RRTStar(start=[0, 0], - goal=[6, 10], - rand_area=[-2, 15], - obstacle_list=obstacle_list, - robot_radius=0.8) - path = rrt_star.planning(animation=False) - assert path is not None - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_star_dubins.py b/tests/test_rrt_star_dubins.py deleted file mode 100644 index c55ca23e43..0000000000 --- a/tests/test_rrt_star_dubins.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.RRTStarDubins import rrt_star_dubins as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_star_reeds_shepp.py b/tests/test_rrt_star_reeds_shepp.py deleted file mode 100644 index 11157aa57a..0000000000 --- a/tests/test_rrt_star_reeds_shepp.py +++ /dev/null @@ -1,46 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.RRTStarReedsShepp import rrt_star_reeds_shepp as m - - -def test1(): - m.show_animation = False - m.main(max_iter=5) - -obstacleList = [ - (5, 5, 1), - (4, 6, 1), - (4, 8, 1), - (4, 10, 1), - (6, 5, 1), - (7, 5, 1), - (8, 6, 1), - (8, 8, 1), - (8, 10, 1) -] # [x,y,size(radius)] - -start = [0.0, 0.0, m.np.deg2rad(0.0)] -goal = [6.0, 7.0, m.np.deg2rad(90.0)] - -def test2(): - step_size = 0.2 - rrt_star_reeds_shepp = m.RRTStarReedsShepp(start, goal, - obstacleList, [-2.0, 15.0], - max_iter=100, step_size=step_size) - rrt_star_reeds_shepp.set_random_seed(seed=8) - path = rrt_star_reeds_shepp.planning(animation=False) - for i in range(len(path)-1): - # + 0.00000000000001 for acceptable errors arising from the planning process - assert m.math.dist(path[i][0:2], path[i+1][0:2]) < step_size + 0.00000000000001 - -def test_too_big_step_size(): - step_size = 20 - rrt_star_reeds_shepp = m.RRTStarReedsShepp(start, goal, - obstacleList, [-2.0, 15.0], - max_iter=100, step_size=step_size) - rrt_star_reeds_shepp.set_random_seed(seed=8) - path = rrt_star_reeds_shepp.planning(animation=False) - assert path is None - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_star_seven_joint_arm.py b/tests/test_rrt_star_seven_joint_arm.py deleted file mode 100644 index 2f6622cf6c..0000000000 --- a/tests/test_rrt_star_seven_joint_arm.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest # Add root path to sys.path -from ArmNavigation.rrt_star_seven_joint_arm_control \ - import rrt_star_seven_joint_arm_control as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_with_pathsmoothing_radius.py b/tests/test_rrt_with_pathsmoothing_radius.py deleted file mode 100644 index a1159255b5..0000000000 --- a/tests/test_rrt_with_pathsmoothing_radius.py +++ /dev/null @@ -1,48 +0,0 @@ -import conftest -import math - -from PathPlanning.RRT import rrt_with_pathsmoothing as rrt_module - -def test_smoothed_path_safety(): - # Define test environment - obstacle_list = [ - (5, 5, 1.0), - (3, 6, 2.0), - (3, 8, 2.0), - (3, 10, 2.0), - (7, 5, 2.0), - (9, 5, 2.0) - ] - robot_radius = 0.5 - - # Disable animation for testing - rrt_module.show_animation = False - - # Create RRT planner - rrt = rrt_module.RRT( - start=[0, 0], - goal=[6, 10], - rand_area=[-2, 15], - obstacle_list=obstacle_list, - robot_radius=robot_radius - ) - - # Run RRT - path = rrt.planning(animation=False) - - # Smooth the path - smoothed = rrt_module.path_smoothing(path, max_iter=1000, - obstacle_list=obstacle_list, - robot_radius=robot_radius) - - # Check if all points on the smoothed path are safely distant from obstacles - for x, y in smoothed: - for ox, oy, obs_radius in obstacle_list: - d = math.hypot(x - ox, y - oy) - min_safe_dist = obs_radius + robot_radius - assert d > min_safe_dist, \ - f"Point ({x:.2f}, {y:.2f}) too close to obstacle at ({ox}, {oy})" - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_rrt_with_sobol_sampler.py b/tests/test_rrt_with_sobol_sampler.py deleted file mode 100644 index affe2b165a..0000000000 --- a/tests/test_rrt_with_sobol_sampler.py +++ /dev/null @@ -1,14 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.RRT import rrt_with_sobol_sampler as m -import random - -random.seed(12345) - - -def test1(): - m.show_animation = False - m.main(gx=1.0, gy=1.0) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_safe_interval_path_planner.py b/tests/test_safe_interval_path_planner.py deleted file mode 100644 index bbcd4e427c..0000000000 --- a/tests/test_safe_interval_path_planner.py +++ /dev/null @@ -1,31 +0,0 @@ -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - ObstacleArrangement, - Position, -) -from PathPlanning.TimeBasedPathPlanning import SafeInterval as m -import numpy as np -import conftest - - -def test_1(): - start = Position(1, 11) - goal = Position(19, 19) - grid_side_length = 21 - grid = Grid( - np.array([grid_side_length, grid_side_length]), - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - ) - - m.show_animation = False - path = m.SafeIntervalPathPlanner.plan(grid, start, goal) - - # path should have 31 entries - assert len(path.path) == 31 - - # path should end at the goal - assert path.path[-1].position == goal - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_space_time_astar.py b/tests/test_space_time_astar.py deleted file mode 100644 index 1194c61d2e..0000000000 --- a/tests/test_space_time_astar.py +++ /dev/null @@ -1,32 +0,0 @@ -from PathPlanning.TimeBasedPathPlanning.GridWithDynamicObstacles import ( - Grid, - ObstacleArrangement, - Position, -) -from PathPlanning.TimeBasedPathPlanning import SpaceTimeAStar as m -import numpy as np -import conftest - - -def test_1(): - start = Position(1, 11) - goal = Position(19, 19) - grid_side_length = 21 - grid = Grid( - np.array([grid_side_length, grid_side_length]), - obstacle_arrangement=ObstacleArrangement.ARRANGEMENT1, - ) - - m.show_animation = False - path = m.SpaceTimeAStar.plan(grid, start, goal) - - # path should have 28 entries - assert len(path.path) == 31 - - # path should end at the goal - assert path.path[-1].position == goal - - assert path.expanded_node_count < 1000 - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_spiral_spanning_tree_coverage_path_planner.py b/tests/test_spiral_spanning_tree_coverage_path_planner.py deleted file mode 100644 index 44170fa4cc..0000000000 --- a/tests/test_spiral_spanning_tree_coverage_path_planner.py +++ /dev/null @@ -1,58 +0,0 @@ -import conftest # Add root path to sys.path -import os -import matplotlib.pyplot as plt -from PathPlanning.SpiralSpanningTreeCPP \ - import spiral_spanning_tree_coverage_path_planner - -spiral_spanning_tree_coverage_path_planner.do_animation = True - - -def spiral_stc_cpp(img, start): - num_free = 0 - for i in range(img.shape[0]): - for j in range(img.shape[1]): - num_free += img[i][j] - - STC_planner = spiral_spanning_tree_coverage_path_planner.\ - SpiralSpanningTreeCoveragePlanner(img) - - edge, route, path = STC_planner.plan(start) - - covered_nodes = set() - for p, q in edge: - covered_nodes.add(p) - covered_nodes.add(q) - - # assert complete coverage - assert len(covered_nodes) == num_free / 4 - - -def test_spiral_stc_cpp_1(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + \ - "/../PathPlanning/SpiralSpanningTreeCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test.png')) - start = (0, 0) - spiral_stc_cpp(img, start) - - -def test_spiral_stc_cpp_2(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + \ - "/../PathPlanning/SpiralSpanningTreeCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test_2.png')) - start = (10, 0) - spiral_stc_cpp(img, start) - - -def test_spiral_stc_cpp_3(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + \ - "/../PathPlanning/SpiralSpanningTreeCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test_3.png')) - start = (0, 0) - spiral_stc_cpp(img, start) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_stanley_controller.py b/tests/test_stanley_controller.py deleted file mode 100644 index bd590cb51a..0000000000 --- a/tests/test_stanley_controller.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathTracking.stanley_control import stanley_control as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_state_lattice_planner.py b/tests/test_state_lattice_planner.py deleted file mode 100644 index 0c14222e81..0000000000 --- a/tests/test_state_lattice_planner.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.StateLatticePlanner import state_lattice_planner as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_state_machine.py b/tests/test_state_machine.py deleted file mode 100644 index e36a8120fd..0000000000 --- a/tests/test_state_machine.py +++ /dev/null @@ -1,51 +0,0 @@ -import conftest - -from MissionPlanning.StateMachine.state_machine import StateMachine - - -def test_transition(): - sm = StateMachine("state_machine") - sm.add_transition(src_state="idle", event="start", dst_state="running") - sm.set_current_state("idle") - sm.process("start") - assert sm.get_current_state().name == "running" - - -def test_guard(): - class Model: - def can_start(self): - return False - - sm = StateMachine("state_machine", Model()) - sm.add_transition( - src_state="idle", event="start", dst_state="running", guard="can_start" - ) - sm.set_current_state("idle") - sm.process("start") - assert sm.get_current_state().name == "idle" - - -def test_action(): - class Model: - def on_start(self): - self.start_called = True - - model = Model() - sm = StateMachine("state_machine", model) - sm.add_transition( - src_state="idle", event="start", dst_state="running", action="on_start" - ) - sm.set_current_state("idle") - sm.process("start") - assert model.start_called - - -def test_plantuml(): - sm = StateMachine("state_machine") - sm.add_transition(src_state="idle", event="start", dst_state="running") - sm.set_current_state("idle") - assert sm.generate_plantuml() - - -if __name__ == "__main__": - conftest.run_this_test(__file__) diff --git a/tests/test_two_joint_arm_to_point_control.py b/tests/test_two_joint_arm_to_point_control.py deleted file mode 100644 index 1de4fcd805..0000000000 --- a/tests/test_two_joint_arm_to_point_control.py +++ /dev/null @@ -1,12 +0,0 @@ -import conftest # Add root path to sys.path -from ArmNavigation.two_joint_arm_to_point_control \ - import two_joint_arm_to_point_control as m - - -def test1(): - m.show_animation = False - m.animation() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_unscented_kalman_filter.py b/tests/test_unscented_kalman_filter.py deleted file mode 100644 index b7dda6e276..0000000000 --- a/tests/test_unscented_kalman_filter.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from Localization.unscented_kalman_filter import unscented_kalman_filter as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index 36861c52b4..0000000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,27 +0,0 @@ -import conftest # Add root path to sys.path -from utils import angle -from numpy.testing import assert_allclose -import numpy as np - - -def test_rot_mat_2d(): - assert_allclose(angle.rot_mat_2d(0.0), - np.array([[1., 0.], - [0., 1.]])) - - -def test_angle_mod(): - assert_allclose(angle.angle_mod(-4.0), 2.28318531) - assert(isinstance(angle.angle_mod(-4.0), float)) - assert_allclose(angle.angle_mod([-4.0]), [2.28318531]) - assert(isinstance(angle.angle_mod([-4.0]), np.ndarray)) - - assert_allclose(angle.angle_mod([-150.0, 190.0, 350], degree=True), - [-150., -170., -10.]) - - assert_allclose(angle.angle_mod(-60.0, zero_2_2pi=True, degree=True), - [300.]) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_visibility_road_map_planner.py b/tests/test_visibility_road_map_planner.py deleted file mode 100644 index 5a663d289d..0000000000 --- a/tests/test_visibility_road_map_planner.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.VoronoiRoadMap import voronoi_road_map as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_voronoi_road_map_planner.py b/tests/test_voronoi_road_map_planner.py deleted file mode 100644 index b0b7550fb2..0000000000 --- a/tests/test_voronoi_road_map_planner.py +++ /dev/null @@ -1,11 +0,0 @@ -import conftest # Add root path to sys.path -from PathPlanning.VisibilityRoadMap import visibility_road_map as m - - -def test1(): - m.show_animation = False - m.main() - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/tests/test_wavefront_coverage_path_planner.py b/tests/test_wavefront_coverage_path_planner.py deleted file mode 100644 index 28bb5987e7..0000000000 --- a/tests/test_wavefront_coverage_path_planner.py +++ /dev/null @@ -1,63 +0,0 @@ -import conftest # Add root path to sys.path -import os -import matplotlib.pyplot as plt -from PathPlanning.WavefrontCPP import wavefront_coverage_path_planner - -wavefront_coverage_path_planner.do_animation = False - - -def wavefront_cpp(img, start, goal): - num_free = 0 - for i in range(img.shape[0]): - for j in range(img.shape[1]): - num_free += 1 - img[i][j] - - DT = wavefront_coverage_path_planner.transform( - img, goal, transform_type='distance') - DT_path = wavefront_coverage_path_planner.wavefront(DT, start, goal) - assert len(DT_path) == num_free # assert complete coverage - - PT = wavefront_coverage_path_planner.transform( - img, goal, transform_type='path', alpha=0.01) - PT_path = wavefront_coverage_path_planner.wavefront(PT, start, goal) - assert len(PT_path) == num_free # assert complete coverage - - -def test_wavefront_CPP_1(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + "/../PathPlanning/WavefrontCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test.png')) - img = 1 - img - - start = (43, 0) - goal = (0, 0) - - wavefront_cpp(img, start, goal) - - -def test_wavefront_CPP_2(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + "/../PathPlanning/WavefrontCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test_2.png')) - img = 1 - img - - start = (10, 0) - goal = (10, 40) - - wavefront_cpp(img, start, goal) - - -def test_wavefront_CPP_3(): - img_dir = os.path.dirname( - os.path.abspath(__file__)) + "/../PathPlanning/WavefrontCPP" - img = plt.imread(os.path.join(img_dir, 'map', 'test_3.png')) - img = 1 - img - - start = (0, 0) - goal = (30, 30) - - wavefront_cpp(img, start, goal) - - -if __name__ == '__main__': - conftest.run_this_test(__file__) diff --git a/users_comments.md b/users_comments.md deleted file mode 100644 index 30dc2d2d85..0000000000 --- a/users_comments.md +++ /dev/null @@ -1,415 +0,0 @@ -# User's demos - -## WHILL MODEL CR with move to a pose control - -This is an electric wheelchair control demo by [Katsushun89](https://github.com/Katsushun89). - -[WHILL Model CR](https://whill.jp/model-cr) is the control target, [M5Stack](https://m5stack.com/) is used for the controller, and [toio](https://toio.io/) is used for the control input device. - -[Move to a Pose Control — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/modules/6_path_tracking/move_to_a_pose/move_to_a_pose.html) is used for its control algorithm ([code link](https://github.com/AtsushiSakai/PythonRobotics/blob/master/PathTracking/move_to_pose/move_to_pose.py)). - -![1](https://github.com/AtsushiSakai/PythonRoboticsGifs/blob/master/Users/WHILL_Model_CR_with_move_to_a_pose/WHLL_Model_CR_with_move_to_a_pose.gif) - -Reference: - -- [toioと同じように動くWHILL Model CR (in Japanese)](https://qiita.com/KatsuShun89/items/68fde7544ecfe36096d2) - - -# Educational users - -If you found users who are using PythonRobotics for education, please make an issue to let me know. - -# Stargazers location map - -You can check stargazer's locations of this project from: - -- [Stargazers location map](https://drive.google.com/open?id=1bBXS9IQmZ3Tfe1wNGvJbObRt9Htt4PGC&usp=sharing) - - -# Related projects - -This is a list of related projects. - -- [onlytailei/CppRobotics: cpp implementation of robotics algorithms including localization, mapping, SLAM, path planning and control](https://github.com/onlytailei/CppRobotics) - -# Individual users comments - ---- - -Dear AtsushiSakai,
thank you for building this cool repo for robotics.
I am still learning robotics and this will give me a good starting point.
I hope this note gets to you.

- ---David Senicic - ---- - -Dear AtsushiSakai,
WE found your project from google when first searching for a rosbag to csv converter. We are a small team in MCity working on Connected (and Automated) vehicles. We are working with an opensource platform that serve as the AI to control the vehicle. Your code and documentation helped us a lot!

Thank you! Have a nice day! - ---Hanlin(Julia) Chen, MCity Apollo team - ---- - -Dear Atsushi Sakai,

With your simplistic descriptions in text and as gifs, i was able to help my students understand easily and effectively, I would be honored to help, in anyway towards this, As a hobbyist have been developing lidar based slam navigation systems from 2011 and its only right to acknowledge and thank you for your contributions. - ---Shiva - ---- - -Dear Atsushi Sakai
I first came across your Matlab repository on ICP and SLAM. The repository for both python and Matlab helped me in understanding the essential concepts of robotics.I am really grateful to you for helping me develop my understanding of the concepts of robotics. - ---Sparsh Garg - ---- - -Dear AtsushiSakai,
Thank you very much for all the example programs related to Robotics - ---Kalyan - ---- -Dear AtsushiSakai,

Thanks for Python Robotics - ---Rebecca Li - ---- - -Thanks alot. - ---Zain - ---- - -Dear AtsushiSakai,

thank you for you code! - -—- Anonymous - ---- - -Thanks! - ---a friend - ---- - -Thanks for the awesome collection of codes! - ---Qi - ---- - -Thank you! - ---huang pingzhong - ---- - -Dear AtsushiSakai,
Thanks a lot for the wonderful collection of projects.It was really useful. I really appreciate your time in maintaing and building this repository.It will be really great if I get a chance to meet you in person sometime.I got really inspired looking at your work.
All the very best for all your future endeavors!
Thanks once again,
- ----Ezhil Bharathi - ---- - -Dear Atsushi Sakai,
Thank you so much for gathering all the useful stuff and good examples of robotics things ! :)
I am very new in this area and want to know more about robotics and SLAM things.
and again, thank you so much :)
- ---Tutorgaming - ---- - -Dear AtsushiSakai,
Very excellent work. Thank you for your share. - ---Thomas Yang - ---- - -Dear Atsushi Saka Arigato 🤗🤗 - ---Badal Kumar - ---- - -Dear AtsushiSakai,
Thanks for teaching how to implement path planning algorithms in robotics.
- ---- - -Your Github page is very useful. I will apply it with cooperative robot. - ---Soloist - ---- - -help me very much thankyou - ---sanchuan - ---- - -Dear AtsushiSakai,
U R so niubility! - ---FangKD - ---- - -thankyou AtsushiSakai! - ---ou minghua - ---- - -Dear AtsushiSakai
Thank You - ---Dogukan Altay - ---- - -so kind of you can you make videos of it. - ---- - -Dear AtshshiSakai,
You are very wise and smart that you created this library for students wanting to learn Probabilistic Robotics and actually perform robot control.
Hats off to you and your work.
I am also reading your book titled : Python Robotics - ---Himanshu - ---- - -Dear AtsushiSakai,
Hello! YOUR CODE IS SUPER SUPER HELPFUL AND GIVES ME CLEAR INSTRUCTIONs as well as STRONG SUPPORT!!
Thank you so much ! - ---Lee - ---- - -Hi AtsushiSakai,

Thanks for creating the pythonrobotics repo! It's an awesome repo that has been of great help to me. I've referenced your extended kalman filter algorithm while creating my own for localization using a 2D lidar, camera, and IMU. Our robot is destined to compete soon, so fingers crossed that all works out! Thanks again. - ---- - -You rock! - ---Matt - ---- - -Dear AtsushiSakai,
You are the best. This is by far the best tutorial for learning and implementing robotics.
Thanks a lot for your time and efforts to do this! - ---Adi B - ---- - -Dear Atushi, thank you for this amazing repository. It is a pleasure to have access to python algorithms without the burden of ROS. While I'm no longer working on such robotics projects, it's wonderful to know its available when I need it and it was amazing to see all the beautiful animations for each algorithm. - ---Shreeyak Sajjan - ---- - -Dear AtsushiSakai
Thank you for your contributions! - ---Luo - ---- - -Dear AtsushiSakai, Your visualizations are awesome, and I am going to have this link bookmarked for future references. Thank you! - ---Srinivasa Varadhan Agaram Chakravarthy - ---- - -Dear AtsushiSakai,
Thank you for this great resource! I very much appreciate all your hard work and effort. - ---Mridul Aanjaneya - ---- - -Thank you for the python robotics sample code and visualizations!! This is my new favorite thing on the internet. - ---selamg@mit.edu - ---- - -Mr AtsushiSakai ..
Your work and teaching is light for me
thank you very much for giving time and effort to make it public for us - ---Karim Anass - ---- - -Thank You - ---Anonymous - ---- - -I've learned the robotics from the traditional way of solving problem through finding packages and reading papers. This amazing project is unbelievably helpful for new-comers to learn and get familiar with the algorithms. Gods know how many hours you've taken to sort all the materials and written everything into one single coherent project. I'm truly amazed and deepest appreciation. - ---Ewing Kang - ---- - -Hey, I'm a student and I just recently got into robotics, and visited your repository multiple times. Today I was super happy to find the link to Patreon! I am impressed by didactic quality of the repo, keep up the good work! - ---Carolina Bianchi - ---- - -Dear AtsushiSakai, thanks a lot for the PythonRobotics git repository. I'm a college student who is trying to make a mini autonomous robot for my final project submission, I still haven't started using your code but by the look of it I'm sure it will be of greater use. Will let you know how my project is coming up. - ---Pragdheesh - ---- - -Hello AtsushiSakai,

Thank you very much for sharing your work! - ---Uma K - ---- - -Hey Atsushi
We are working on a multiagent simulation game and this project gave us really good insights.
In future, I would like to contribute to this project with our multiagent task allocation among robots and multi robot map merging( Which is a big hurdle as we found).
Thanks for what you are doing for open source.
- ---Mayank - ---- - -Thanks a lot for this amazing set of very useful librarires! - ---Razvan Viorel Mihai - ---- - -Dear Atsushi Sakai,

This is probably one of the best open-source robotics based Algorithms I have seen so far. Thank you for posting this knowledge with other engineers. It will definitely help soon to become engineers like myself. - ---Don - ---- - -hanks frnd, for ur contibusion - -—-- - -Thank you for python robotics!! - ---Manuel Miguez - ---- - -Great job - ---Anonymous - ---- - -Dear Atsushi Sakai
Thank you for the Python Robotics - ---Alex Liberzon - ---- - -Thanks for your robotics repo! - ---Mithi - ---- - -You made such a nice work. Congratulations :) - ---ATroya - ---- - -thank you for python path finding repo - ---fengzongbao - ---- - -Dear AtsushiSakai,

Thank you so much for making the concept of robotics approachable for the general mass. Keep up the good work :) - ---Harsh Munshi - ---- - -Benefit a lot for your GitHub repository of PythonRobotics. Thanks so much. - ---Haoran - ---- - -Thanks! - ---Reinzor - ---- - -Thanks for writing these algorithms. They are very helpful for learning robotics. - ---Arihant Lunawat - ---- - -Dear AtsushiSakai,
Thank you for providing us this great repository for playing and look around:)! - ---Hsin-Wen - ---- - -Thanks for PythonRobotics! - ---Nick V - ---- - -Dear AtsushiSakai, thank you so much for this material, it's so useful to me, i'm really glad with it =D - ---Juanda - ---- - -Dear Atsushi Thanks for compiling such a vast resource for robotics motion planning. - ---Kartik Prakash - ---- - -Thanks for your job!
I have learned a lot from it! - ---ZhongyiShen - ---- - -Dear Atsushi Sakai,
Thank you so much for creating PythonRobotics and documenting it so well. I am a senior in high school trying to learn and better myself in these concepts. - ---Rohan Mathur - - - -# Citations - -1. B. Blaga, M. Deac, R. W. Y. Al-doori, M. Negru and R. Dǎnescu, "Miniature Autonomous Vehicle Development on Raspberry Pi," 2018 IEEE 14th International Conference on Intelligent Computer Communication and Processing (ICCP), Cluj-Napoca, Romania, 2018, pp. 229-236. -doi: 10.1109/ICCP.2018.8516589 -keywords: {Automobiles;Task analysis;Autonomous vehicles;Path planning;Global Positioning System;Cameras;miniature autonomous vehicle;path planning;navigation;parking assist;lane detection and tracking;traffic sign recognition}, -URL: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8516589&isnumber=8516425 - -2. Peggy (Yuchun) Wang and Caitlin Hogan, "Path Planning with Dynamic Obstacle Avoidance for a Jumping-Enabled Robot", AA228/CS238 class report, Department of Computer Science, Stanford University, URL: https://web.stanford.edu/class/aa228/reports/2018/final113.pdf - -3. Welburn, E, Hakim Khalili, H, Gupta, A, Watson, S & Carrasco, J 2019, A Navigational System for Quadcopter Remote Inspection of Offshore Substations. in The Fifteenth International Conference on Autonomic and Autonomous Systems 2019. URL:https://research.manchester.ac.uk/portal/files/107169964/ICAS19_A_Navigational_System_for_Quadcopter_Remote_Inspection_of_Offshore_Substations.pdf - -4. E. Horváth, C. Hajdu, C. Radu and Á. Ballagi, "Range Sensor-based Occupancy Grid Mapping with Signatures," 2019 20th International Carpathian Control Conference (ICCC), Krakow-Wieliczka, Poland, 2019, pp. 1-5. -URL: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8765684&isnumber=8765679 - -5. Josie Hughes, Masaru Shimizu, and Arnoud Visser, "A Review of Robot Rescue Simulation Platforms for Robotics Education" -URL: https://2019.robocup.org/downloads/program/HughesEtAl2019.pdf - -6. Hughes, Josie, Masaru Shimizu, and Arnoud Visser. "A review of robot rescue simulation platforms for robotics education." (2019). -URL: https://www.semanticscholar.org/paper/A-Review-of-Robot-Rescue-Simulation-Platforms-for-Hughes-Shimizu/318a4bcb97a44661422ae1430d950efc408097da - -7. Ghosh, Ritwika, et al. "CyPhyHouse: A Programming, Simulation, and Deployment Toolchain for Heterogeneous Distributed Coordination." arXiv preprint arXiv:1910.01557 (2019). -URL: https://arxiv.org/abs/1910.01557 - -8. Hahn, Carsten, et al. "Dynamic Path Planning with Stable Growing Neural Gas." (2019). -URL: https://pdfs.semanticscholar.org/5c06/f3cb9542a51e1bf1a32523c1bc7fea6cecc5.pdf - -9. Brijen Thananjeyan, et al. "ABC-LMPC: Safe Sample-Based Learning MPC for Stochastic Nonlinear Dynamical Systems with Adjustable Boundary Conditions" -URL: https://arxiv.org/pdf/2003.01410 - -# Others - -- Autonomous Vehicle Readings https://richardkelley.io/readings - -- 36 Amazing Python Open Source Projects (v.2019) – Mybridge for Professionals https://medium.mybridge.co/36-amazing-python-open-source-projects-v-2019-2fe058d79450 - -- Real-time Model Predictive Control (MPC), ACADO, Python | Work-is-Playing http://grauonline.de/wordpress/?page_id=3244 - -- 💡 Greatest of GitHub - Python Robotics (Amazing!) - YouTube https://www.youtube.com/watch?v=f_pPaYx6Gu0&feature=emb_logo diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/utils/angle.py b/utils/angle.py deleted file mode 100644 index c9e02c5f2e..0000000000 --- a/utils/angle.py +++ /dev/null @@ -1,83 +0,0 @@ -import numpy as np -from scipy.spatial.transform import Rotation as Rot - - -def rot_mat_2d(angle): - """ - Create 2D rotation matrix from an angle - - Parameters - ---------- - angle : - - Returns - ------- - A 2D rotation matrix - - Examples - -------- - >>> angle_mod(-4.0) - - - """ - return Rot.from_euler('z', angle).as_matrix()[0:2, 0:2] - - -def angle_mod(x, zero_2_2pi=False, degree=False): - """ - Angle modulo operation - Default angle modulo range is [-pi, pi) - - Parameters - ---------- - x : float or array_like - A angle or an array of angles. This array is flattened for - the calculation. When an angle is provided, a float angle is returned. - zero_2_2pi : bool, optional - Change angle modulo range to [0, 2pi) - Default is False. - degree : bool, optional - If True, then the given angles are assumed to be in degrees. - Default is False. - - Returns - ------- - ret : float or ndarray - an angle or an array of modulated angle. - - Examples - -------- - >>> angle_mod(-4.0) - 2.28318531 - - >>> angle_mod([-4.0]) - np.array(2.28318531) - - >>> angle_mod([-150.0, 190.0, 350], degree=True) - array([-150., -170., -10.]) - - >>> angle_mod(-60.0, zero_2_2pi=True, degree=True) - array([300.]) - - """ - if isinstance(x, float): - is_float = True - else: - is_float = False - - x = np.asarray(x).flatten() - if degree: - x = np.deg2rad(x) - - if zero_2_2pi: - mod_angle = x % (2 * np.pi) - else: - mod_angle = (x + np.pi) % (2 * np.pi) - np.pi - - if degree: - mod_angle = np.rad2deg(mod_angle) - - if is_float: - return mod_angle.item() - else: - return mod_angle diff --git a/utils/plot.py b/utils/plot.py deleted file mode 100644 index eb5aa7ad04..0000000000 --- a/utils/plot.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -Matplotlib based plotting utilities -""" -import math -import matplotlib.pyplot as plt -import numpy as np -from mpl_toolkits.mplot3d import art3d -from matplotlib.patches import FancyArrowPatch -from mpl_toolkits.mplot3d.proj3d import proj_transform -from mpl_toolkits.mplot3d import Axes3D - -from utils.angle import rot_mat_2d - - -def plot_covariance_ellipse(x, y, cov, chi2=3.0, color="-r", ax=None): - """ - This function plots an ellipse that represents a covariance matrix. The ellipse is centered at (x, y) and its shape, size and rotation are determined by the covariance matrix. - - Parameters: - x : (float) The x-coordinate of the center of the ellipse. - y : (float) The y-coordinate of the center of the ellipse. - cov : (numpy.ndarray) A 2x2 covariance matrix that determines the shape, size, and rotation of the ellipse. - chi2 : (float, optional) A scalar value that scales the ellipse size. This value is typically set based on chi-squared distribution quantiles to achieve certain confidence levels (e.g., 3.0 corresponds to ~95% confidence for a 2D Gaussian). Defaults to 3.0. - color : (str, optional) The color and line style of the ellipse plot, following matplotlib conventions. Defaults to "-r" (a red solid line). - ax : (matplotlib.axes.Axes, optional) The Axes object to draw the ellipse on. If None (default), a new figure and axes are created. - - Returns: - None. This function plots the covariance ellipse on the specified axes. - """ - eig_val, eig_vec = np.linalg.eig(cov) - - if eig_val[0] >= eig_val[1]: - big_ind = 0 - small_ind = 1 - else: - big_ind = 1 - small_ind = 0 - a = math.sqrt(chi2 * eig_val[big_ind]) - b = math.sqrt(chi2 * eig_val[small_ind]) - angle = math.atan2(eig_vec[1, big_ind], eig_vec[0, big_ind]) - plot_ellipse(x, y, a, b, angle, color=color, ax=ax) - - -def plot_ellipse(x, y, a, b, angle, color="-r", ax=None, **kwargs): - """ - This function plots an ellipse based on the given parameters. - - Parameters - ---------- - x : (float) The x-coordinate of the center of the ellipse. - y : (float) The y-coordinate of the center of the ellipse. - a : (float) The length of the semi-major axis of the ellipse. - b : (float) The length of the semi-minor axis of the ellipse. - angle : (float) The rotation angle of the ellipse, in radians. - color : (str, optional) The color and line style of the ellipse plot, following matplotlib conventions. Defaults to "-r" (a red solid line). - ax : (matplotlib.axes.Axes, optional) The Axes object to draw the ellipse on. If None (default), a new figure and axes are created. - **kwargs: Additional keyword arguments to pass to plt.plot or ax.plot. - - Returns - --------- - None. This function plots the ellipse based on the specified parameters. - """ - - t = np.arange(0, 2 * math.pi + 0.1, 0.1) - px = [a * math.cos(it) for it in t] - py = [b * math.sin(it) for it in t] - fx = rot_mat_2d(angle) @ (np.array([px, py])) - px = np.array(fx[0, :] + x).flatten() - py = np.array(fx[1, :] + y).flatten() - if ax is None: - plt.plot(px, py, color, **kwargs) - else: - ax.plot(px, py, color, **kwargs) - - -def plot_arrow(x, y, yaw, arrow_length=1.0, - origin_point_plot_style="xr", - head_width=0.1, fc="r", ec="k", **kwargs): - """ - Plot an arrow or arrows based on 2D state (x, y, yaw) - - All optional settings of matplotlib.pyplot.arrow can be used. - - matplotlib.pyplot.arrow: - https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.arrow.html - - Parameters - ---------- - x : a float or array_like - a value or a list of arrow origin x position. - y : a float or array_like - a value or a list of arrow origin y position. - yaw : a float or array_like - a value or a list of arrow yaw angle (orientation). - arrow_length : a float (optional) - arrow length. default is 1.0 - origin_point_plot_style : str (optional) - origin point plot style. If None, not plotting. - head_width : a float (optional) - arrow head width. default is 0.1 - fc : string (optional) - face color - ec : string (optional) - edge color - """ - if not isinstance(x, float): - for (i_x, i_y, i_yaw) in zip(x, y, yaw): - plot_arrow(i_x, i_y, i_yaw, head_width=head_width, - fc=fc, ec=ec, **kwargs) - else: - plt.arrow(x, y, - arrow_length * math.cos(yaw), - arrow_length * math.sin(yaw), - head_width=head_width, - fc=fc, ec=ec, - **kwargs) - if origin_point_plot_style is not None: - plt.plot(x, y, origin_point_plot_style) - - -def plot_curvature(x_list, y_list, heading_list, curvature, - k=0.01, c="-c", label="Curvature"): - """ - Plot curvature on 2D path. This plot is a line from the original path, - the lateral distance from the original path shows curvature magnitude. - Left turning shows right side plot, right turning shows left side plot. - For straight path, the curvature plot will be on the path, because - curvature is 0 on the straight path. - - Parameters - ---------- - x_list : array_like - x position list of the path - y_list : array_like - y position list of the path - heading_list : array_like - heading list of the path - curvature : array_like - curvature list of the path - k : float - curvature scale factor to calculate distance from the original path - c : string - color of the plot - label : string - label of the plot - """ - cx = [x + d * k * np.cos(yaw - np.pi / 2.0) for x, y, yaw, d in - zip(x_list, y_list, heading_list, curvature)] - cy = [y + d * k * np.sin(yaw - np.pi / 2.0) for x, y, yaw, d in - zip(x_list, y_list, heading_list, curvature)] - - plt.plot(cx, cy, c, label=label) - for ix, iy, icx, icy in zip(x_list, y_list, cx, cy): - plt.plot([ix, icx], [iy, icy], c) - - -class Arrow3D(FancyArrowPatch): - - def __init__(self, x, y, z, dx, dy, dz, *args, **kwargs): - super().__init__((0, 0), (0, 0), *args, **kwargs) - self._xyz = (x, y, z) - self._dxdydz = (dx, dy, dz) - - def draw(self, renderer): - x1, y1, z1 = self._xyz - dx, dy, dz = self._dxdydz - x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz) - - xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M) - self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) - super().draw(renderer) - - def do_3d_projection(self, renderer=None): - x1, y1, z1 = self._xyz - dx, dy, dz = self._dxdydz - x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz) - - xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M) - self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) - - return np.min(zs) - - -def _arrow3D(ax, x, y, z, dx, dy, dz, *args, **kwargs): - '''Add an 3d arrow to an `Axes3D` instance.''' - arrow = Arrow3D(x, y, z, dx, dy, dz, *args, **kwargs) - ax.add_artist(arrow) - - -def plot_3d_vector_arrow(ax, p1, p2): - setattr(Axes3D, 'arrow3D', _arrow3D) - ax.arrow3D(p1[0], p1[1], p1[2], - p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2], - mutation_scale=20, - arrowstyle="-|>", - ) - - -def plot_triangle(p1, p2, p3, ax): - ax.add_collection3d(art3d.Poly3DCollection([[p1, p2, p3]], color='b')) - - -def set_equal_3d_axis(ax, x_lims, y_lims, z_lims): - """Helper function to set equal axis - - Args: - ax (Axes3DSubplot): matplotlib 3D axis, created by - `ax = fig.add_subplot(projection='3d')` - x_lims (np.array): array containing min and max value of x - y_lims (np.array): array containing min and max value of y - z_lims (np.array): array containing min and max value of z - """ - x_lims = np.asarray(x_lims) - y_lims = np.asarray(y_lims) - z_lims = np.asarray(z_lims) - # compute max required range - max_range = np.array([x_lims.max() - x_lims.min(), - y_lims.max() - y_lims.min(), - z_lims.max() - z_lims.min()]).max() / 2.0 - # compute mid-point along each axis - mid_x = (x_lims.max() + x_lims.min()) * 0.5 - mid_y = (y_lims.max() + y_lims.min()) * 0.5 - mid_z = (z_lims.max() + z_lims.min()) * 0.5 - - # set limits to axis - ax.set_xlim(mid_x - max_range, mid_x + max_range) - ax.set_ylim(mid_y - max_range, mid_y + max_range) - ax.set_zlim(mid_z - max_range, mid_z + max_range) - - -if __name__ == '__main__': - plot_ellipse(0, 0, 1, 2, np.deg2rad(15)) - plt.axis('equal') - plt.show() -