Skip to main content

A modular, GPU-accelerated framework for Physical Reservoir Computing simulation and evaluation

Project description

OpenPRC: Physical Reservoir Computing Framework

Python 3.10+ License CUDA arXiv

OpenPRC is a modular, GPU-accelerated Python framework for simulating and evaluating physical reservoir computers — mechanical systems that process information through their intrinsic dynamics.

If you use OpenPRC in your research, please cite:

@article{phalak2026openprc,
  title={OpenPRC: A Unified Open-Source Framework for Physics-to-Task Evaluation in Physical Reservoir Computing},
  author={Phalak, Yogesh and Lor, Wen Sin and Khairnar, Apoorva and Jantzen, Benjamin and Naughton, Noel and Li, Suyi},
  journal={arXiv preprint arXiv:2604.07423},
  year={2026}
}

Simulation Capabilities

OpenPRC supports diverse mechanical substrates ranging from compliant mass-spring networks to rigid-foldable origami. Below are examples of validated simulation outputs:

Soft Reservoir Network
Soft Reservoir Network
Mass-spring lattice under dynamic actuation
Miura-Ori Tessellation
Miura-Ori Tessellation
Rigid-foldable origami pattern
Kirigami Structure
Kirigami Structure
Compliant network with geometric cuts
K-Cone Origami
K-Cone Origami
Non-periodic origami configuration
Bistable Slab
Bistable Slab
Multistable mechanical metamaterial
Tapered Spring
Tapered Spring
Nonlinear elastic element dynamics

Installation

pip install openprc

# With GPU support
pip install openprc[cuda]

# With all optional dependencies
pip install openprc[full]

Dependencies

Package Purpose Extra
numpy, h5py, scipy Core numerics and I/O (always)
numba JIT-compiled CPU physics (always)
scikit-learn Ridge readout (always)
pycuda CUDA backend [cuda]
jax / jaxlib Differentiable JAX backend [jax]
piviz-3d, imgui 3-D animator [viz]
opencv-python Vision utilities [vision]
trimesh, rosbags, yourdfpy Robot bundle tooling [automod]

Pipeline

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   demlat    │────▶│   reservoir │────▶│   analysis  │────▶│  optimize   │
│  (Physics)  │     │  (Readout)  │     │(Diagnostics)│     │(Calibration)│
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘

Quick Start

import numpy as np
from openprc.demlat import SimulationSetup, Simulation, Engine, ShowSimulation

EXP = "experiments/my_prc"

# ── 1. Build geometry ──────────────────────────────────────────────────────
setup = SimulationSetup(EXP, overwrite=True)
setup.set_simulation_params(duration=10.0, dt=0.001, save_interval=0.01)
setup.set_physics(gravity=-9.81, damping=0.1)

anchor = setup.add_node([0.0, 0.0, 0.0], fixed=True)
mass   = setup.add_node([1.0, 0.0, 0.0], mass=1.0)
setup.add_bar(anchor, mass, stiffness=1e4, rest_length=1.0, damping=5.0)

# ── 2. Drive with a force signal ──────────────────────────────────────────
t   = np.arange(0, 10.0, 0.001)
sig = np.stack([0.5 * np.sin(2 * np.pi * t),
                np.zeros_like(t),
                np.zeros_like(t)], axis=1).astype("float32")
setup.add_signal("drive", sig, dt=0.001)
setup.add_actuator(anchor, "drive", type="force")
setup.save()

# ── 3. Run (CUDA, or "cpu" / "jax") ──────────────────────────────────────
eng = Engine(backend="cuda")          # BarHingeModel is the default
eng.run(Simulation(EXP))

# ── 4. Animate ────────────────────────────────────────────────────────────
ShowSimulation(EXP)

API Reference

demlat — Physics Simulation

from openprc.demlat import SimulationSetup, Simulation, Engine, ShowSimulation

# ── Geometry ──────────────────────────────────────────────────────────────
setup = SimulationSetup("./experiments/my_exp", overwrite=True)
setup.set_simulation_params(duration=5.0, dt=0.001, save_interval=0.01)
setup.set_physics(
    gravity=-9.81, damping=0.1,
    enable_collision=True,          # node-level sphere collision
    collision_radius=0.02,
    collision_restitution=0.6,
    collision_iterations=3,
)

n0 = setup.add_node([0.0, 0.0, 0.0], fixed=True,  collidable=True)
n1 = setup.add_node([0.5, 0.0, 0.0], mass=1.0,    collidable=True)
n2 = setup.add_node([0.5, 0.5, 0.0], mass=1.0)

setup.add_bar(n0, n1, stiffness=1e4, rest_length=0.5, damping=5.0)
setup.add_hinge([n0, n1, n2, n2], stiffness=50.0, rest_angle=np.pi / 2)

# ── Signals & Actuators ───────────────────────────────────────────────────
t   = np.arange(0, 5.0, 0.001)
pos = np.stack([0.1 * np.sin(2 * np.pi * t),
                np.zeros_like(t),
                np.zeros_like(t)], axis=1).astype("float32")
setup.add_signal("wave", pos, dt=0.001)

setup.add_actuator(n0, "wave", type="position")              # full 3-DOF
setup.add_actuator(n1, "wave", type="force", dof=[0, 0, 1]) # z-axis only
setup.save()

# ── Run ───────────────────────────────────────────────────────────────────
eng = Engine(backend="cuda")   # or "cpu" / "jax"; BarHingeModel is default
eng.run(Simulation("./experiments/my_exp"))

ShowSimulation("./experiments/my_exp")

reservoir — Readout Training

from openprc.reservoir import StateLoader, Ridge, Trainer, features

loader = StateLoader("./experiments/my_exp/output/simulation.h5")

# Node position features for selected nodes
feat = features.NodePositions(node_ids=[0, 1, 2], dims="all")
X    = feat.transform(loader)          # (T, n_features)

# Or use bar strains as the observable (stored as ε = ΔL/L₀)
feat = features.BarStrains()

# Train ridge readout
readout = Ridge(regularization=1e-4)
trainer = Trainer(
    features=feat,
    readout=readout,
    experiment_dir="./experiments/my_exp",
    loader=loader,
    washout=2.0,          # seconds to discard at start
    train_duration=6.0,
    test_duration=2.0,
)

y_target = ...            # (T,) or (T, n_outputs) numpy array
result = trainer.train(y_target, task_name="NARMA10")
result.save()

analysis — Correlation Diagnostics & Multistability

from openprc.analysis import correlation as corr
from openprc.analysis import EquilibriumFinder

# ── Correlation diagnostics (x: features, y: targets) ────────────────────
lin = corr.Linear(x, y, lag_sweep=True)
print(lin.pearson)         # zero-lag Pearson r per channel
lin.ccf.plot()             # cross-correlation lag profiles

nr = corr.Nonparametric(x, y)
print(nr.dcor)             # distance correlation (detects nonlinear deps)

# ── Find all mechanical equilibria ───────────────────────────────────────
finder  = EquilibriumFinder.from_experiment("./experiments/my_exp")
results = finder.find_all(num_random=50)
results.summary()
finder.save_results(results, "./experiments/my_exp/equilibria.h5")

optimize — JAX-Based Parameter Calibration

from openprc.optimize import Calibration

from openprc.demlat import BarHingeModel
cal = Calibration(BarHingeModel, backend="jax")

cal.load_geometry("./experiments/my_exp")
cal.load_reference("./experiments/my_exp/output/simulation.h5")

cal.optimize_params(bar_stiffness=True, hinge_stiffness=True)
cal.set_bounds(bar_stiffness=(10.0, 1e5))

result = cal.run(max_iterations=500, lr=0.01, cost="mse")
cal.save("./experiments/my_exp/optimized_geometry.h5")

automod — Robot PRC Pipeline

import openprc.automod as automod

# Stage 1: convert URDF links to spring-mass reservoirs
automod.batch_preprocess(
    bundle_dir="./robot_bundle",
    robot_name="go1",
    params=automod.PRESETS["small"],   # "small", "medium", "large"
)

# Stage 2: run DEMLAT simulations for all trajectories
automod.batch_simulate(
    bundle_dir="./robot_bundle",
    robot_name="go1",
    splits=("train", "test"),
    gravity=-9.81, damping_scale=2.0,
)

# Stage 3: train ridge readout with k-fold CV
run_dir = automod.run_training(
    bundle_dir="./robot_bundle",
    robot_name="go1",
    features="node_vel",              # see automod.FEATURE_LEVELS
    targets=["body_vel", "qvel"],
    n_folds=5,
)

# Stage 4: generate plots
automod.plot_run(run_dir)

License

Apache 2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openprc-0.1.0.tar.gz (328.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openprc-0.1.0-py3-none-any.whl (386.7 kB view details)

Uploaded Python 3

File details

Details for the file openprc-0.1.0.tar.gz.

File metadata

  • Download URL: openprc-0.1.0.tar.gz
  • Upload date:
  • Size: 328.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openprc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1e32d364b7dc386c217f0bb4a27d0177e8019626e2ca03a41dbdc6971454ccc7
MD5 2b3c1dab019063a9f030a76f39b915af
BLAKE2b-256 620d13e72238f577e256bf2934768b46a07cf55a54084c3e893a82121f6a86b4

See more details on using hashes here.

Provenance

The following attestation bundles were made for openprc-0.1.0.tar.gz:

Publisher: publish.yml on DARE-Lab-VT/OpenPRC-dev

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openprc-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: openprc-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 386.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openprc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ffffbd6b2fb77698214c36d9f1ed85242ca3427b86cbca00a179e70bce78a34
MD5 b6eefd65b938ca206a124a292e28e26c
BLAKE2b-256 8801bf5af137847f1fccd6bba8561e7170238484d9ccf2ca64a4222ce42d2325

See more details on using hashes here.

Provenance

The following attestation bundles were made for openprc-0.1.0-py3-none-any.whl:

Publisher: publish.yml on DARE-Lab-VT/OpenPRC-dev

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page