Potential Field Module User Guide
=================================

This comprehensive guide covers the potential field path-planning tools in ManipulaPy, optimized for Python 3.10.12.

.. note::
   This guide is written for Python 3.10.12 users and includes version-specific examples 
   and performance tips.

Introduction to Potential Fields
--------------------------------------

Potential field methods treat the robot as a point moving under the influence of an artificial
potential surface. The robot is "pulled" toward the goal by an **attractive** potential and
"pushed" away from obstacles by **repulsive** potentials. By following the negative gradient
of the combined field, the robot can find a collision-free path.

Key components:

- **Attractive potential** draws the robot toward the goal.  
- **Repulsive potential** pushes the robot away from obstacles within an influence radius.  
- **Gradient** direction of steepest descent in the combined potential.  
- **Collision checking** rapid test whether a given configuration is in collision.  

Mathematical Background
~~~~~~~~~~~~~~~~~~~~~~~

Let :math:`\mathbf{q} \in \mathbb{R}^n` be the robot's configuration (e.g., joint angles).  
Let :math:`\mathbf{q}_{\mathrm{goal}}` be the goal configuration, and let  
:math:`\{\mathbf{o}_i \in \mathbb{R}^n\}` be a set of obstacle points.

Attractive Potential
^^^^^^^^^^^^^^^^^^^^

A common quadratic attractive potential:

.. math::
   U_{\mathrm{att}}(\mathbf{q})
     = \frac{1}{2} k_{\mathrm{att}} \|\mathbf{q} - \mathbf{q}_{\mathrm{goal}}\|^2

where :math:`k_{\mathrm{att}} > 0` is the attractive gain.

Repulsive Potential
^^^^^^^^^^^^^^^^^^^

An inverse-distance repulsive potential, active only within an influence distance :math:`d_0`:

.. math::
   U_{\mathrm{rep}}(\mathbf{q})
     = \sum_{i}
       \begin{cases}
         \frac{1}{2} k_{\mathrm{rep}} \left(\frac{1}{d_i} - \frac{1}{d_0}\right)^2
           & \text{if } d_i \leq d_0,\\
         0 & \text{if } d_i > d_0,
       \end{cases}

where :math:`d_i = \|\mathbf{q} - \mathbf{o}_i\|` and :math:`k_{\mathrm{rep}} > 0`.

Total Potential and Gradient
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The combined potential:

.. math::
   U(\mathbf{q})
     = U_{\mathrm{att}}(\mathbf{q})
       + U_{\mathrm{rep}}(\mathbf{q}).

The control "force" is the negative gradient:

.. math::
   \mathbf{F}(\mathbf{q})
     = -\nabla U(\mathbf{q})
     = -\nabla U_{\mathrm{att}}(\mathbf{q})
       - \nabla U_{\mathrm{rep}}(\mathbf{q}).

Closed-form gradients:

.. math::
   \nabla U_{\mathrm{att}}(\mathbf{q})
     = k_{\mathrm{att}} (\mathbf{q} - \mathbf{q}_{\mathrm{goal}}),

.. math::
   \nabla U_{\mathrm{rep}}(\mathbf{q})
     = \sum_{i: d_i \leq d_0}
       k_{\mathrm{rep}}
       \left(\frac{1}{d_i} - \frac{1}{d_0}\right)
       \frac{1}{d_i^3} (\mathbf{q} - \mathbf{o}_i).

Class Reference
---------------

ManipulaPy provides two main classes in this module:

- **PotentialField**  
  Compute attractive & repulsive potentials and their gradients.

- **CollisionChecker**  
  Use URDF geometry to build convex hulls and test for self-collision.

Installation
~~~~~~~~~~~~

Ensure required packages are installed:

.. code-block:: bash

   pip install ManipulaPy[core] scipy urchin

Usage Examples
--------------

1. **Basic potential and gradient**

.. code-block:: python

   import numpy as np
   from ManipulaPy.potential_field import PotentialField

   # Define goal and obstacles in configuration space
   q_goal    = np.array([0.5, 0.2, -0.3])
   obstacles = [np.array([0.1, 0.0, 0.0]), np.array([0.4, 0.1, -0.2])]

   pf = PotentialField(
       attractive_gain=1.5,
       repulsive_gain=50.0,
       influence_distance=0.4
   )

   q = np.array([0.0, 0.0, 0.0])
   U_att = pf.compute_attractive_potential(q, q_goal)
   U_rep = pf.compute_repulsive_potential(q, obstacles)
   grad  = pf.compute_gradient(q, q_goal, obstacles)

   print(f"U_att = {U_att:.3f}, U_rep = {U_rep:.3f}")
   print(f"Total gradient = {grad}")

2. **Collision checking**

.. code-block:: python

   from ManipulaPy.potential_field import CollisionChecker

   cc = CollisionChecker("robot.urdf")
   q_test = np.array([0.1, -0.2, 0.3, 0.0, 0.0, 0.0])

   if cc.check_collision(q_test):
       print("Configuration is in collision!")
   else:
       print("Configuration is collision-free.")

3. **Gradient descent path planning**

.. code-block:: python

   path = []
   q = np.zeros(6)
   for _ in range(100):
       grad = pf.compute_gradient(q, q_goal, obstacles)
       q    = q - 0.05*grad  # step size
       path.append(q.copy())
       if np.linalg.norm(q - q_goal) < 1e-3:
           break

   print(f"Planned {len(path)} steps to goal")

Advanced Topics
---------------

Performance Tips
~~~~~~~~~~~~~~~~

- **Vectorize obstacle list**: stack obstacles into an :math:`(m \times n)` array and
  compute all distances at once for large :math:`m`.  
- **Tune gains**: high :math:`k_{\mathrm{rep}}` produces stronger obstacle avoidance but
  may create local minima.  
- **Cache gradient**: if you repeatedly query similar :math:`\mathbf{q}`, memoize the result.

Combining with Trajectory Planning
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can integrate the gradient from ``PotentialField`` into your
``TrajectoryPlanning`` loop to adjust intermediate waypoints:

.. code-block:: python

   from ManipulaPy.path_planning import TrajectoryPlanning

   planner = TrajectoryPlanning(robot, "robot.urdf", dynamics, joint_limits)
   traj    = planner.joint_trajectory(q_start, q_goal, Tf=2.0, N=500, method=5)

   for idx, q in enumerate(traj["positions"]):
       if cc.check_collision(q):
           grad = pf.compute_gradient(q, q_goal, obstacles)
           traj["positions"][idx] -= 0.01*grad

Troubleshooting
---------------

- **Zero repulsive gradient**  
  If your robot never "feels" obstacles, check that
  ``influence_distance`` is larger than the minimum :math:`d_i`.  

- **Local minima**  
  Potential fields can trap in local minima. Hybridize with RRT or rapidly-exploring
  random tree to escape.  

- **Performance bottleneck**  
  For many obstacles, vectorize distance computations or implement a CUDA kernel
  (see CUDA Kernels guide).

References
----------

- Latombe, J.-C., *Robot Motion Planning*, Kluwer, 1991.  
- Khatib, O., "Real-time obstacle avoidance for manipulators and mobile robots,"
  *IEEE IJRR*, 1986.  
- urchin.urdf — URDF parser for Python (used for mesh loading and convex hulls).