Skip to content
MANUAL

Clocks and discrete-time models ​

A discrete-time model computes values when a clock ticks. Use it for controllers, filters, sampled measurements, and logic that remembers earlier samples. Dyad expresses these models as equations between the current and previous ticks.

Start by connecting continuous and discrete signals with library components. Then learn to write a reusable clocked component and initialize its sample history. For a complete simulation, continue with Discrete-Time Modeling. The syntax index links to the rest of the language guide. Download as a Dyad projectClockGuide.zipOpen in Dyad Studio

What is a clock? ​

A clock defines the instants at which a signal has a new value. A periodic clock with period 0.1 ticks every 0.1 units of model time. A discrete controller reads its inputs and computes its outputs at those instants. A continuous plant has variables defined throughout the intervals between ticks.

A clock domain is the collection of signals and equations that use the same clock. Two controllers can share a domain even when their equations differ. Controllers running every 0.01 and 0.1 seconds belong to different domains; they need an explicit transfer when exchanging signals.

Clock ticks represent model time. DiscreteComponents.PeriodicClock(dt = 0.1) supplies a periodic clock; connecting a signal to its y connector assigns that clock to the signal.

Cross time domains deliberately ​

A clocked signal has values at its ticks. A continuous-time signal can be queried between ticks. Use a conversion component where a model crosses that boundary.

Modeling taskComponentBehavior
Read a continuous measurement at clock ticksDiscreteComponents.SamplerSamples the input on the output clock.
Drive a continuous plant from a clocked commandDiscreteComponents.ZeroOrderHoldHolds the most recent command between ticks.
Transfer a value between discrete clocksDiscreteComponents.LatestReads the latest value available from the input clock, with an initial value before its first tick.

These conversion components come from DiscreteComponents. Place them at the boundary between domains, as the following examples show. See Sampled-Data Systems for a complete plant-and-controller example.

Sample a continuous signal and hold the result ​

The following model samples a sine wave every 0.1 seconds, filters the samples, and produces a continuous output that stays constant between updates. It uses a local SmoothingFilter and requires BlockComponents and DiscreteComponents. SmoothingFilter is the example's sampled filter: it smooths changes in its input and accepts the clock of its connected signals. Its definition is included in the example project; Write a component that inherits its clock explains how to author it.

In the assembled model, trace source → sampler → filter → hold. The sampler reads the continuous source at ticks, and the hold makes the filter's output available between ticks. Select a block to inspect its parameters; open Code on narrower screens. The example project download includes the complete filter definition and its graphical metadata.

sampler.u receives the continuous sine wave. The connection to clock.y sets sampler.y and the filter's signals on the periodic clock. hold.y returns to continuous time, ready to drive a continuous plant input. The hold's initial_condition supplies its value before the first input tick; the filter's y0 initializes its own recurrence.

Transfer samples between different rates ​

A fast measurement and a slow controller need a rule for which measurement the controller sees. Latest chooses the most recent available input sample at each output tick. Here the fast filter runs every 0.01 seconds and the slow filter every 0.1 seconds:

dyad
component TwoRateFilter
  source = BlockComponents.Sources.Sine(frequency = 1.0, amplitude = 1.0)
  sampler = DiscreteComponents.Sampler()
  fast = SmoothingFilter()
  transfer = DiscreteComponents.Latest(init = 0.0)
  slow = SmoothingFilter()
  fast_clock = DiscreteComponents.PeriodicClock(dt = 0.01)
  slow_clock = DiscreteComponents.PeriodicClock(dt = 0.1)
relations
  connect(source.y, sampler.u)
  connect(sampler.y, fast.u, fast_clock.y)
  connect(fast.y, transfer.u)
  connect(transfer.y, slow.u, slow_clock.y)
end

The transfer's input belongs to the fast domain and its output to the slow domain. Connecting the two domains directly would ask them to share a clock. At t = 0.1, both clocks tick: Latest transfers the fast value computed at that instant. At a destination tick between source ticks, it reuses the last source value. The init parameter supplies the value before the first source tick. This component transfers samples; it does not average a group of fast samples or provide an anti-aliasing filter.

A hold followed by a sampler has different same-instant ordering from Latest. Choose the transfer that matches the intended timing, and supply its initial value before the input clock first ticks.

Declare input and output clocks ​

A reusable component can accept a clock from its surroundings:

dyad
component ClockedGain@[input clk extends Discrete]
  u = RealInput@[clk]()
  y = RealOutput@[clk]()
  parameter k::Real = 2.0
relations
  y@clk = k * u@clk
end

The header declares the clock parameter clk. The connector declarations put u and y on that clock. The equation computes a gain at each tick.

DeclarationPurpose
input clkAccept a clock supplied or inferred from the surrounding model.
input clk extends DiscreteAccept a discrete clock for sampled equations.
input clk extends PeriodicRequire a periodic clock, for example when using its sample interval.
input clk extends ContinuousRequire continuous time, as on a sampler's input.
output clkExpose a clock defined by the component, as a clock source does.

Clock direction and signal direction describe different things. A filter has an input clock even though it has both an input signal and an output signal. An output clock exposes a clock supplied by a component, such as DiscreteComponents.PeriodicClock. The declaration describes the clock interface; the component must also define the clock.

Read the current and previous samples ​

u@clk and u@(clk) refer to the value of u at the current tick of clk. u@(clk-1) refers to the preceding sample. The 1 counts ticks, so on a 0.1-second clock it means one sample earlier, or 0.1 seconds earlier.

FormMeaning
RealInput@[clk]()Assign a clock to a connector instance.
u@clk or u@(clk)Read the current sample.
u@(clk-1)Read the previous sample.
u@(clk-2)Read the sample two ticks earlier.
initial u@(clk-1) = u0Supply the sample history needed at startup.

The square brackets in @[clk] bind a clock to an instance. The expression suffix @(clk) selects a sample of a value. A change of clock in a suffix is not a resampling operation: use the conversion components to cross domains.

Write a component that inherits its clock ​

The filter used in the sampled-signal example combines the current input with its previous output:

dyad
component SmoothingFilter@[input clk extends Discrete]
  u = RealInput@[clk]()
  y = RealOutput@[clk]()
  parameter a::Real = 0.8
  parameter y0::Real = 0.0
relations
  initial y@(clk-1) = y0
  y@clk = a * y@(clk-1) + (1 - a) * u@clk
end

input clk makes the surrounding model supply the clock. extends Discrete restricts it to a discrete clock. Both connectors use that clock, so each tick reads one input and produces one output. With 0 ≤ a < 1, increasing a gives more weight to the previous output and smooths changes more strongly.

At the first tick, y0 supplies the previous output and the recurrence computes the current output. All equations at a tick take effect at the same model time; the solver determines their evaluation order from their dependencies. A shift such as @(clk-1) introduces a delay.

Initialize the history you use ​

A recurrence needs values for its past samples before it can compute the first sample. For a second-order recurrence, provide both history values:

dyad
component SecondOrderFilter@[input clk extends Discrete]
  u = RealInput@[clk]()
  y = RealOutput@[clk]()
  parameter a1::Real = 0.5
  parameter a2::Real = 0.2
relations
  initial y@(clk-1) = 0.0
  initial y@(clk-2) = 0.0
  y@clk = a1 * y@(clk-1) + a2 * y@(clk-2) + u@clk
end

An initial history value and the first output can differ: the equation still runs at the first tick. If the requested condition is a particular first output or steady operation, derive the history from that requirement. The initialization guide explains those choices and the initialization variants used by DiscreteComponents.

Supply a clock in the containing model ​

Keep reusable filters independent of a particular sample rate. Choose the rate where you assemble the model, using a clock source such as DiscreteComponents.PeriodicClock:

dyad
component ConstantFilteredSignal
  clock = DiscreteComponents.PeriodicClock(dt = 0.1)
  filter = SmoothingFilter()
  variable y::Real
relations
  filter.u = 1.0
  connect(filter.y, clock.y)
  y = filter.y
end

This example requires DiscreteComponents as a library dependency and the SmoothingFilter definition above. PeriodicClock exposes an output clock through a connector named y whose signal type is RealInput. Connecting filter.y to it establishes the clock of the filter's signal. Here the filter runs every 0.1 units of model time. A simulation analysis supplies the time interval and solver settings.

Clock parameters can also be bound explicitly when nesting components:

dyad
component FilterPair@[input clk extends Discrete]
  u = RealInput@[clk]()
  y = RealOutput@[clk]()
  first = SmoothingFilter@[clk = clk]()
  second = SmoothingFilter@[clk = clk]()
relations
  connect(u, first.u)
  connect(first.y, second.u)
  connect(second.y, y)
end

The binding @[clk = clk] names the child's clock on the left and the containing component's clock on the right. @[clk] is the shorthand when those names match.

Use the sample period in a recurrence ​

SampleTime() lets a reusable component obtain the interval of its periodic clock. For example, a backward difference approximates an input derivative:

dyad
component BackwardDifference@[input clk extends Periodic]
  u = RealInput@[clk]()
  y = RealOutput@[clk]()
  parameter u0::Real = 0.0
  structural parameter Ts::Real = SampleTime()
relations
  initial u@(clk-1) = u0
  y@clk = (u@clk - u@(clk-1)) / Ts
end

The initial input history controls the first derivative estimate. Set u0 to the expected starting measurement when a startup jump would be inappropriate.

Design feedback and larger models ​

A same-tick dependency passes through a component immediately in model time. When a feedback loop needs memory, express that memory with a delayed value. For a strictly proper state-space model, omit the direct feedthrough term structurally: multiplying the current input by a parameter whose value is zero can still leave a same-tick dependency.

Choose a worked control pattern:

Use these guides as the model grows:

Synchronous equations assume computation completes at the tick's model time. Execution time on a physical controller must fit its scheduling requirements.

Library compatibility ​

The examples use Latest(init = ...); older tutorials may use LastValue. Sampler initialization options also vary between DiscreteComponents versions. Check the API provided by your installed library. SampleTime() requires a compatible DiscreteComponents/SynchToolkit environment; see Discrete-Time Modeling for the worked simulation setup.