---
title: "Double Pendulum Trajectories"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Double Pendulum Trajectories}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
library(RandomWalker)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>",
  eval = requireNamespace("deSolve", quietly = TRUE))
```

## Model and units

`double_pendulum_walk()` models two point masses joined by rigid, massless rods
in a vertical plane. There is no friction or external forcing. Independent
normal perturbations of the initial angles create an ensemble of trajectories;
after initialization, motion is deterministic. This is not a random-waiting-time
walk. Large-angle motion can be chaotic, so small starting differences can grow.

Angles are in radians from downward vertical, angular velocities in radians per
second, lengths in meters, masses in kilograms, and time in seconds. Both angles
are absolute. The pivot is `(0, 0)` and positive y points upward.

The optional `deSolve` package is required for simulation. Install it yourself
before running these examples if it is unavailable. Plotting uses `ggplot2`;
animation additionally uses `gganimate`, and GIF rendering uses `gifski`.

## Repeatable trajectories

```{r generate}
set.seed(287)
walks <- double_pendulum_walk(.num_walks = 2, .n = 101)
head(walks)
attr(walks, "initial_states")
```

`.n` counts observations, including time zero. With `.n = 101` and the default
`.delta_time = 0.05`, the final observation is at five seconds. The default
401 observations cover 20 seconds. Sampling intervals do not constrain the
adaptive solver's internal integration steps.

`x1`, `y1` locate the first bob; `x`, `y` locate the second. These are positions,
not increments, so the generator does not add cumulative statistics.

```{r deterministic}
fixed <- double_pendulum_walk(.num_walks = 2, .n = 21, .angle_sd = 0)
```

Zero angle noise produces identical trajectories for identical initial states
and does not consume random numbers. Set `.theta1`, `.theta2`, `.omega1`, and
`.omega2` to choose other starts; masses, lengths, and gravity are configurable.

## Plot and summarize

```{r static, eval=requireNamespace("deSolve", quietly=TRUE) && requireNamespace("ggplot2", quietly=TRUE)}
plot_double_pendulum(walks, .walk = 1)
```

The spatial plot shows the second bob's path, with equal axis scaling and color
for elapsed time. `.walk` selects a walk label, not its position in the data.

```{r integration, warning=FALSE, eval=requireNamespace("deSolve", quietly=TRUE) && requireNamespace("ggplot2", quietly=TRUE)}
summarize_walks(walks, .value = x)
visualize_walks(walks, .pluck = c("x", "y"))
```

`visualize_walks()` shows coordinate traces against step number. Signed
coordinates can produce undefined geometric means in the existing summary
function; those statistics are not changed by this generator.

## Animate and save a GIF

Construction returns a customizable `gganim` object and does not render or save
files. Each sampled observation becomes a frame with both rods, bobs, a pivot,
elapsed time, and the second bob's recent trail. Set `.trail_length = 0` to hide
the trail; otherwise it counts positions including the current observation.

The following rendering example is intentionally not run during vignette builds:

```{r animation, eval=FALSE}
animation <- animate_double_pendulum(walks, .walk = 1, .trail_length = 30)
gif <- gganimate::animate(
  animation,
  nframes = attr(walks, "n"),
  fps = 1 / attr(walks, "delta_time"),
  width = 500, height = 500,
  renderer = gganimate::gifski_renderer()
)
gganimate::anim_save("double-pendulum.gif", animation = gif)
```

Use every sampled observation and the reciprocal sampling interval as the
frame rate to preserve simulated timing. Changing the frame rate changes playback
speed. The display uses discrete states with fixed spatial limits and no tweening.

## Numerical model

The implementation uses LSODA with relative and absolute tolerances of `1e-9`.
The equations follow the [double-pendulum derivation](https://www.myphysicslab.com/pendulum/double-pendulum-en.html)
with angle difference `theta1 - theta2`. This corrects the reversed difference in
the [original reference script](https://github.com/spsanderson/random_r_projects/blob/main/double_pendulum/double_pendulum.r).
Long chaotic trajectories should not be interpreted as exact forecasts.
