# Confusing Gradient Output

I got confusing output gradients from Nimble on a simple scene. The scene is about two balls with the same mass making fully elastic collision. In this scene, Nimble gives inconsistent gradients with the analytical gradients.

## Scene description

Two balls are allowed to move horizontally. There is no friction or gravity. The two balls are of the same mass 1kg and have the same radius r = 0.1m. In the beginning, the left ball at x1 = 0 (shown in blue) moves at v0 = 1m/s to the right, while the right ball at x2 = 0.52m (shown in green) has velocity u0 = 0. Since there is no friction, the blue ball would make the uniform motion. At t = 0.5s, the two balls would collide. Then the two balls would exchange their speeds since they are of the same mass and the collision is fully elastic. The blue ball would then stay still while the green ball moves at 1m/s to the right. At t = T = 1s, the green ball would appear at xf = 1.2m.

## Gradients computation

It is easy to show that the analytical form of xf w.r.t. (x1, x2, v0, u0) is:
xf = v0 T + x1 + 2r

So the analytical gradient of xf w.r.t. (x1, x2, v0, u0) is (1, 0, 1, 0).
However, the output gradients from Nimble is (0.75, 0.25, 0.91, 0.08), which is obviously inconsistent with the analytical gradients.

## Reproduce

**System configuration**:

- OS: Ubuntu 20.04 LTS
- CPU: AMD Ryzen Threadripper 3970X 32-Core Processor
- GPU: NVIDIA GeForce RTX 3090
- Nimblephysics version: 0.8.38
- Pytorch version: 1.13.0
- Python: 3.9.13

**Source code**:

```
import nimblephysics as nimble
import torch
def create_ball(radius, color):
ball = nimble.dynamics.Skeleton()
sphereJoint, sphereBody = ball.createTranslationalJoint2DAndBodyNodePair()
sphereShape = sphereBody.createShapeNode(nimble.dynamics.SphereShape(radius))
sphereVisual = sphereShape.createVisualAspect()
sphereVisual.setColor([i / 255.0 for i in color])
sphereShape.createCollisionAspect()
sphereBody.setFrictionCoeff(0.0)
sphereBody.setRestitutionCoeff(1.0)
sphereBody.setMass(1)
return ball
def create_world():
world = nimble.simulation.World()
world.setGravity([0, 0, 0]) # No gravity
radius = 0.1
world.addSkeleton(create_ball(radius, [68, 114, 196]))
world.addSkeleton(create_ball(radius, [112, 173, 71]))
return world
def simulate_and_backward(world, x1, x2, v0, u0):
# Ball 1 is initialized to be at x1 on the x-axis, with velocity v0.
# Ball 2 is initialized to be at x2 on the x-axis, with velocity u0.
# The zeros below mean that the vertical positions and velocities are all zero.
# So the balls woul only move in the horizontal direction.
init_state = torch.tensor([x1, 0, x2, 0, v0, 0, u0, 0], requires_grad=True)
control_forces = torch.zeros(4) # No external forces
total_simulation_time = 1.0 # simulate for 1 second
num_time_steps = 1000 # split into 1000 discrete small time steps
# Each time step has length 0.001 seconds
world.setTimeStep(total_simulation_time / num_time_steps)
state = init_state
states = [state]
for i in range(num_time_steps):
state = nimble.timestep(world, state, control_forces)
states.append(state)
# xf is the final x-coordinate of ball 2
xf = state[2]
xf.backward()
# The gradients on the y-axis are irrelevant, so we exclude them.
grad = (init_state.grad)[0:8:2]
print(f"xf = {xf.detach().item()}")
print(f"gradients of xf = {grad}")
return states
if __name__ == "__main__":
world = create_world()
gui = nimble.NimbleGUI(world)
gui.serve(8080)
states = simulate_and_backward(world, x1=0, x2=0.52, v0=1, u0=0)
gui.loopStates(states)
input()
gui.stopServing()
```

**Execution results**:

```
xf = 1.1989999809264922
gradients of xf = tensor([0.7500, 0.2500, 0.9197, 0.0803])
```