Skip to content

Curves API

skelarm.curves

Closed periodic task-space curves for repeated tracing.

A periodic-curve task makes the robot's tip trace a closed planar curve over and over. Each curve is a position function of a phase angle theta (2*pi-periodic); :class:PeriodicTaskReference maps simulated time to phase (theta = omega * t with omega = 2*pi / period) and exposes the TaskReference interface, so :func:~skelarm.ik_joint_reference converts it to a joint reference for the tracking controllers. The built-in curves are circle, ellipse, lemniscate (the horizontal Bernoulli infinity), vertical_lemniscate (an upright figure-eight), and rose (a k-petal rhodonea r = a*cos(k*theta)). Register your own with :func:register_curve.

See docs/reference/09_trajectory_filtering.md and the Control Configuration guide.

PeriodicTaskReference

A task-space reference that traces a closed curve with a fixed period.

Implements the TaskReference interface (sample + duration). The velocity and acceleration are obtained by central differences in time (they are not needed by :func:~skelarm.ik_joint_reference, which uses only the position).

Parameters:

Name Type Description Default
curve CurveFunction

A 2*pi-periodic position function theta -> (x, y).

required
period float

Time for one full loop (seconds).

required
duration float

Total trace time (seconds); duration / period loops are traced.

required
Source code in src/skelarm/curves.py
class PeriodicTaskReference:
    """A task-space reference that traces a closed curve with a fixed period.

    Implements the ``TaskReference`` interface (``sample`` + ``duration``). The
    velocity and acceleration are obtained by central differences in time (they are not
    needed by :func:`~skelarm.ik_joint_reference`, which uses only the position).

    Parameters
    ----------
    curve : CurveFunction
        A ``2*pi``-periodic position function ``theta -> (x, y)``.
    period : float
        Time for one full loop (seconds).
    duration : float
        Total trace time (seconds); ``duration / period`` loops are traced.
    """

    def __init__(self, curve: CurveFunction, *, period: float, duration: float) -> None:
        """Store the curve and its time mapping."""
        if period <= 0.0:
            msg = f"period must be positive, got {period}"
            raise ValueError(msg)
        self._curve = curve
        self.period = float(period)
        self.duration = float(duration)
        self._omega = 2.0 * np.pi / self.period

    def position(self, t: float) -> NDArray[np.float64]:
        """The curve position at time ``t``."""
        return self._curve(self._omega * t)

    def sample(self, t: float) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
        """Return ``(p, dp, ddp)`` at time ``t`` (position and time derivatives)."""
        h = 1e-4 * self.period
        p = self.position(t)
        forward = self.position(t + h)
        backward = self.position(t - h)
        dp = (forward - backward) / (2.0 * h)
        ddp = (forward - 2.0 * p + backward) / (h * h)
        return p, dp, ddp

__init__(curve, *, period, duration)

Store the curve and its time mapping.

Source code in src/skelarm/curves.py
def __init__(self, curve: CurveFunction, *, period: float, duration: float) -> None:
    """Store the curve and its time mapping."""
    if period <= 0.0:
        msg = f"period must be positive, got {period}"
        raise ValueError(msg)
    self._curve = curve
    self.period = float(period)
    self.duration = float(duration)
    self._omega = 2.0 * np.pi / self.period

position(t)

The curve position at time t.

Source code in src/skelarm/curves.py
def position(self, t: float) -> NDArray[np.float64]:
    """The curve position at time ``t``."""
    return self._curve(self._omega * t)

sample(t)

Return (p, dp, ddp) at time t (position and time derivatives).

Source code in src/skelarm/curves.py
def sample(self, t: float) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
    """Return ``(p, dp, ddp)`` at time ``t`` (position and time derivatives)."""
    h = 1e-4 * self.period
    p = self.position(t)
    forward = self.position(t + h)
    backward = self.position(t - h)
    dp = (forward - backward) / (2.0 * h)
    ddp = (forward - 2.0 * p + backward) / (h * h)
    return p, dp, ddp

build_curve(kind, params)

Build the position function for curve kind from params.

Raises:

Type Description
ValueError

If kind is not a registered curve.

Source code in src/skelarm/curves.py
def build_curve(kind: str, params: Mapping[str, Any]) -> CurveFunction:
    """Build the position function for curve ``kind`` from ``params``.

    Raises
    ------
    ValueError
        If ``kind`` is not a registered curve.
    """
    if kind not in _CURVES:
        msg = f"unknown curve {kind!r}; choose from {curve_kinds()}"
        raise ValueError(msg)
    return _CURVES[kind](params)

curve_kinds()

Return the registered curve kinds, sorted.

Source code in src/skelarm/curves.py
def curve_kinds() -> tuple[str, ...]:
    """Return the registered curve kinds, sorted."""
    return tuple(sorted(_CURVES))

register_curve(name, factory)

Register a closed-curve factory under name for the periodic_curve task.

Parameters:

Name Type Description Default
name str

The [task].curve value to bind.

required
factory CurveFactory

A callable (params) -> (theta -> position) building a 2*pi-periodic position function from the task parameters.

required
Source code in src/skelarm/curves.py
def register_curve(name: str, factory: CurveFactory) -> None:
    """Register a closed-curve factory under ``name`` for the ``periodic_curve`` task.

    Parameters
    ----------
    name : str
        The ``[task].curve`` value to bind.
    factory : CurveFactory
        A callable ``(params) -> (theta -> position)`` building a ``2*pi``-periodic
        position function from the task parameters.
    """
    _CURVES[name] = factory