![image](https://hackmd.io/_uploads/ByiD7mCN0.png) A block $b$ is composed of $N$ ports $b_{p1}\dots b_{pN}$, one terminal $b_t$, and a $N$-ary function mapping values on the ports to a value on the terminal. The value of this function is denoted $b(b_{p1}\dots b_{pN})$. ![image](https://hackmd.io/_uploads/Hy8Q4QR4A.png) A wire connects a terminal to a port. For a wire $w$, let $w_p$ and $w_t$ denote its port and terminal. Terminals and ports are nodes. Nodes are composed of a set of values and a value. Let $U_n$ denote the set associated with the node $n$. At time $t$, let $n(t)$ denote the value of $n$. The value of a node is either an element of its space or nothing. That is, $\forall t(n(t)\in U_n\lor n(t)\equiv\varnothing)$. A wire connecting port $p$ to terminal $t$ is valid if $U_p\subseteq U_t$. That is, all of the port's values are admissible values for the terminal. A block diagram is composed of a set $B$ of blocks, and a set $W$ of valid wires. For a block $b\in B$ at time $t$, if $\exists b_p,b_p(t)\equiv\varnothing$, then $b(b_{p1}\dots b_{pN})\equiv\varnothing$. That is, if any of $b$'s ports have no value, then $b$'s function outputs no value. To execute a block diagram, the value of each block's port is updated, then values are propogated along its wires. ![image](https://hackmd.io/_uploads/ryx41S0NA.png) _Update June 6._ This execution algorithm isn't quite right. Consider the block diagram from [First GDS](https://hackmd.io/lFTRNOAlTWuVCxgzFieV3w): ![image](https://hackmd.io/_uploads/S1QziJlBA.png) Suppose at $t=0$ each terminal has some initial state. At $t=1$, a new value for $\Delta$ is selected and this is propogated to the polar-to-cartesian block; However, at this point the output values of this block are not updated, and in turn the output values of the cartesian-to-polar block are not updated. At $t=2$, a new value for $\Delta$ is selected and added to the same polar coordinates used at $t=1$ (as the output values of the cartesian-to-polar block were updated). From then on, things are silly and weird. To make this concrete, the program below implements the specification above. ```julia struct Node{Space} space::Type value::Dict{UInt,Space} function Node(t::Type) new{t}(t, Dict()) end end struct Block terminals::Vector{Node} port::Node logic::Function end struct Wire port::Node terminal::Node function Wire(port::Node, terminal::Node) new(port, terminal) end end # Defining blocks cartesian2radius = Block([Node(Int), Node(Int)], Node(Real), (x, y) -> sqrt(x*x + y*y)) cartesian2theta = Block([Node(Int), Node(Int)], Node(Real), (x, y) -> atan(y, x)) addDelta = Block([Node(Real)], Node(Real), (theta) -> theta + rand()*2*pi) polar2x = Block([Node(Real), Node(Real)], Node(Int), (r, th) -> floor(r * cos(th))) polar2y = Block([Node(Real), Node(Real)], Node(Int), (r, th) -> floor(r * sin(th))) blocks = [cartesian2radius, cartesian2theta, addDelta, polar2x, polar2y] # Wiring wires = [] push!(wires, Wire(cartesian2radius.port, polar2x.terminals[1])) push!(wires, Wire(cartesian2radius.port, polar2y.terminals[1])) push!(wires, Wire(cartesian2theta.port, addDelta.terminals[1])) push!(wires, Wire(addDelta.port, polar2x.terminals[2])) push!(wires, Wire(addDelta.port, polar2y.terminals[2])) push!(wires, Wire(polar2x.port, cartesian2radius.terminals[1])) push!(wires, Wire(polar2y.port, cartesian2radius.terminals[2])) push!(wires, Wire(polar2x.port, cartesian2theta.terminals[1])) push!(wires, Wire(polar2y.port, cartesian2theta.terminals[2])) # Initial state time = 0 cartesian2radius.port.value[time] = 10 cartesian2theta.port.value[time] = 0 while cartesian2radius.port.value[time] > 0 global time += 1 for b in blocks v = [] hasvs = true for n in b.terminals if !haskey(n.value, time - 1) hasvs = false break end push!(v, n.value[time-1]) end if hasvs b.port.value[time] = b.logic(v...) elseif haskey(b.port.value, time - 1) b.port.value[time] = b.port.value[time-1] end end for w in wires if haskey(w.port.value, time) w.terminal.value[time] = w.port.value[time] end end if haskey(cartesian2radius.port.value, time) println(cartesian2radius.port.value[time]) end end ``` Running it results in: ``` # ... 1.0 12.529964086141668 1.0 12.041594578792296 1.0 13.0 0.0 ``` Which has the described behavior of interleaving the outputs. Another stab which resolves this issue: ```julia mutable struct Node{Space} space::Type value::Vector{Space} function Node(t::Type) new{t}(t, Vector()) end end mutable struct Block terminals::Vector{Node} port::Node logic::Function name::String end wire(port, terminal) = terminal.value = port.value floor2zero(x) = x < 0 ? ceil(x) : floor(x) cartesian2radius = Block([Node(Int), Node(Int)], Node(Real), (x, y) -> sqrt(x*x + y*y), "c2r") cartesian2theta = Block([Node(Int), Node(Int)], Node(Real), (x, y) -> atan(y, x), "c2t") addDelta = Block([Node(Real)], Node(Real), (theta) -> theta + rand()*2*pi, "Δ") polar2x = Block([Node(Real), Node(Real)], Node(Int), (r, th) -> floor2zero(r * cos(th)), "p2x") polar2y = Block([Node(Real), Node(Real)], Node(Int), (r, th) -> floor2zero(r * sin(th)), "p2y") blocks = [cartesian2radius, cartesian2theta, addDelta, polar2x, polar2y] wire(cartesian2radius.port, polar2x.terminals[1]) wire(cartesian2radius.port, polar2y.terminals[1]) wire(cartesian2theta.port, addDelta.terminals[1]) wire(addDelta.port, polar2x.terminals[2]) wire(addDelta.port, polar2y.terminals[2]) wire(polar2x.port, cartesian2radius.terminals[1]) wire(polar2y.port, cartesian2radius.terminals[2]) wire(polar2x.port, cartesian2theta.terminals[1]) wire(polar2y.port, cartesian2theta.terminals[2]) push!(cartesian2radius.port.value, 10) push!(cartesian2theta.port.value, 0) while cartesian2radius.port.value[length(cartesian2radius.port.value)] > 0 for block in blocks v = [] g = true for node in block.terminals if length(block.port.value) > length(node.value) g = false break end push!(v, node.value[max(length(block.port.value), 1)]) end if g y = block.logic(v...) push!(block.port.value, y) end end end ```