#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.