hsim/doc/pres.typ

637 lines
24 KiB
Typst

#import "@preview/cetz:0.3.2"
#import "@preview/cetz-plot:0.1.1"
#import "@preview/finite:0.4.1"
#import "@preview/touying:0.6.1": *
#import themes.simple: *
// Notes
//
// Shorten: too long on the explanation
// Instead, rely on a simple, two-axes presentation: We need
// - continuously described variables: IVP, ODEs
// - scheduling discrete-time signals with continuous ones: solution in simulink
// is zc events
// Need an introduction: what the problem is, what are we doing
// - Executable assertions: before any kind of formal methods, a programmer's
// tool
// - Explaining the simulation as succintly as possible
//
// Zélus:
// - Cite Simulink
//
// Discrete nodes:
// - remove some stuff
// - show signals
// - "redge" -> "rising_edge"
// - "true, false" -> "T, F"
// - cite Mealy
// - OCaml datatype
// - "Given i : I as input stream, simulation defines o : O [...]"
//
// Van der Pol:
// - show the math and oscillator
//
// Continuous nodes:
// - remove some stuff
// - the other way around: model first, low-level representation later (as for
// the discrete)
// - does not typecheck!
// - remind the audience of the vdp oscillator math
// - f_der, f_out
//
// Hybrid nodes:
// - the other way around, too: model first, low-level representation later
// - f_zero
//
// Synthesis: new constructs for continuous time (hybrid, der, up...)
//
// Assertions:
// - Shorter, explain as a programmer's tool
//
// Now, my work:
// - define the runtime as precisely and succintly as possible;
//
// ODE/ZC solvers
// - Too "hand-wavy"
#show: simple-theme.with(
config-common(show-bibliography-as-footnote: bibliography("sources.bib")))
#set raw(syntaxes: "zelus.sublime-syntax")
#show raw: set text(font: "CaskaydiaCove NF")
#let zelus = smallcaps[Zélus]
#let lustre = smallcaps[Lustre]
#let simulink = smallcaps[Simulink]
#let ocaml = smallcaps[OCaml]
#let Time = math.italic[Time]
// 20 mins presentation - 10 mins questions
= Internship Defense --- MPRI M2 \ Hybrid System Models with Transparent Assertions
Henri #smallcaps[Saudubray]#footnote(
[All code accessible at #link("https://codeberg.org/17maiga/hsim").],
numbering: "*"
), supervised by Marc #smallcaps[Pouzet], #smallcaps[Inria PARKAS]
10/09/2025
= Context: #zelus and Hybrid System Models
== The #zelus Programming Language
- Language@cit:zelus_sync_lng_with_ode for description of hybrid system models
- Synchronous language kernel _à la_ #lustre@cit:lustre
- Extension to continuous-time behaviour
- Compilation to #ocaml code
- Execution using an off-the-shelf numerical solver@cit:sundials@cit:sundialsml
== Discrete-Time Systems
Programs (nodes) are functions on streams
#align(center + horizon, [
```zelus
let node rising_edge(x: bool) =
x -> (x && not(pre(x)))
```
#linebreak()
#table(
columns: (auto, auto, auto, auto, auto, auto, auto),
align: center + horizon, stroke: (x, y) => (
left: if x == 0 { 0pt } else { 1pt },
top: if y == 0 { 0pt } else { 1pt },
),
`x`, $F$, $T$, $T$, $F$, $T$, [...],
```zelus pre(x)```, $bot$, $F$, $T$, $T$, $F$, [...],
```zelus x && not(pre(x))```, $bot$, $T$, $F$, $F$, $T$, [...],
```zelus x -> (x && not(pre(x)))```, $F$, $T$, $F$, $F$, $T$, [...],
)
])
// Prononcer les suites, "front montant", `fby` ?
#pagebreak()
Compiled down to Mealy machines@cit:mealy@cit:sync_based_codegen_hyb_sys_lng:
$ s_0 : S wide wide f_"step" : S times I -> O times S $
#pause
With input $i : NN -> I$, simulation defines output $o : NN -> O$
$ (o_n, s_(n+1)) = f_"step" (s_n, i_n) $
== Physical Simulation in Discrete Time
// Mention initial values in vdp
The Van der Pol oscillator:
$ (d x)/(d t) = y wide (d y)/(d t) = mu(1 - x^2)y - x wide x(0) = y(0) = 1 $
#pause
#align(center, cetz.canvas({
cetz-plot.plot.plot(size: (20, 4), axis-style: "school-book",
x-tick-step: 10, y-tick-step: 5, x-label: $t$, y-label: none,
y-grid: "both", legend: (19, 4.5), {
let d = csv("data/vdp.csv")
let x = d.map(r => (r.at(0), r.at(1)).map(v => float(v.trim())))
let y = d.map(r => (r.at(0), r.at(2)).map(v => float(v.trim())))
cetz-plot.plot.add(x, line: "spline", label: $x$)
cetz-plot.plot.add(y, line: "spline", label: $y$)
})
}))
#pagebreak()
#only((1,2))[
```zelus
let h = 0.001
(* Euler integrators. *)
let node f_euler(x0, x') = x where
rec x = x0 -> pre(x + x' * h)
let node b_euler(x0, x') = x where
rec x = x0 -> pre(x) + x' * h
(* Van der Pol oscillator. *)
let node vdp(μ) = (x, y) where
rec x = b_euler(1.0, y)
and y = f_euler(1.0, (μ * (1.0 - (x * x)) * y) - x)
```
]
#only(2)[
#place(dx: 55%, dy: -70%, cetz.canvas({
let d = csv("data/xsmall.csv")
let x = d.map(r => (r.at(0), r.at(1)).map(v => float(v.trim())))
let y = d.map(r => (r.at(0), r.at(2)).map(v => float(v.trim())))
cetz-plot.plot.plot(size: (10, 5), axis-style: "school-book",
x-tick-step: 10, y-tick-step: 5, x-grid: "both", y-grid: "both",
y-min: -8, y-max: 8, legend: (9, 5.5), y-label: none, x-label: none, {
cetz-plot.plot.add(x, label: $x$)
cetz-plot.plot.add(y, label: $y$)
})
}))
]
#only(3)[
```zelus
let h = 0.1
(* Euler integrators. *)
let node f_euler(x0, x') = x where
rec x = x0 -> pre(x + x' * h)
let node b_euler(x0, x') = x where
rec x = x0 -> pre(x) + x' * h
(* Van der Pol oscillator. *)
let node vdp(μ) = (x, y) where
rec x = b_euler(1.0, y)
and y = f_euler(1.0, (μ * (1.0 - (x * x)) * y) - x)
```
#place(dx: 55%, dy: -70%, cetz.canvas({
let d = csv("data/middle.csv")
let x = d.map(r => (r.at(0), r.at(1)).map(v => float(v.trim())))
let y = d.map(r => (r.at(0), r.at(2)).map(v => float(v.trim())))
cetz-plot.plot.plot(size: (10, 5), axis-style: "school-book",
x-tick-step: 10, y-tick-step: 5, x-grid: "both", y-grid: "both",
y-min: -8, y-max: 8, legend: (9, 5.5), y-label: none, x-label: none, {
cetz-plot.plot.add(x, label: $x$)
cetz-plot.plot.add(y, label: $y$)
})
}))
]
== Continuous-Time Systems
Extension to continuous time. \ #pause
New constructs: ```zelus hybrid```, ```zelus der```, ```zelus init```
$ (d x)/(d t) = y wide (d y)/(d t) = mu(1 - x^2)y - x wide x(0) = y(0) = 1 $
```zelus
let hybrid vdp(μ) = (x, y) where
rec der x = y init 1.0
and der y = (μ * (1.0 - (x * x)) * y) - x) init 1.0
```
#pagebreak()
Programs are compiled down to initial value problems (IVPs)
$ y_0 : Y wide f_"der" : Time times I times Y -> Y'
wide f_"out" : Time times I times Y -> O $
#pause
Use a numerical solver to compute an approximation of $y : Time -> Y$
$ y(0) = y_0 wide (d y)/(d t)(t) = f_"der" (t, i(t), y(t)) $
#pause
With input $i : Time -> I$, simulation defines output
$o : Time -> O$
$ o(t) = f_"out" (t, i(t), y(t)) $
== Hybrid Systems
- Mixing discrete and continuous behaviours #pause
- Discrete steps are scheduled with _zero-crossing events_
#grid(align: horizon + center, columns: (1fr, 4fr), {
let p(b) = cetz.canvas({
cetz-plot.plot.plot(
axis-style: "left", size: (5, 3),
y-tick-step: none, y-label: none,
x-tick-step: none, x-label: none, b
)
})
let data = csv("data/ball.csv").map(r => r.map(float))
alternatives-match((
"1-3": p({
cetz-plot.plot.add(data, line: "spline")
}),
"4-": p({
cetz-plot.plot.add(data, line: "spline")
cetz-plot.plot.add((
(3.2, 0), (8.3, 0), (12.4, 0), (15.6, 0), (18.2, 0), (20.3, 0),
(22.0, 0), (23.2, 0), (24, 0), (24.5, 0), (24.8, 0), (25, 0),
(25.1, 0), (25.2, 0), (25.3, 0), (25.4, 0), (25.5, 0), (25.6, 0),
(25.7, 0), (25.8, 0), (25.9, 0), (26.0, 0), (26.1, 0), (26.2, 0),
(26.3, 0), (26.4, 0), (26.5, 0), (26.6, 0), (26.7, 0), (26.8, 0),
(26.9, 0), (27.0, 0), (27.1, 0), (27.2, 0), (27.3, 0), (27.4, 0),
(27.5, 0), (27.6, 0), (27.7, 0), (27.8, 0), (27.9, 0), (28.0, 0),
), mark: "o", mark-size: .1)
})
))
}, [
#pause $ (d y)/(d t) = y' wide (d y')/(d t) = -g $
#pause "When $y < 0$ at time $t$, $y'(t) = -k times (lim_(h -> t^-) y'(h))$"
])
#pause
```zelus
let hybrid bouncing_ball(y0, y'0) = y where
rec der y = y' init y0
and der y' = -g init y'0 reset up(-y) -> -k * (last y')
```
#pagebreak()
Compiled down to both a Mealy machine and an IVP \ #pause
Use of a zero-crossing detection mechanism@cit:illinois to monitor for events
$ f_"zero" : Time times I times Y -> Z $
== Models with assertions
// Observers rather than sub-nodes...
// Mention more clearly that they are executable
- In #lustre, assertions are observers on the parent state@cit:assertion_lustre
#only(2)[
- Hybrid assertions are the same!
```zelus
let hybrid sincos() = (sin, cos) where
rec der sin = cos init 0.0
and der cos = -sin init 1.0
and assert(
let der neg_cos = sin init -1.0 in
abs(neg_cos + cos) <= ε
)
```
]
== Interferences
Simultaneous simulation of independent systems causes interferences
#{
let code1 = ```zelus
let hybrid ball() = p where
rec der p = v init 0.0
and der v = a * (cos p) init 2.953
and a = g * (sin p) - 5.0 * v / (cos p)
let hybrid main() = p where
p = ball()
```
let code2 = ```zelus
let hybrid ball() = p where
rec der p = v init 0.0
and der v = a * (cos p) init 2.953
and a = g * (sin p) - 5.0 * v / (cos p)
let hybrid main() = p where
p = ball() and _ = vdp(10.0)
```
let plot(file, x-step) = cetz.canvas({
let x = csv(file).map(
row => (float(row.at(0).trim()), float(row.at(1).trim())))
cetz-plot.plot.plot(axis-style: "left", size: (7, 2), y-tick-step: none,
y-max: calc.pi * 2, y-min: 0, x-grid: "major",
x-tick-step: none, x-label: none, y-label: none, legend: (6, 2),
x-ticks: ((x-step, ""),), {
cetz-plot.plot.add(x, label: `p`)
})
})
let plot1(t) = plot("data/0000001.csv", t)
let plot2(t) = plot("data/10.csv", t)
let plot_ball(coords) = cetz.canvas({
cetz-plot.plot.plot(
size: (7, 2), axis-style: "left",
x-label: `p`, y-label: [~~~], x-tick-step: none, y-tick-step: none, {
cetz-plot.plot.add(t => calc.cos(t), domain: (0, 13), samples: 500)
})
cetz.draw.circle(coords, radius: .15)
})
let anim1 = (
((0.2, 2.10), 00.0), ((1.0, 1.10), 00.5), ((1.8, 0.20), 01.0),
((2.5, 1.30), 01.5), ((3.1, 2.10), 02.5), ((3.2, 2.15), 03.5),
((3.1, 2.10), 04.5), ((2.5, 1.30), 06.0), ((1.8, 0.20), 07.5),
((1.0, 1.10), 08.0), ((1.7, 0.15), 09.0), ((1.9, 0.30), 10.0),
((1.7, 0.15), 12.0), ((1.5, 0.30), 14.0), ((1.7, 0.15), 18.0),
((1.7, 0.15), 20.0), ((1.7, 0.15), 30.0), ((1.7, 0.15), 50.0),
((1.7, 0.15), 60.0),
).map(coords => grid(columns: (7fr, 3fr), align: center, code1, [
#plot_ball(coords.at(0))
#v(-10%)
#plot1(coords.at(1) / 2)
]))
let anim2 = (
((0.2, 2.10), 00.0), ((1.0, 1.10), 00.5), ((1.8, 0.20), 01.0),
((2.5, 1.30), 01.5), ((3.1, 2.10), 02.0), ((3.4, 2.15), 03.5),
((3.7, 2.05), 05.5), ((4.3, 1.20), 07.0), ((5.1, 0.15), 07.5),
((5.8, 1.15), 08.2), ((6.5, 2.05), 08.5), ((6.6, 2.15), 08.6),
((6.5, 2.10), 09.2), ((5.9, 1.30), 09.8), ((5.2, 0.20), 10.2),
((4.4, 1.10), 11.0), ((5.1, 0.15), 12.5), ((5.3, 0.30), 13.0),
((5.1, 0.15), 14.0), ((4.9, 0.30), 16.0), ((5.1, 0.15), 20.0),
((5.1, 0.15), 22.0), ((5.1, 0.15), 30.0), ((5.1, 0.15), 50.0),
((5.1, 0.15), 60.0),
).map(coords => grid(columns: (7fr, 3fr), align: center, code2, [
#plot_ball(coords.at(0))
#v(-10%)
#plot2(coords.at(1) / 2)
]))
alternatives(
align(center, cetz.canvas({
cetz-plot.plot.plot(size: (15, 5), x-tick-step: none, y-tick-step: none,
axis-style: "left", y-label: none, x-label: `p`, {
cetz-plot.plot.add(t => calc.cos(t), domain: (0, 13), samples: 500)
})
cetz.draw.circle((0.4, 5.25), radius: 0.3)
cetz.draw.line((0.8, 5), (1.5, 4.5), mark: (end: "straight"))
})),
grid(columns: (7fr, 3fr), align: center, code1, []), ..anim1,
grid(columns: (7fr, 3fr), align: center, code2, []), ..anim2,
)
}
#only(47)[This is the main issue with assertions]
== Summary
- Extension of a synchronous kernel to continuous time (```zelus der```,
```zelus init```, ...)
- Scheduling of discrete steps using zero-crossing events (```zelus up```, ...)
- Execution using a numerical solver
- Simultaneous simulation causes interferences
= Formalizing the Runtime
== Numerical Solvers
- Used to solve the model's initial value problem
- Integration is done _sequentially_ on successive intervals
$ italic("csolve")(italic("ivp")) :
(h : Time) -> (h' : [0, h]) times (u : [0, h'] -> Y) $
#pause
#align(center, {
import cetz-plot.plot: *
let p(b, t) = cetz.canvas(plot(
size: (10, 2), axis-style: "school-book", legend: (9.2, 2.5),
y-tick-step: none, y-label: none, y-min: 0, y-max: 5, x-tick-step: none,
x-label: none, x-min: 0, x-max: 5.5, x-ticks: t, b
))
alternatives(
p({
add(domain: (0, 2), t => calc.sin(t) + t, label: $u$)
}, ((2, $h'$), (5, $h$))),
p({
add(domain: (2, 4), t => calc.sin(t) + t, label: $u$)
add(domain: (0, 2), t => calc.sin(t) + t)
}, ((2, ""), (4, $h'$), (5, $h$))),
p({
add(domain: (4, 5), t => calc.sin(t) + t, label: $u$)
add(domain: (0, 4), t => calc.sin(t) + t)
}, ((2, ""), (4, ""), (5, $h', h$)))
)
}) #pause #pause #pause
#only("5-")[- Solvers maintain their own internal state]
#only("6-")[- Viewed as discrete nodes]
== Zero-crossing Detection
- Detection of zero-crossing events using the integration results \
$ italic("zsolve")(f_"zero") : (h : Time) times ([0, h] -> Y) -> (h' : [0, h]) times italic("Option")(Z_i) $
#pause
#align(center, alternatives(
cetz.canvas({
cetz-plot.plot.plot(
size: (10, 2), axis-style: "school-book", legend: (9, 2.5),
y-tick-step: none, y-label: none, y-min: -2, y-max: 5,
x-tick-step: none, x-label: none, x-min: 0, x-max: 2.2,
x-ticks: ((2/3, $h', h$),), {
cetz-plot.plot.add(domain: (0, 2/3), t => t * t - 1)
})
}),
cetz.canvas({
cetz-plot.plot.plot(
size: (10, 2), axis-style: "school-book", legend: (9, 2.5),
y-tick-step: none, y-label: none, y-min: -2, y-max: 5,
x-tick-step: none, x-label: none, x-min: 0, x-max: 2.2,
x-ticks: ((1, $h'$), (4/3, $h$)), {
cetz-plot.plot.add(domain: (2/3, 4/3), t => t * t - 1)
cetz-plot.plot.add(domain: (0, 2/3), t => t * t - 1)
})
cetz.draw.line((4.55, 2.5), (4.55, 1.5), mark: (end: "straight"))
}),
cetz.canvas({
cetz-plot.plot.plot(
size: (10, 2), axis-style: "school-book", legend: (9, 2.5),
y-tick-step: none, y-label: none, y-min: -2, y-max: 5,
x-tick-step: none, x-label: none, x-min: 0, x-max: 2.2,
x-ticks: ((1, ""), (2, $h', h$),), {
cetz-plot.plot.add(domain: (1, 2), t => -t * t + 3.5 * t)
cetz-plot.plot.add(domain: (0, 1), t => t * t - 1)
})
})
))
#pause
- Discrete steps are performed when a zero-crossing event is detected #pause
- State changes may occur during discrete steps
#pause
- Viewed as discrete nodes
== Full simulation
- Simulations are discrete nodes on streams of functions
- Two step modes: discrete and continuous
#align(center + horizon, grid(columns: (1fr, 1fr), {
let a(s) = finite.automaton(
(D: (D: "", C: ""),
C: (C: "", D: "zero-crossing")),
initial: "D", final: (), layout: finite.layout.linear.with(spacing: 6),
style: s
)
alternatives(
a((D: (fill: gray))),
a((C: (fill: gray), D-C: (stroke: red))),
a((C: (fill: gray), C-C: (stroke: red))),
a((D: (fill: gray), C-D: (stroke: red))),
a((C: (fill: gray), D-C: (stroke: red))),
a((C: (fill: gray), C-C: (stroke: red))),
a((D: (fill: gray), C-D: (stroke: red))),
a((D: (fill: gray), D-D: (stroke: red))),
a((C: (fill: gray), D-C: (stroke: red))),
)
}, {
let p(b, t: ()) = cetz.canvas(cetz-plot.plot.plot(
size: (8, 5), axis-style: "school-book",
x-tick-step: none, x-label: none, x-min: 0, x-max: 10,
y-tick-step: none, y-label: none, y-min: -3, y-max: 10, b,
x-ticks: t, x-grid: "both"
))
alternatives[#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(((0, 4),), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(((0, -1),), label: "zero", style: (stroke: 2pt + green))
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2), label: "zero", style: (stroke: 2pt + green))
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 3), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 3), label: "zero", style: (stroke: 2pt + green))
}, t: ((2.25, ""),))][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(((2.25, 3),), mark-style: (stroke: red + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((2.25, -1),), mark-style: (stroke: green + 2pt), mark: "+", mark-size: .04)
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(t => calc.cos(t) + 3.65, domain: (2.25, 6), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 1.8, domain: (2.25, 6), style: (stroke: green + 2pt))
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(t => calc.cos(t) + 3.65, domain: (2.25, 8), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 1.8, domain: (2.25, 8), style: (stroke: green + 2pt))
}, t: ((7.2, ""),))][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(t => calc.cos(t) + 3.65, domain: (2.25, 7.2), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 1.8, domain: (2.25, 7.2), style: (stroke: green + 2pt))
cetz-plot.plot.add(((7.2, 5),), mark-style: (stroke: red + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((7.2, 1),), mark-style: (stroke: green + 2pt), mark: "+", mark-size: .04)
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(t => calc.cos(t) + 3.65, domain: (2.25, 7.2), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 1.8, domain: (2.25, 7.2), style: (stroke: green + 2pt))
cetz-plot.plot.add(((7.2, 5),), mark-style: (stroke: red + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((7.2, 1),), mark-style: (stroke: green + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((7.2, 6),), mark-style: (stroke: red + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((7.2, -1),), mark-style: (stroke: green + 2pt), mark: "+", mark-size: .04)
})][#p({
cetz-plot.plot.add(t => calc.sin(t - 1/(0.1*t+0.1)) + 8 + 0.1 * t,
domain: (0, 10), label: "in", samples: 500, style: (stroke: 2pt + blue))
cetz-plot.plot.add(t => calc.cos(t) + 3, domain: (0, 2.25), label: "out", style: (stroke: 2pt + red))
cetz-plot.plot.add(t => 0.2 * t * t - 1, domain: (0, 2.25), label: "zero", style: (stroke: 2pt + green))
cetz-plot.plot.add(t => calc.cos(t) + 3.65, domain: (2.25, 7.2), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 1.8, domain: (2.25, 7.2), style: (stroke: green + 2pt))
cetz-plot.plot.add(((7.2, 5),), mark-style: (stroke: red + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(((7.2, 1),), mark-style: (stroke: green + 2pt), mark: "+", mark-size: .04)
cetz-plot.plot.add(t => t * (0.04 * calc.cos(t)) - 0.2 * (1 / t * t) + 6, domain: (7.2, 10), style: (stroke: red + 2pt))
cetz-plot.plot.add(t => calc.sin(t) + (0.2 * (t - 2.25)) - 2.8, domain: (7.2, 10), style: (stroke: green + 2pt))
})]
}))
== Lifting simulations
- We can manipulate simulations directly in #zelus
```zelus
type 'a dense = time * (time -> 'a)
type 'a signal = 'a dense option
val solve : ('a -C-> 'b) -S-> ('a signal -D-> 'b signal)
```
#pause
- Running part of the model with its own solver instance is possible #pause
- This is exactly what we need for assertions!
== Simulating assertions
- Compile models with assertions down to two separate models
#pause
- Simulate the assertion independently with `solve`
#pause
- At each step of the parent model, run the assertion up to the horizon
#align(center, {
let p(b, t) = cetz.canvas(cetz-plot.plot.plot(
size: (10, 4), axis-style: "left",
x-label: none, y-label: none, x-tick-step: none, y-tick-step: none,
y-min: 0, x-min: 0.1, x-max: 10, x-ticks: t,
b
))
import cetz-plot.plot: add
alternatives(
p({
add(t => calc.sin(t) + 5, domain: (0, 5), label: "Parent", samples: 500)
add(((0, 0),), label: "Assertion", samples: 500)
}, ((5, ""),)),
p({
add(t => calc.sin(t) + 5, domain: (0, 5), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 2), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""))),
p({
add(t => calc.sin(t) + 5, domain: (0, 5), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 5), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""))),
p({
add(t => calc.sin(t) + 5, domain: (0, 10), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 5), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""), (10, ""))),
p({
add(t => calc.sin(t) + 5, domain: (0, 10), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 7), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""), (10, ""), (7, ""))),
p({
add(t => calc.sin(t) + 5, domain: (0, 10), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 9), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""), (10, ""), (7, ""), (9, ""))),
p({
add(t => calc.sin(t) + 5, domain: (0, 10), label: "Parent", samples: 500)
add(t => calc.cos(2 * t) + 2, domain: (0, 10), label: "Assertion", samples: 500)
}, ((5, ""), (2, ""), (10, ""), (7, ""), (9, ""))),
)
})
= Conclusion
== Conclusion
*Results*
- Efficient implementation of a hybrid system simulation runtime
- Primitives to allow manipulation of simulations directly in #zelus
- Assertions representable as continuous observers
#pause
*Future Work*
- Implementation of a compilation pass (in progress)
- Support for _nested assertions_
- Concerns related to lifting simulations (can we use ```zelus pre```)?