Metadata-Version: 2.4
Name: cosseratbench
Version: 0.2.0
Summary: A visual benchmark suite for rope, cable, and Cosserat rod simulation.
Author: Jake Van Slyke
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: cosseratbench[pyelastica,mujoco] ; extra == 'all'
Requires-Dist: mujoco>=3.2 ; extra == 'mujoco'
Requires-Dist: pyelastica>=1.0 ; extra == 'pyelastica'
Requires-Python: >=3.10
Project-URL: Repository, https://github.com/jakeryderv/cosseratbench
Provides-Extra: all
Provides-Extra: mujoco
Provides-Extra: pyelastica
Description-Content-Type: text/markdown

# cosseratbench

***A tool for exploring, comparing, and stress-testing simulations of slender flexible bodies.***

What works well, what becomes difficult, and what breaks, for which solver,
under which conditions. See the [vision](docs/vision.md) for the idea behind it,
and [findings](docs/findings.md) for what it has shown so far.

The scenarios it is growing toward:

| #  | Experiment                         | What it tests                                                           |
| -- | ---------------------------------- | ----------------------------------------------------------------------- |
| 1  | **Twist → plectoneme / knot**      | twist, bending, buckling, extreme curvature, self-contact               |
| 2  | **Rope drop / pile**               | gravity, friction, chaotic self-contact, many simultaneous contacts     |
| 3  | **Catenary / hanging cable**       | basic gravity, tension, sag, stretch; good sanity/validation case       |
| 4  | **Cantilever bend + twist**        | isolated bending stiffness, torsion, large deformation                  |
| 5  | **Pendulum / swinging cable**      | dynamics, inertia, damping, oscillation                                 |
| 6  | **Snap / whip test**               | very fast motion, high curvature, timestep stability                    |
| 7  | **Cylinder wrap / capstan**        | rod-cylinder contact, friction, sliding, tension transfer               |
| 8  | **Pulley / sheave**                | moving contact, bending around small radius, tension under motion       |
| 9  | **Obstacle course**                | repeated contact against cylinders/planes/spheres, sliding and snagging |
| 10 | **Two-rope interaction**           | rod-rod contact, crossing, rubbing, entanglement                        |
| 11 | **Loop / knot tightening**         | persistent dense self-contact and friction under increasing tension     |
| 12 | **Compression / coiling**          | rope pushed into a confined area; buckling and pile formation           |
| 13 | **Container packing**              | rope fed into a box/cylinder; dense 3D self-contact                     |
| 14 | **Parameter/extreme stress sweep** | deliberately push stiffness, friction, speed, resolution, timestep      |

## Status

Early. Five experiments run against two solvers
([PyElastica](https://github.com/GazzolaLab/PyElastica) and
[MuJoCo](https://mujoco.org)'s cable plugin): four validations scored against an
analytical or high-accuracy numerical reference (catenary, cantilever, pendulum,
twist) and one exploration of contact with friction (capstan). A web viewer plays
the results back. Rod ends can be driven: moved, turned, or left free to slide
under a load. Rods can touch fixed cylinders, with friction; contact between rods
is not built yet.

Known solver limit: MuJoCo cannot run the twist experiment. A cable clamped at
both ends diverges once it holds about 5 rad of twist; see
[findings](docs/findings.md) for this and other solver behaviour.

## Usage

```sh
uv sync --all-extras        # from a clone; installs both solver backends
uv run cosseratbench list
uv run cosseratbench run    # every experiment x every solver, saved under results/
uv run cosseratbench run catenary --solver pyelastica --n-elements 100
uv run cosseratbench run catenary --vary span --vary time_step_scale
uv run cosseratbench run --vary all   # every parameter and solver option, one at a time
uv run cosseratbench view   # open the results in a browser
```

Each run writes `results/<experiment>/<variant>/<solver>/result.json` (outcome,
metrics, observations, wall time) and `trajectory.npz` (node positions over time),
next to an `experiment.json` describing the problem. A variant is the ordinary
case (`default`) or one change from it: a physical parameter the experiment
declares, the resolution, or `time_step_scale`, which multiplies the time step
each solver would choose. `cosseratbench list` shows what each experiment can vary. Wall time excludes a short warm-up run, so one-off costs
such as JIT compilation do not count against a solver.

## Viewer

`cosseratbench view` serves an interactive page for whatever is under `results/`:
3D playback of every solver on one timeline, overlaid or split into panes that
share a camera, with the analytical reference drawn where one exists; the metrics
table; the speed of the fastest node over time; and the physical scenario.

`cosseratbench site OUT` writes the same page as static files, for hosting
anywhere (GitHub Pages, for example). The page loads three.js from a CDN, so it
needs a network connection.

## Documentation

- [Vision](docs/vision.md): what the project is for and the principles behind it.
- [Findings](docs/findings.md): what the experiments have shown about each solver,
  and about measuring them.
- [Decisions](docs/decisions/README.md): the choices that shape the project, and why.

## How it fits together

- A **scenario** describes the physics and nothing else: geometry, material,
  boundary conditions, loads, gravity, duration, all in SI units. A rod may
  start stretched, for example already hanging in equilibrium. Time steps,
  element counts, contact stiffnesses and damping coefficients are not part of
  it; they are each solver's business.
- A **solver** adapter turns a scenario into a **trajectory**, node positions
  over time, at a requested resolution. It declares the physics it models
  (`Capability`), and an experiment that needs more is reported as unsupported
  rather than run.
- An **experiment** pairs a scenario with **metrics**. Metrics see only the
  scenario and the trajectory, so every solver is judged by the same code.

## Adding a solver or an experiment

Both are discovered through entry points, so they can live in your own package:

```toml
[project.entry-points."cosseratbench.solvers"]
mysolver = "mypackage.adapter:MySolver"

[project.entry-points."cosseratbench.experiments"]
myexperiment = "mypackage.experiments:my_experiment"
```

```python
from cosseratbench import Capability, Scenario, Trajectory


class MySolver:
    name = "mysolver"
    capabilities = frozenset({Capability.STRETCH})

    def run(self, scenario: Scenario, *, n_elements: int, n_frames: int) -> Trajectory: ...
```

A solver that breaks down should raise `cosseratbench.solver.Diverged`, with the
simulated time if it knows it; the runner also catches blow-ups a solver misses.
To take part in time step sweeps, accept a `time_step_scale` keyword in the
constructor and multiply your own choice of step by it.

An experiment builds its scenario from its parameters:

```python
from cosseratbench import Experiment, Parameter


def build(youngs_modulus: float) -> Scenario: ...


my_experiment = Experiment(
    name="myexperiment",
    description="One line for listings.",
    build=build,
    parameters=(Parameter("youngs_modulus", default=1e6, values=(1e5, 1e6, 1e7), unit="Pa"),),
    metrics={"my_metric": my_metric},  # functions of (scenario, trajectory)
    notes="What it explores and what to look for; the viewer shows this.",
)
```

The built-in solvers and experiments register the same way; see
`src/cosseratbench/solvers` and `src/cosseratbench/experiments`.

