hsim/doc/notes.typ

97 lines
3.7 KiB
Typst

#import "@preview/cetz:0.3.2"
#import "@preview/cetz-plot:0.1.1"
#set page(paper: "a4")
#set par(justify: true)
= Notes on hybrid system simulation
== The problem
The initial problem is _transparent assertions_. We want to simulate assertions
with their own solvers, because simulating assertions with the same solver as
the parent system changes the final results, when assertions are supposed to be
transparent.
Assertions are themselves hybrid systems, and can contain their own assertions.
We thus need a semantics that is both *independent* of the solver, which becomes
a parameter, and *recursive*, in that the simulation of an assertion is itself
the simulation of a hybrid system with assertions.
The main idea is as follows: a solver can be seen as a synchronous function (an
inner state, a step function, and a reset function).
```ocaml
type ('p, 'a, 'b) dnode = DNode :
{ s: 's; step: 's -> 'a -> 'b * 's; reset : 'p -> 's -> 's } ->
('p, 'a, 'b) dnode
```
An ODE solver is, in fact, a special case of a discrete node, which is
initialized with an initial value problem, takes as input a time horizon to
reach, and returns as output an actual time reached and a dense solution.
```ocaml
type ('y, 'yder) csolver =
(('y, 'yder) ivp, time, time * (time -> 'y)) dnode
```
Similarly, a zero-crossing solver is initialized with a zero-crossing problem,
takes as input a horizon and dense function, and returns an actual time reached
and optional zero-crossing.
```ocaml
type ('y, 'zin, 'zout) zsolver =
(('y, 'zout) zc, time * (time -> 'y), time * 'zin option) dnode
```
A complete solver is then a composition of these two: it is initialized with
both an `ivp` and `zc`, takes in a horizon, and returns an actual time reached,
dense solution and optional zero-crossing.
```ocaml
type ('y, 'yder, 'zin, 'zout) solver =
(('y, 'yder) ivp * ('y, 'zout) zc, time, time * (time -> 'y) * 'zin option) dnode
```
The construction of a full solver from an ODE and a zero-crossing solver is
available in the file `src/lib/hsim/solver.ml`.
The simulation of a hybrid system with a solver can itself be seen as a discrete
node, operating on streams of functions defined on successive intervals.
== Output format
In "lazy" mode, the output has format ```ocaml 'b signal = 'b value option```.
In "greedy" mode, the output has format ```ocaml 'b value list```.
Indeed, we cannot concatenate a discrete step (i.e. a function defined on a
singleton interval $[t,t]$) with anything else, as it may be hidden by the
(inclusive) border of the other function:
#align(center, ```ocaml
f = concat { start = 0.0; length = 1.0; u = fun t -> t }
{ start = 1.0; length = 0.0; u = fun t -> 2.0 }
```)
#columns(2, [
#align(center, cetz.canvas({
cetz-plot.plot.plot(
size: (1, 1), x-tick-step: 1, y-tick-step: 1, axis-style: "left", {
cetz-plot.plot.add(((0,0),(1,1)))
cetz-plot.plot.add(((1,2),), mark: "o", mark-size: .03)})}))
#colbreak()
#linebreak()
$ f(t) = cases(
t & "if" 0.0 <= t <= 1.0,
2.0 & "if" t = 1.0 + partial,
bot & "otherwise",
) $
])
How do we represent infinitesimal steps in floating-point numbers ?
A possible representation of the above could be
#align(center, ```ocaml
f = [{ start = 0.0; length = 1.0; u = fun t -> t };
{ start = 1.0; length = 0.0; u = fun _ -> 2.0 }]
```)
but returning a list is problematic, however, as it cannot be passed easily as
input to the underlying models. Unless the input can itself be considered as a
list, in which case the simulation simply operates over the whole list,
resetting the solver as needed. This has the advantage of not having to wait
several steps, providing nothing, until the solver is done integrating the
input, before we can provide it with another input.