Skip to content

Code examples

The parameter values in these examples are for demonstration. Use the Python API for interactive work and arrays; use the command line for batch runs.

Simulate and plot in Python

Install .[viz] from the source directory. Run python examples/python_balance.py, or paste the code below into a Python session:

import numpy as np
import matplotlib.pyplot as plt
from zcartpole import ZNCartPole, simulate

model = ZNCartPole(m=[0.1], l=[0.4], M=1.0, u_max=80.0)
sol = simulate(model, lambda t, state: model.lqr_control(state),
               th0=[0.05], t_max=3.0, dt=0.002)
angle = np.unwrap(np.arctan2(sol.y[3], sol.y[2]))

plt.plot(sol.t, angle)
plt.xlabel('Time (s)')
plt.ylabel('Link angle (rad)')
plt.show()

sol.t, sol.y, and sol.u are arrays. This low-level example applies ideal force directly. Use SystemConfig.from_dict(...) and run_system(...) for a reproducible actuator run; a complete dictionary example is on the Python API page. simulate_linear_actuator(...) returns actuator and mechanical arrays in memory.

Simulate an actuator in memory

import matplotlib.pyplot as plt
from zcartpole import ZNCartPole, LinearActuator, simulate_linear_actuator

model = ZNCartPole(m=[0.1], l=[0.4], M=1.0, u_max=80.0)
drive = LinearActuator.from_transfer_functions(
    [20.0], [0.05, 1.0],
    command_min=-4.0, command_max=4.0,
    force_limit_n=80.0, delay_s=0.01, command_unit='V',
)
sol = simulate_linear_actuator(
    model, drive,
    controller=lambda t, state, force: model.lqr_control(state),
    th0=[0.05], t_max=3.0, dt=0.002,
    rail_limit=0.5, predict_delay=True,
)
fig, axes = plt.subplots(2, 1, sharex=True)
axes[0].plot(sol.t, sol.y[0])
axes[0].set_ylabel('Cart position (m)')
axes[1].plot(sol.t, sol.u)
axes[1].set_ylabel('Applied force (N)')
axes[1].set_xlabel('Time (s)')
fig.tight_layout()
plt.show()

This code returns arrays in memory. sol.command is the issued voltage command, and sol.rail_violation_time records a rail crossing.

Configured run from Python

from zcartpole import SystemConfig, run_system

config = SystemConfig.from_json('examples/quick_transfer_balance.json')
result = run_system(config, 'balance_python')
print(result.status)
print(result.summary['simulation']['peak_cart_m'])

The JSON file is supplied in the source archive. run_system records normalized inputs, trajectory, and summary in a new directory. The same function accepts a dictionary via SystemConfig.from_dict.

Command line examples

The commands below run from the extracted source directory. Each output directory must be new.

Install the base package from the source directory:

python -m pip install -e .
python examples/run_links.py --links 1 --mode balance -o n1_balance
python examples/run_links.py --links 2 --mode balance -o n2_balance
python examples/run_links.py --links 3 --mode balance -o n3_balance

examples/run_links.py builds a SystemConfig with n link entries, runs run_system, and prints the path to summary.json. The script is a complete example of the Python API. A completed run writes config.json, trajectory.csv, and summary.json.

Install the optional optimizer. Add viz only when saving a GIF:

python -m pip install -e '.[opt,viz]'
python examples/run_links.py --links 1 --mode swingup -o n1_swingup
python examples/run_links.py --links 2 --mode swingup -o n2_swingup
python examples/run_links.py --links 3 --mode swingup -o n3_swingup --animation

The last command writes n3_swingup/simulation.gif if planning produces a trajectory. The other results still need summary.json inspection: a valid plan can miss the catch. Swing-up solve time depends on the link count, seeds, nodes, and initial state.

Run a JSON file

python -m zcartpole examples/quick_transfer_balance.json -o balance_json_run
python -m zcartpole examples/actuator_transfer_system.json -o triple_json_run

To change the motor, lengths, masses, initial state, or LQR weights, copy the JSON and edit the input fields. See configuration and actuators. From a wheel installation, use an absolute path to a JSON file; example files are not installed with the wheel.

Inspect the result with Python

import csv
import json
from pathlib import Path

folder = Path('n2_balance')
summary = json.loads((folder / 'summary.json').read_text(encoding='utf-8'))
print(summary['status'])
print(summary['simulation']['peak_cart_m'])

with (folder / 'trajectory.csv').open(newline='', encoding='utf-8') as handle:
    rows = list(csv.DictReader(handle))
print(rows[-1]['cart_position_m'])
print(rows[-1]['link_2_angle_rad'])

For swing-up, compare plan.csv (ideal force) with trajectory.csv (actuator simulation). The output guide lists all columns and statuses.

Other source examples

File Purpose
examples/actuator_first_order_system.json Identified first-order actuator format.
examples/dc_motor_system.json Optional electrical motor format.
examples/engineer_system.json Legacy force-lag format.
examples/animate_link_counts.py GIFs for n = 1, 2, and 3 using an ideal-force simulation.
examples/benchmark_math.py Warm mechanical-step timings for Python and Numba.
examples/robustness_checks.py, examples/actuated_robustness.py Selected parameter scenarios.
examples/actuator_step_measurement.csv, examples/actuator_validation_measurement.csv Input formats for optional fitting and validation commands.

The browser trajectory demo uses bundled data. It is separate from the JSON runner.