Metadata-Version: 2.4
Name: cadence-cycletime
Version: 0.4.0
Summary: Machine cycle-time and motion-sequence planning for automated equipment: will this machine hold its target cycle time, worst case, not on a good day?
Project-URL: Homepage, https://github.com/Mech-eng-au/cadence
Project-URL: Repository, https://github.com/Mech-eng-au/cadence
Project-URL: Changelog, https://github.com/Mech-eng-au/cadence/blob/main/CHANGELOG.md
Author: Sebastian Argandona
License-Expression: MIT
License-File: LICENSE
Keywords: PLC,automation,cycle time,motion profiling,scheduling,takt
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: hypothesis>=6; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# cadence

[![CI](https://github.com/Mech-eng-au/cadence/actions/workflows/ci.yml/badge.svg)](https://github.com/Mech-eng-au/cadence/actions/workflows/ci.yml)

Machine **cycle-time and motion-sequence planning** for automated equipment:
will this machine hold its target cycle time - **worst case**, not on a good
day? Pure Python, standard library only.

Given a station's movements (point-to-point moves and timed dwells), their
precedence, and the controller's timing characteristics, cadence:

1. **Profiles** each move into a duration from distance and kinematic limits,
   with a selectable profile per movement: closed-form **trapezoidal**
   (default) or jerk-limited **S-curve** (7-phase, `jmax`), rest-to-rest.
2. **Schedules** the cycle - what runs in parallel - under precedence and
   shared-actuator constraints (one axis does one thing at a time).
3. Adds **sensor + PLC scan latency** wherever a step waits on a sensor:
   sensor response + input filter + fieldbus + I/O update + task scan, with a
   latched (touch-probe) option that skips the scan tax.
4. Reports a **nominal and a worst-case** cycle, the **critical path**, and
   per-movement **slack** - and judges the target against the worst case,
   because that is what upholds takt.
5. Proves its own answer where it can: every schedule carries a
   **lower bound** on the makespan, so a result is either **provably
   optimal** or honestly labelled "within X ms of best possible".
6. Tells you **which knob to turn** when takt is missed
   (`suggest_levers`): raise vmax/amax on a critical move, latch a sensor,
   hand off commanded, shorten a dwell, speed up the PLC task - each
   suggestion quantified by a re-solve and ranked by saving.
7. Models how machines are actually built: per-move **settle time** and
   selectable **hand-off release modes** - on completion, with a set
   distance remaining, or a set time early (event-based scheduling).
8. Analyses the **repeating cycle** (`steady_state`, CLI `--cyclic`): the
   tightest period the solved pattern can repeat at (pipelined), a
   pattern-independent takt lower bound, lead time vs takt, and the
   bottleneck - with the same provable-optimality trust model.

## Install

```bash
pip install cadence-cycletime
```

The distribution is named `cadence-cycletime` (the bare name is reserved by an
empty project on PyPI); the import name is simply `cadence`:

```python
import cadence
```

## Quickstart

```python
from cadence import Cell, Controller, Movement, Resource, solve

cell = Cell(
    controller=Controller(task_ms=4, io_ms=1, filter_ms=1, remote_io=True, bus_ms=0.25),
    resources=(
        Resource(id="x", name="Gantry X"),
        Resource(id="y", name="Gantry Y"),
        Resource(id="z", name="Gantry Z"),
    ),
    movements=(
        Movement(id="move_x", resource="x", kind="move", dist=300, vmax=1500, amax=10000),
        Movement(id="move_y", resource="y", kind="move", dist=200, vmax=1200, amax=8000,
                 preds=("move_x",)),
        Movement(id="move_z", resource="z", kind="move", dist=100, vmax=800, amax=5000,
                 preds=("move_y",), trigger="sensor", sensor_ms=1.5),
    ),
    target_ms=1000.0,
)

result = solve(cell)
print(f"Worst-case cycle: {result.worst_ms:.1f} ms (nominal {result.nominal_ms:.1f} ms)")
print(f"Meets target: {result.meets_target}")
print(f"Scan jitter: {result.scan_jitter_ms:.1f} ms")
print(f"Critical path: {result.worst.critical_path}")
```

Units are consistent pairs: distance **mm** (or deg for rotary axes),
velocity mm/s, acceleration mm/s², time **ms**.

## CLI

Cells serialize to a strict, versioned JSON format (`cell_to_dict` /
`cell_from_dict`), and the CLI solves a cell file directly - headline figures,
a per-movement table, an ASCII Gantt, and ranked improvement levers:

```bash
python -m cadence solve examples/pick_place.json --levers
python -m cadence solve station.json --json          # machine-readable result
```

### As a CI check

Because `solve()` needs no web stack, a design can be regression-checked in CI.
`--check` exits with code 2 when the worst case misses the target:

```bash
python -m cadence solve station.json --target 6000 --check
```

## The latency model

For a sensor-confirmed hand-off, the time from physical event to the next move
being commanded:

```
fixed   = sensor_ms + filter_ms + (2 * bus_ms if remote_io else 0)
nominal = fixed + io_ms/2 + task_ms/2    # event sampled mid-window, mid-scan
worst   = fixed + io_ms   + task_ms      # event just missed: full I/O + scan
```

`worst - nominal` is the **scan jitter** you must budget per gated handshake
on the critical path. A latched (touch-probe) input timestamps at the drive
cycle: `nominal = worst = sensor_ms + bus`. A commanded (drive-coordinated)
hand-off costs nothing.

## Consumed by

- [toolbox-app](https://github.com/Mech-eng-au/toolbox-app) - the Cycle Planner tool (installed editable).

## Status & roadmap

v0.2 schedules with a deterministic, latency-aware **list heuristic**, and
every result carries a scheduler-independent **lower bound**: at typical
station scale the heuristic is usually **provably optimal**
(`SolveResult.approximate` is False); otherwise the gap to best-possible is
reported honestly. Candidates for later, in evidence order: an exact CP-SAT
model (OR-Tools) if real cells show a nonzero gap, jerk-limited (S-curve)
profiling and servo settle time behind `profile_move()`, and move-overlap /
early-release hand-off semantics.

## License

MIT
