Skip to content
Get started

Sampled controls

Standard library

Eqiora.Controls.Sampled provides a unit delay and a discrete integrator. Each instance runs on a clock supplied by the caller and keeps one memory value, which you initialize.

Component Input Tick outputs
UnitDelay u: 1 y is the previous memory; current u becomes the next memory
DiscreteIntegrator rate: 1 / s before is old memory; after is old memory plus period times rate
sampled.eqi
import Eqiora.Controls.Sampled.sampled as sampled;
model SampledSignals(
input signal: 1 at tick,
input rate: 1 / s at tick,
output delayed: 1 at tick,
output accumulated: 1 at tick
) {
clock tick = periodic(1[s] / 4, phase = 0[s] / 1);
instance delay: sampled.UnitDelay(tick = tick, initial_value = 5);
instance integral: sampled.DiscreteIntegrator(tick = tick, initial_value = 3);
connect signal -> delay.u;
connect rate -> integral.rate;
relation outputs at tick {
delayed = delay.y;
accumulated = integral.after;
}
}

The first tick is at zero and the period is 1/4 s. With input samples [2, -1, 3], delayed is [5, 2, -1]. With rate samples [2, -1, 3] /s, accumulated is [3.5, 3.25, 4]. Select integral.before instead of integral.after to observe memory before the current increment.

Place this source in src/main.eqi of a local project and add Eqiora.Controls.Sampled at version 0.1.0 using the package workflow. Compile with entry="SampledSignals". The complete sampled-controls example shows how to supply timestamped values and run an execution_session.

sampled.eqi — library definitions
/// Publish the previous sample and then retain the current input.
public component UnitDelay(
clock tick: periodic,
parameter initial_value: 1,
input u: 1 at tick,
output y: 1 at tick
) {
state memory: 1 at tick;
initial { pre(memory) = initial_value; }
relation update at tick {
y = pre(memory);
next(memory) = u;
}
}
/// Integrate the supplied rate once per tick, exposing both sides of the update.
public component DiscreteIntegrator(
clock tick: periodic,
parameter initial_value: 1,
input rate: 1 / s at tick,
output before: 1 at tick,
output after: 1 at tick
) {
state memory: 1 at tick;
initial { pre(memory) = initial_value; }
relation update at tick {
next(memory) = pre(memory) + period(tick) * rate;
before = pre(memory);
after = next(memory);
}
}

initial_value and tick must be bound. The integrator multiplies by the clock’s period, so changing the period changes each increment. There is no output before the first tick, and all equations at a tick hold simultaneously.

These blocks use dimensionless memory. To integrate a physical rate, choose a fixed nonzero scale in the desired output unit. Divide the physical rate by the scale, use a dimensionless initial value, and multiply the output by the same scale. For example, with a 2 V scale, 4 V/s becomes 2 /s; a quarter-second step increases normalized memory by 0.5 and physical voltage by 1 V.

The example project contains voltage and displacement adapters written this way. They are ordinary equations and add no state.

An input must use the same clock as the block that receives it. Two clocks with equal periods are still different clocks, so pass the same tick to every block that should update together.

Clock syntax · State and storage