Skip to content
LIBRARY
Nonlinear.Tests.PadeDelay.md

Nonlinear.Tests.PadeDelay

Test harness for the PadeDelay block.

A unit step (0 -> 1 at t = 0.5) is fed through three Padé delays with delayTime = 1:

  • pd1 — first order (n = 1), balanced (default).

  • pd3 — third order (n = 3), balanced (default).

  • pd3u — third order (n = 3), unbalanced (balance = false).

Expected behavior (an approximation of a 1-second delay):

  • Each output starts with the characteristic non-minimum-phase undershoot (y jumps to -1 the instant the step arrives, because m = n gives direct feedthrough d = -1).

  • Each settles to 1 (DC gain = 1: a delayed constant is the same constant).

  • pd3 holds near zero longer and then rises more steeply than pd1 (a sharper delay approximation), at the cost of some ringing.

  • pd3u must reproduce pd3 exactly: balancing is a state transformation that changes only the internal coordinates, not the input->output response.

A slow sine (sine, amplitude 1, frequency 0.25 → period 4, i.e. much slower than delayTime = 1) is also fed through pd_sine (n = 3). This demonstrates the block's core purpose — a time shift: pd_sine.y is the sine delayed by ~1 s (a quarter period), with amplitude preserved.

Usage

BlockComponents.Nonlinear.Tests.PadeDelay()

Behavior

julia
using BlockComponents #hide
using ModelingToolkit #hide
@named sys = BlockComponents.Nonlinear.Tests.PadeDelay() #hide
let eqs = full_equations(sys); Base.length(eqs) > 25 ? nothing : eqs end #hide
<< @example-block not executed in draft mode >>

Source

dyad
"""
Test harness for the PadeDelay block.

A unit step (0 -> 1 at t = 0.5) is fed through three Padé delays with
`delayTime = 1`:
- `pd1`  — first order (n = 1), balanced (default).
- `pd3`  — third order (n = 3), balanced (default).
- `pd3u` — third order (n = 3), unbalanced (`balance = false`).

Expected behavior (an approximation of a 1-second delay):
- Each output starts with the characteristic non-minimum-phase undershoot
  (`y` jumps to -1 the instant the step arrives, because m = n gives direct
  feedthrough d = -1).
- Each settles to 1 (DC gain = 1: a delayed constant is the same constant).
- `pd3` holds near zero longer and then rises more steeply than `pd1` (a sharper
  delay approximation), at the cost of some ringing.
- `pd3u` must reproduce `pd3` exactly: balancing is a state transformation that
  changes only the internal coordinates, not the input->output response.

A slow sine (`sine`, amplitude 1, frequency 0.25 → period 4, i.e. much slower
than `delayTime = 1`) is also fed through `pd_sine` (n = 3). This demonstrates the
block's core purpose — a time shift: `pd_sine.y` is the sine delayed by ~1 s
(a quarter period), with amplitude preserved.
"""
test component PadeDelay
  "Unit step at t = 0.5"
  step = BlockComponents.Sources.Step(height = 1.0, offset = 0.0, start_time = 0.5) {
    "Dyad": {"placement": {"diagram": {"x1": 20, "y1": 230, "x2": 120, "y2": 330}}}
  }
  "First-order Padé delay, delayTime = 1"
  pd1 = BlockComponents.Nonlinear.PadeDelay(n = 1, delayTime = 1.0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 20, "x2": 340, "y2": 120, "rot": 0}
      }
    }
  }
  "Third-order Padé delay, delayTime = 1 (balanced realization, default)"
  pd3 = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0) {
    "Dyad": {"placement": {"diagram": {"x1": 240, "y1": 230, "x2": 340, "y2": 330}}}
  }
  "Third-order Padé delay, unbalanced realization — output must match pd3"
  pd3u = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0, balance = false) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 420, "x2": 340, "y2": 520, "rot": 0}
      }
    }
  }
  "Slow sine input (period 4), to show the delay as a time shift"
  sine = BlockComponents.Sources.Sine(amplitude = 1.0, frequency = 0.25) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 20, "y1": 620, "x2": 120, "y2": 720, "rot": 0}
      }
    }
  }
  "Third-order Padé delay applied to the slow sine"
  pd_sine = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 620, "x2": 340, "y2": 720, "rot": 0}
      }
    }
  }
relations
  connect(step.y, pd1.u) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [{"x": 161.66666666666666, "y": 280}, {"x": 161.66666666666666, "y": 70}],
          "E": 2
        }
      ]
    }
  }
  connect(step.y, pd3.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(step.y, pd3u.u) {
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 165, "y": 280}, {"x": 165, "y": 470}], "E": 2}]}
  }
  connect(sine.y, pd_sine.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
metadata {
  "Dyad": {
    "icons": {"default": "dyad://BlockComponents/Example.svg"},
    "tests": {
      "case1": {
        "stop": 8,
        "atol": {"pd1.y": 0.0001, "pd3.y": 0.0001, "pd3u.y": 0.0001, "pd_sine.y": 0.0001},
        "expect": {"signals": ["step.y", "pd1.y", "pd3.y", "pd3u.y", "sine.y", "pd_sine.y"]}
      }
    }
  }
}
end
Flattened Source
dyad
"""
Test harness for the PadeDelay block.

A unit step (0 -> 1 at t = 0.5) is fed through three Padé delays with
`delayTime = 1`:
- `pd1`  — first order (n = 1), balanced (default).
- `pd3`  — third order (n = 3), balanced (default).
- `pd3u` — third order (n = 3), unbalanced (`balance = false`).

Expected behavior (an approximation of a 1-second delay):
- Each output starts with the characteristic non-minimum-phase undershoot
  (`y` jumps to -1 the instant the step arrives, because m = n gives direct
  feedthrough d = -1).
- Each settles to 1 (DC gain = 1: a delayed constant is the same constant).
- `pd3` holds near zero longer and then rises more steeply than `pd1` (a sharper
  delay approximation), at the cost of some ringing.
- `pd3u` must reproduce `pd3` exactly: balancing is a state transformation that
  changes only the internal coordinates, not the input->output response.

A slow sine (`sine`, amplitude 1, frequency 0.25 → period 4, i.e. much slower
than `delayTime = 1`) is also fed through `pd_sine` (n = 3). This demonstrates the
block's core purpose — a time shift: `pd_sine.y` is the sine delayed by ~1 s
(a quarter period), with amplitude preserved.
"""
test component PadeDelay
  "Unit step at t = 0.5"
  step = BlockComponents.Sources.Step(height = 1.0, offset = 0.0, start_time = 0.5) {
    "Dyad": {"placement": {"diagram": {"x1": 20, "y1": 230, "x2": 120, "y2": 330}}}
  }
  "First-order Padé delay, delayTime = 1"
  pd1 = BlockComponents.Nonlinear.PadeDelay(n = 1, delayTime = 1.0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 20, "x2": 340, "y2": 120, "rot": 0}
      }
    }
  }
  "Third-order Padé delay, delayTime = 1 (balanced realization, default)"
  pd3 = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0) {
    "Dyad": {"placement": {"diagram": {"x1": 240, "y1": 230, "x2": 340, "y2": 330}}}
  }
  "Third-order Padé delay, unbalanced realization — output must match pd3"
  pd3u = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0, balance = false) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 420, "x2": 340, "y2": 520, "rot": 0}
      }
    }
  }
  "Slow sine input (period 4), to show the delay as a time shift"
  sine = BlockComponents.Sources.Sine(amplitude = 1.0, frequency = 0.25) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 20, "y1": 620, "x2": 120, "y2": 720, "rot": 0}
      }
    }
  }
  "Third-order Padé delay applied to the slow sine"
  pd_sine = BlockComponents.Nonlinear.PadeDelay(n = 3, m = 3, delayTime = 1.0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 620, "x2": 340, "y2": 720, "rot": 0}
      }
    }
  }
relations
  connect(step.y, pd1.u) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [{"x": 161.66666666666666, "y": 280}, {"x": 161.66666666666666, "y": 70}],
          "E": 2
        }
      ]
    }
  }
  connect(step.y, pd3.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(step.y, pd3u.u) {
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 165, "y": 280}, {"x": 165, "y": 470}], "E": 2}]}
  }
  connect(sine.y, pd_sine.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
metadata {
  "Dyad": {
    "icons": {"default": "dyad://BlockComponents/Example.svg"},
    "tests": {
      "case1": {
        "stop": 8,
        "atol": {"pd1.y": 0.0001, "pd3.y": 0.0001, "pd3u.y": 0.0001, "pd_sine.y": 0.0001},
        "expect": {"signals": ["step.y", "pd1.y", "pd3.y", "pd3u.y", "sine.y", "pd_sine.y"]}
      }
    }
  }
}
end


Test Cases

julia
using BlockComponents
using DyadInterface: TransientAnalysis, rebuild_sol, ODEAlg
using ModelingToolkit: toggle_namespacing, get_initial_conditions, @named
using CSV, DataFrames, Plots

snapshotsdir = joinpath(dirname(dirname(pathof(BlockComponents))), "test", "snapshots")
<< @setup-block not executed in draft mode >>

Test Case case1

julia
@named model_case1 = BlockComponents.Nonlinear.Tests.PadeDelay()
model_case1 = toggle_namespacing(model_case1, false)

model_case1 = toggle_namespacing(model_case1, true)
result_case1 = TransientAnalysis(; model = model_case1, alg = ODEAlg.Auto(), start = 0e+0, stop = 8e+0, abstol=1e-6, reltol=1e-6)
sol_case1 = rebuild_sol(result_case1)
<< @setup-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.step.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig0.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.step.y], width=2, label="Actual value of step.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of step.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.pd1.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig1.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.pd1.y], width=2, label="Actual value of pd1.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of pd1.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.pd3.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig2.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.pd3.y], width=2, label="Actual value of pd3.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of pd3.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.pd3u.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig3.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.pd3u.y], width=2, label="Actual value of pd3u.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of pd3u.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.sine.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig4.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.sine.y], width=2, label="Actual value of sine.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of sine.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>
julia
df_case1 = DataFrame(:t => sol_case1[:t], :actual => sol_case1[model_case1.pd_sine.y])
dfr_case1 = try CSV.read(joinpath(snapshotsdir, "BlockComponents.Nonlinear.Tests.PadeDelay_case1_sig5.ref"), DataFrame); catch e; nothing; end
plt = plot(sol_case1, idxs=[model_case1.pd_sine.y], width=2, label="Actual value of pd_sine.y")
if !isnothing(dfr_case1)
  scatter!(plt, dfr_case1.t, dfr_case1.expected, mc=:red, ms=3, label="Expected value of pd_sine.y")
end
<< @setup-block not executed in draft mode >>
julia
plt
<< @example-block not executed in draft mode >>