Metadata-Version: 2.4
Name: grays-tortuosity-model
Version: 0.1.0
Summary: Scale-dependent curvature-energy tortuosity model for sampled planar paths.
Author: Gray's Tortuosity Model contributors
License-Expression: MIT
Keywords: tortuosity,curvature,geometry,path-analysis,scale-space
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: matplotlib>=3.7
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# Gray's Tortuosity Model

This repository develops a scale-dependent tortuosity model for sampled planar paths.

The baseline model is a scale-normalized curvature energy:

```math
T_{p,\ell}[\gamma] = C_p \ell^{p-1} \int_0^L |\kappa_\ell(s)|^p ds
```

The first implemented version uses `p = 2`:

```math
T_\ell[\gamma] = \frac{T_c}{2\pi}\ell \int_0^L \kappa_\ell(s)^2 ds
```

where `ell` is the characteristic length scale of the moving object, `T_c` is the tortuosity assigned to a reference circle of radius `ell`, and `kappa_ell` is curvature after heading smoothing at scale `ell`.

## Current Status

Implemented:

- Equal-arc-length path resampling
- Segment heading calculation and unwrapping
- Causal exponential heading smoothing
- Curvature estimation from smoothed headings
- Normalized tortuosity calculation
- Analytic tests for lines and circles
- Initial polygon and stadium examples
- Four-panel closed-shape catalog generator
- Random-walk percentile catalog generator
- Circle analytic convergence figure
- Circle radius-by-scale validation figure
- Gaussian heading smoother and smoother comparison figure
- Corner-interaction figure for nearby turns
- Polygon catalog comparing numerical values to isolated-corner estimates
- Hypotrochoid and epicycloid generators and visual catalog
- Trochoid tortuosity-density profile catalog
- Local tortuosity density and windowed tortuosity profiles
- Average tortuosity, defined as total tortuosity divided by arc length
- Signed tortuosity density and curvature-based path reconstruction
- Reconstruction catalog from prescribed signed-density functions
- Signal-style signed-density reconstruction examples
- Growing oscillatory signed-density reconstruction example
- Growing oscillatory signed-density amplitude sweep
- Growing oscillatory signed-density sweep animation
- Signed-density signal amplitude atlas and response metrics
- Optional signed-density signal sweep animation
- Autoscaled signed-density signal sweep animation
- Damped oscillatory signed-density reconstruction example
- Constant-offset damped oscillatory reconstruction sweep
- Polar curve curvature and tortuosity-density catalog
- Density derivative utilities and profile catalog
- Speed-aware dynamics utilities and exposure figure
- Reference-speed traversal planner with tortuosity thresholding
- Provocation bridge utilities and memory-state figure
- Experienced load-change utility and feature figure
- Wheel-center transmission utilities and collision-check figure
- Motion-sickness bridge design roadmap
- Global geometry and curve-algebra planning notes
- Tangent-vector, affine-density, involute, and evolute curve-algebra utilities
- CSV command-line interface for path tortuosity reports and density export
- Reusable Matplotlib density-path and profile plotting helpers
- Display-only curve normalization for comparative plots with independent, global, shared-scale, fixed, grouped, pair-comparison, fixed-viewport, and auto-offset modes
- Reproducible scale-sweep utility with CSV output
- Midpoint-tangent quadratic smoothing for direct polyline edge-vector density
- Polygon-friendly tortuosity helper returning total and average tortuosity with midpoint, Gaussian, exponential, none, or custom smoothing
- Spiral divergence signatures for comparing infinite-tortuosity convergent spirals
- Complex-series partial-sum paths with direct adjacent-term density diagnostics
- Zeta Dirichlet partial-sum tail spiral diagnostics for `sigma > 1`
- Zeta vertical-curve diagnostics pairing GTM density with `|zeta|` and origin winding
- Additional signed-density reconstruction sweep for `(0.5 sin(Ax)/A + 1)tanh(x)`
- Polygon midpoint-smoothing catalog with scale and corner-fraction comparison
- Optional Riemann-Siegel `Z(t)` polar-curve tortuosity density through the first two zeros
- Long Riemann-Siegel `Z(t)` polar-density view through `t=100`
- Zeta tangent-oval radial sweep through a zero, with parameter-colored fixed-axis sheet/GIF evolution, radius diagnostics, and per-radius cache/progress reporting from `R=0.1` to `R=40`
- Zeta tangent-oval boundary-layer collapse diagnostics separating seam tortuosity, zero-contact tortuosity, and zero-contact length scales
- Zeta-zero derivative-angle scan plotting `arg(-i zeta'(rho_n))` over thousands of zeros
- Tanh orbital-bridge diagnostics relating density, radius, cumulative turn, and accumulated tortuosity

## Quick Start

Install the package in editable mode:

```bash
python -m pip install -e ".[dev]"
```

Run tests:

```bash
pytest
```

Evaluate a CSV path from the command line:

```bash
grays-tortuosity path.csv --ell 0.5 --x-column x --y-column y --density-summary --json
```

Evaluate polygon vertices directly from Python:

```python
from grays_tortuosity import polygon_tortuosity

vertices = [(0.0, 0.0), (2.0, 0.0), (2.0, 1.0), (0.0, 1.0)]
result = polygon_tortuosity(vertices, ell=0.25)
print(result.value, result.average)
```

Passing two points is treated as a straight segment and returns exactly zero total and average tortuosity.

Plot local density from Python:

```python
from grays_tortuosity import finite_difference_tortuosity_density, plot_density_path

result = finite_difference_tortuosity_density(path, ell=0.5)
fig, ax, collection = plot_density_path(result.total.points, result.density)
fig.savefig("density_path.png", dpi=180)
```

See `docs/api_quick_reference.md` for the main public APIs.

For notebook exploration, see `docs/notebooks/gtm_quickstart.ipynb`.

Smooth a polyline through its segment midpoints and compute density directly from adjacent edge vectors:

```python
from grays_tortuosity import midpoint_quadratic_tortuosity_density

result = midpoint_quadratic_tortuosity_density(polyline, ell=0.5)
print(result.total, result.density.max())
```

Compare convergent spirals by cutoff divergence instead of total tortuosity alone:

```python
from grays_tortuosity import fit_tortuosity_divergence, logarithmic_spiral_cutoff_tortuosity

values = logarithmic_spiral_cutoff_tortuosity(epsilons, pitch=0.25, ell=0.5)
fit = fit_tortuosity_divergence(epsilons, values, tail_fraction=0.4)
print(fit.delta, fit.coefficient)
```

Run examples:

```bash
python examples/core/evaluate_basic_shapes.py
python examples/core/scale_sweep_catalog.py
python examples/geometry/closed_shape_catalog.py
python examples/core/circle_scale_validation.py
python examples/core/polygon_catalog.py
python examples/core/local_tortuosity_profiles.py
python examples/core/signed_tortuosity_reconstruction.py
python examples/geometry/curve_algebra_scaling.py
python examples/geometry/density_self_intersection_criteria.py
python examples/geometry/closure_and_energy_bounds.py
python examples/geometry/tangent_vector_affine_density.py
python examples/geometry/involute_evolute_density.py
```

Additional examples are grouped under `examples/applied/`, `examples/reconstruction/`, `examples/signals_and_animation/`, `examples/polar_spiral_series/`, and `examples/research_controls/`.

## Scale Sweep

Generate a reproducible scale-sweep figure and CSV table:

```bash
python examples/core/scale_sweep_catalog.py
```

The script writes `docs/assets/scales/scale_sweep_catalog.png` and `docs/assets/scales/scale_sweep_catalog.csv`. Smooth geometric curves grow roughly linearly with `ell` for `p=2`, while response-smoothed paths with nearby opposite turns can decrease at larger `ell` because smoothing merges local turn structure.

![Scale sweep catalog](docs/assets/scales/scale_sweep_catalog.png)

## Spiral Divergence

Generate a logarithmic-spiral divergence signature figure and CSV table:

```bash
python examples/polar_spiral_series/spiral_divergence_signatures.py
```

The script writes `docs/assets/spiral/spiral_divergence_signatures.png` and `docs/assets/spiral/spiral_divergence_signatures.csv`. It compares spirals with the same divergence exponent but different coefficients, contraction per turn, and turns per e-fold.

![Spiral divergence signatures](docs/assets/spiral/spiral_divergence_signatures.png)

## Complex Series Paths

Generate a partial-sum path example for complex series terms:

```bash
python examples/polar_spiral_series/complex_series_paths.py
```

The script writes `docs/assets/spiral/complex_series_paths.png` and `docs/assets/spiral/complex_series_paths.csv`. It treats complex terms as polyline edge vectors, computes `Im(conj(a_n) a_{n+1})`, and evaluates midpoint-smoothed GTM density.

![Complex series paths](docs/assets/spiral/complex_series_paths.png)

## Zeta Research

Riemann/zeta tooling is kept as dependent research code under `research/zeta/` rather than as part of the package quickstart path.

```bash
python -m pip install -e .
python -m pip install -r research/zeta/requirements.txt
python research/zeta/scripts/zeta_curves/zeta_curve_remapping_gallery.py
python research/zeta/scripts/critical_strip/zeta_horizontal_proof_certificates.py --zero-count 2000 --samples 81 --workers 0
```

Research entry points:

- `research/zeta/scripts/README.md`: runnable zeta and Riemann-Siegel diagnostics.
- `research/zeta/docs/README.md`: current research reports and boundary notes.
- `research/zeta/assets/README.md`: curated generated figures; CSV/cache/frame outputs are ignored and regenerable.

The stable GTM package does not ship zeta-specific helpers; zeta research utilities live under `research/zeta/lib/` and use the research requirements file.

## Shape Catalog

Generate a small four-panel catalog of closed shapes and their average tortuosity values:

```bash
python examples/geometry/closed_shape_catalog.py
```

The script writes `docs/assets/closed_shapes/closed_shape_catalog.png`. The catalog uses average tortuosity so long straight sections reduce the path-level value instead of being hidden by total accumulated tortuosity.

![Closed-shape tortuosity catalog](docs/assets/closed_shapes/closed_shape_catalog.png)

Generate a six-panel catalog of non-backtracking random walks sampled at tortuosity percentiles:

```bash
python examples/applied/random_walk_percentiles.py
```

The script writes `docs/assets/dynamics/random_walk_percentiles.png`.

![Random-walk tortuosity percentile catalog](docs/assets/dynamics/random_walk_percentiles.png)

Generate the Phase 2 circle convergence check:

```bash
python examples/core/circle_convergence.py
```

The script writes `docs/assets/scales/circle_convergence.png`.

![Circle analytic convergence](docs/assets/scales/circle_convergence.png)

Generate the Phase 2 radius-by-scale validation grid:

```bash
python examples/core/circle_scale_validation.py
```

The script writes `docs/assets/scales/circle_scale_validation.png`.

![Circle scale validation](docs/assets/scales/circle_scale_validation.png)

Generate the heading smoother comparison figure:

```bash
python examples/core/smoother_comparison.py
```

The script writes `docs/assets/scales/smoother_comparison.png`.

![Heading smoother comparison](docs/assets/scales/smoother_comparison.png)

Generate the Phase 3 corner-interaction figure:

```bash
python examples/geometry/corner_interactions.py
```

The script writes `docs/assets/polygon/corner_interactions.png`.

![Corner interaction tortuosity](docs/assets/polygon/corner_interactions.png)

Generate the Phase 3 polygon catalog:

```bash
python examples/core/polygon_catalog.py
```

The script writes `docs/assets/polygon/polygon_catalog.png`.

![Polygon tortuosity catalog](docs/assets/polygon/polygon_catalog.png)

Generate the midpoint-smoothed polygon catalog:

```bash
python examples/core/polygon_midpoint_smoothing_catalog.py
```

The script writes `docs/assets/polygon/polygon_midpoint_smoothing_catalog.png` and `docs/assets/polygon/polygon_midpoint_smoothing_catalog.csv`. The strict midpoint/tangent construction has no tightness knob: endpoint locations are fixed at segment midpoints. Smaller corner fractions hug vertices more tightly, but they change the constraint and require straight connectors between rounded corners. Scaling the polygon up at fixed `ell` lowers the midpoint-smoothed quadratic total approximately like `1/scale` for `p=2`.

![Polygon midpoint smoothing catalog](docs/assets/polygon/polygon_midpoint_smoothing_catalog.png)

Generate a hypotrochoid and epicycloid catalog, including smooth hypotrochoids with `d != r`, the nonregular `d = r` hypocycloid case, average tortuosity values, and dashed Gaussian-response paths:

```bash
python examples/applied/trochoid_catalog.py
```

The script writes `docs/assets/polar/trochoid_catalog.png`.

![Trochoid catalog](docs/assets/polar/trochoid_catalog.png)

Generate the matching trochoid density-profile catalog:

```bash
python examples/applied/trochoid_density_profiles.py
```

The script writes `docs/assets/polar/trochoid_density_profiles.png`. Density is plotted against normalized arc length so the profiles remain comparable across different radii and path lengths. For nonregular cusped curves, the raw sampled spike curve is omitted from the y-axis and replaced with spike-location markers plus the raw maximum.

![Trochoid density profiles](docs/assets/polar/trochoid_density_profiles.png)

Generate local tortuosity density profiles, with blue meaning low turning load and red meaning high turning load:

```bash
python examples/core/local_tortuosity_profiles.py
```

The script writes `docs/assets/scales/local_tortuosity_profiles.png`.

![Local tortuosity profiles](docs/assets/scales/local_tortuosity_profiles.png)

Generate the sign-preserving density and reconstruction example:

```bash
python examples/core/signed_tortuosity_reconstruction.py
```

The script writes `docs/assets/reconstruction/signed_tortuosity_reconstruction.png`.

![Signed tortuosity reconstruction](docs/assets/reconstruction/signed_tortuosity_reconstruction.png)

Generate curves from prescribed signed tortuosity-density functions:

```bash
python examples/reconstruction/reconstruct_density_functions.py
```

The script writes `docs/assets/reconstruction/reconstructed_density_functions.png`.

![Reconstructed density functions](docs/assets/reconstruction/reconstructed_density_functions.png)

Compare signal-style signed-density inputs:

```bash
python examples/reconstruction/reconstruct_density_signals.py
```

The script writes `docs/assets/reconstruction/reconstructed_density_signals.png`.

![Reconstructed density signals](docs/assets/reconstruction/reconstructed_density_signals.png)

Reconstruct the growing oscillatory signed-density function `amp*x*sin(x)`:

```bash
python examples/reconstruction/reconstruct_ax_sin_density.py
```

The script writes `docs/assets/reconstruction/reconstructed_ax_sin_density.png`.

![Growing oscillatory signed-density reconstruction](docs/assets/reconstruction/reconstructed_ax_sin_density.png)

Sweep the same growing oscillatory density over `0.01 <= amp <= 0.10`:

```bash
python examples/reconstruction/reconstruct_ax_sin_density_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_ax_sin_density_sweep.png`.

![Growing oscillatory signed-density sweep](docs/assets/reconstruction/reconstructed_ax_sin_density_sweep.png)

Sweep the tanh-modulated density `(0.5 sin(Ax)/A + 1)tanh(x)` over `pi <= A <= 6pi`:

```bash
python examples/reconstruction/reconstruct_tanh_modulated_density_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_tanh_modulated_density_sweep.png` and `docs/assets/reconstruction/reconstructed_tanh_modulated_density_sweep.csv`.

![Tanh-modulated signed-density sweep](docs/assets/reconstruction/reconstructed_tanh_modulated_density_sweep.png)

Explore the orbital-bridge interpretation of the tanh-modulated density:

```bash
python examples/applied/tanh_orbital_bridge_diagnostics.py
```

The script writes `docs/assets/reconstruction/tanh_orbital_bridge_diagnostics.png` and `docs/assets/reconstruction/tanh_orbital_bridge_diagnostics.csv`. It compares reconstructed paths, prescribed density, instantaneous radius `R=1/|kappa|`, cumulative turn, cumulative tortuosity, and the phase relation between turn and accumulated tortuosity.

![Tanh orbital bridge diagnostics](docs/assets/reconstruction/tanh_orbital_bridge_diagnostics.png)

Generate an animated version of the tanh-modulated sweep:

```bash
python examples/signals_and_animation/animate_tanh_modulated_density_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_tanh_modulated_density_sweep.gif`.

![Tanh-modulated signed-density sweep animation](docs/assets/reconstruction/reconstructed_tanh_modulated_density_sweep.gif)

Generate an animated version of the same sweep:

```bash
python examples/signals_and_animation/animate_ax_sin_density_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_ax_sin_density_sweep.gif`.

![Growing oscillatory signed-density sweep animation](docs/assets/reconstruction/reconstructed_ax_sin_density_sweep.gif)

Sweep signal amplitude up to `amp=2.0`. The atlas includes the delayed-step case, while the response plot focuses on the periodic signals so the trivial Heaviside response does not dominate the axes:

```bash
python examples/reconstruction/reconstruct_density_signal_atlas.py
python examples/reconstruction/reconstruct_density_signal_response.py
```

The scripts write `docs/assets/reconstruction/reconstructed_density_signal_amplitude_atlas.png` and `docs/assets/reconstruction/reconstructed_density_signal_response.png`.

![Signed-density signal amplitude atlas](docs/assets/reconstruction/reconstructed_density_signal_amplitude_atlas.png)

![Signed-density signal amplitude response](docs/assets/reconstruction/reconstructed_density_signal_response.png)

Generate an optional GIF animation of the periodic amplitude sweep:

```bash
python examples/signals_and_animation/animate_density_signal_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_density_signal_sweep.gif`.

Generate a second autoscaled GIF that refits each periodic curve every frame so small folded motifs remain visible:

```bash
python examples/signals_and_animation/animate_density_signal_sweep_autoscale.py
```

The script writes `docs/assets/reconstruction/reconstructed_density_signal_sweep_autoscale.gif`.

![Autoscaled signed-density signal sweep](docs/assets/reconstruction/reconstructed_density_signal_sweep_autoscale.gif)

Reconstruct the damped oscillatory signed-density function `sin(4x) exp(-(x/4)^2)`:

```bash
python examples/reconstruction/reconstruct_damped_sinusoid_density.py
```

The script writes `docs/assets/reconstruction/reconstructed_damped_sinusoid_density.png`.

![Damped sinusoid signed-density reconstruction](docs/assets/reconstruction/reconstructed_damped_sinusoid_density.png)

Sweep a constant offset in `sin(4x) exp(-(x/4)^2) + C` over `0 <= x <= 10`, moving from a disturbed line-like case to disturbed circle-like cases:

```bash
python examples/reconstruction/reconstruct_damped_sinusoid_offset_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_damped_sinusoid_offset_sweep.png`.

![Damped sinusoid offset sweep](docs/assets/reconstruction/reconstructed_damped_sinusoid_offset_sweep.png)

Generate an animated version of the same constant-offset sweep:

```bash
python examples/signals_and_animation/animate_damped_sinusoid_offset_sweep.py
```

The script writes `docs/assets/reconstruction/reconstructed_damped_sinusoid_offset_sweep.gif`.

![Damped sinusoid offset sweep animation](docs/assets/reconstruction/reconstructed_damped_sinusoid_offset_sweep.gif)

Generate the polar curve catalog comparing curvature, density per arc length, and accumulation per polar angle:

```bash
python examples/polar_spiral_series/polar_curve_catalog.py
```

The script writes `docs/assets/polar/polar_curve_catalog.png`.

![Polar curve catalog](docs/assets/polar/polar_curve_catalog.png)

Generate the density-derivative profile catalog:

```bash
python examples/geometry/density_derivative_profiles.py
```

The script writes `docs/assets/dynamics/density_derivative_profiles.png`.

![Density derivative profiles](docs/assets/dynamics/density_derivative_profiles.png)

Generate the speed-aware dynamics example:

```bash
python examples/applied/speed_aware_dynamics.py
```

The script writes `docs/assets/dynamics/speed_aware_dynamics.png`.

![Speed-aware dynamics](docs/assets/dynamics/speed_aware_dynamics.png)

Generate the tortuosity-constrained traversal planning example:

```bash
python examples/applied/tortuosity_constrained_traversal.py
```

The script writes `docs/assets/dynamics/tortuosity_constrained_traversal.png`.

![Tortuosity-constrained traversal](docs/assets/dynamics/tortuosity_constrained_traversal.png)

Generate the spatiotemporal scale-frequency bridge example:

```bash
python examples/applied/spatiotemporal_scale_frequency_bridge.py
```

The script writes `docs/assets/dynamics/spatiotemporal_scale_frequency_bridge.png` and shows a scale-space density map `Q(s, ell)` together with speed-dependent temporal frequencies from `f=v/lambda`.

![Spatiotemporal scale-frequency bridge](docs/assets/dynamics/spatiotemporal_scale_frequency_bridge.png)

Generate the curve algebra and scaling example:

```bash
python examples/geometry/curve_algebra_scaling.py
```

The script writes `docs/assets/closed_shapes/curve_algebra_scaling.png` and shows the scalar multiplication law, synchronized curve-addition interference, and tangent-cancellation singularity.

![Curve algebra and scaling](docs/assets/closed_shapes/curve_algebra_scaling.png)

Generate the density/self-intersection criteria example:

```bash
python examples/geometry/density_self_intersection_criteria.py
```

The script writes `docs/assets/closed_shapes/density_self_intersection_criteria.png` and compares a small-turning certified curve, a simple monotone spiral-type curve beyond the small-turning bound, and a non-monotone self-intersecting reconstruction.

![Density self-intersection criteria](docs/assets/closed_shapes/density_self_intersection_criteria.png)

Generate the signed-density closure and closed-curve energy-bound example:

```bash
python examples/geometry/closure_and_energy_bounds.py
```

The script writes `docs/assets/closed_shapes/closure_and_energy_bounds.png` and shows that heading closure and position closure are separate constraints, plus the fixed-perimeter lower bound attained by the circle.

![Closure and energy bounds](docs/assets/closed_shapes/closure_and_energy_bounds.png)

Generate the tangent-vector algebra and affine-density example:

```bash
python examples/geometry/tangent_vector_affine_density.py
```

The script writes `docs/assets/closed_shapes/tangent_vector_affine_density.png` and shows velocity-field interference, affine tangent-stretch density modulation, and the drift transition from looped to cusped to unwound curves.

![Tangent-vector affine density](docs/assets/closed_shapes/tangent_vector_affine_density.png)

Generate the involute/evolute density example:

```bash
python examples/geometry/involute_evolute_density.py
```

The script writes `docs/assets/closed_shapes/involute_evolute_density.png` and shows the circle involute cusp law, the pulled-back density integrand, and ellipse evolute centers of curvature.

![Involute evolute density](docs/assets/closed_shapes/involute_evolute_density.png)

Generate the GTM provocation-memory bridge example:

```bash
python examples/applied/provocation_memory_bridge.py
```

The script writes `docs/assets/dynamics/provocation_memory_bridge.png`. This is a bridge signal and memory-state demonstration, not a validated physiological motion-sickness model.

![Provocation memory bridge](docs/assets/dynamics/provocation_memory_bridge.png)

Generate the experienced load-change feature example:

```bash
python examples/applied/provocation_load_change_features.py
```

The script writes `docs/assets/dynamics/provocation_load_change_features.png` and separates steady load `P(t)` from changing load `C(t)=|dP/dt|`.

![Provocation load-change features](docs/assets/dynamics/provocation_load_change_features.png)

Generate the wheel-center transmission example:

```bash
python examples/applied/wheel_center_transmission.py
```

The script writes `docs/assets/dynamics/wheel_center_transmission.png` and uses an open rectangle with wheel radius `R=1/(2*pi)`, width `1+R`, and height `1+2R` to compare blind normal offsets, sampled collision-aware validity, circle-relative contact-coordinate jumps, and penetration depth.

![Wheel-center transmission](docs/assets/dynamics/wheel_center_transmission.png)

## Basic Usage

```python
from grays_tortuosity import circle_points, tortuosity

path = circle_points(radius=2.0)
result = tortuosity(path, ell=0.5, closed=True)
print(result.value)
```

By default, `tortuosity` uses exponential heading smoothing. `smoothing="gaussian"` is available as a centered, non-causal alternative. Use `smoothing="none"` for continuously defined smooth paths such as circles, ellipses, and parametric curves when you want the geometric curvature-energy total without response filtering:

```python
from grays_tortuosity import stadium_points, tortuosity

path = stadium_points(radius=1.0, straight=3.0)
geometric = tortuosity(path, ell=0.5, closed=True, smoothing="none")
filtered = tortuosity(path, ell=0.5, closed=True, smoothing="exponential")
print(geometric.value, filtered.value)
```

For polygonal or otherwise non-differentiable paths, use a smoother so sharp heading jumps produce finite values. Keep `smoothing="exponential"` for causal response-style interpretation; use `smoothing="gaussian"` for centered offline analysis. For a stadium with the same turn radius as a circle, the geometric total stays near the circle value as straight sections are added. Filtered values differ because smoothers attenuate curvature transitions between straight sections and circular caps.

For local profiles of smooth sampled curves, use the finite-difference local estimator rather than unsmoothed heading turns. It estimates curvature from coordinate derivatives, avoiding the fuzzy polyline turn-angle artifacts that appear when tiny heading discretization errors are squared.

Prototype 3D support uses Bishop-frame curvature components. The vector signed density is `q=A||K||^(p-1)K`, and sampled Bishop curvature can be reconstructed into a 3D path:

```python
import numpy as np
from grays_tortuosity import reconstruct_path_from_bishop_curvature, signed_vector_tortuosity_density

curvature = np.column_stack([np.full(1000, 0.5), np.zeros(1000)])
density = signed_vector_tortuosity_density(curvature, ell=0.5)
path = reconstruct_path_from_bishop_curvature(curvature, ds=0.01).points
```

Wheel/tire harmonic smoothing is intentionally documented separately as an optional future transmission layer in `docs/design/wheel_harmonic_tire_model.md`.

Pose/vestibular bridge helpers expose kinematic inputs from sampled SE(3) trajectories:

```python
import numpy as np
from grays_tortuosity import gravity_referenced_specific_force, head_angular_velocity

time = np.linspace(0.0, 1.0, 101)
positions = np.zeros((time.size, 3))
rotations = np.tile(np.eye(3), (time.size, 1, 1))
omega = head_angular_velocity(time, rotations)
specific_force = gravity_referenced_specific_force(time, positions, rotations)
```

The pose convention and future vestibular integration points are documented in `docs/design/pose_and_vestibular_inputs.md`.

For a circle of radius `R`, the continuum model predicts:

```math
T = T_c \frac{\ell}{R}
```

for `p = 2`.

Random walks are available as a simple grid-path test case:

```python
from grays_tortuosity import random_walk_points, tortuosity

path = random_walk_points(steps=12, seed=7)
result = tortuosity(path, ell=0.75)
print(result.value)
```

For well-separated polygon corners, compare against the isolated-corner approximation:

```python
from grays_tortuosity import isolated_corner_approximation, regular_polygon_points, tortuosity

path = regular_polygon_points(4, radius=4.0)
numerical = tortuosity(path, ell=0.2, closed=True).value
approximation = isolated_corner_approximation(path, closed=True)
print(numerical, approximation)
```

Local tortuosity is available in two forms:

```python
from grays_tortuosity import f_curve_points, finite_difference_windowed_tortuosity, tortuosity_density, windowed_tortuosity

path = f_curve_points()
density = tortuosity_density(path, ell=0.5)
local = windowed_tortuosity(path, ell=0.5, window=2.0, mode="centered")
smooth_local = finite_difference_windowed_tortuosity(path, ell=0.5, window=2.0, mode="centered")
print(density.density.max(), local.windowed.max())
```

`tortuosity_density` is the instantaneous local turning load `dT/ds`. `windowed_tortuosity` accumulates that density over a trailing or centered arc-length buffer. `average_tortuosity` reports total tortuosity per unit arc length.

Signed density preserves left-versus-right turning:

```python
from grays_tortuosity import curvature_from_signed_density, finite_difference_signed_tortuosity_density, f_curve_points

path = f_curve_points()
signed = finite_difference_signed_tortuosity_density(path, ell=0.5)
curvature = curvature_from_signed_density(signed.density, ell=0.5)
```

Given an initial point, initial heading, and signed curvature as a function of arc length, `reconstruct_path_from_curvature` reconstructs the corresponding planar curve up to numerical integration error.

You can also reconstruct directly from signed density samples or a signed-density function:

```python
import numpy as np
from grays_tortuosity import reconstruct_path_from_signed_density_function

curve = reconstruct_path_from_signed_density_function(
    lambda s: 0.05 * np.sin(s),
    s_max=2.0 * np.pi,
    ds=0.01,
    ell=1.0,
)
print(curve.points[-1])
```

## Development Plan

See `docs/development_plan.md`.
