Was this page helpful?

Causality Loops

Cycles

The interconnection pattern for a collection of reactors can form a cycle, but some care is required. Consider the following example:

target C;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}
target Cpp;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}

target Python;
reactor A {
    input x;
    output y;
    reaction(x) -> y {=
        # ... something here ...
    =}
}
reactor B {
    input x;
    output y;
    reaction(x) {=
        # ... something here ...
    =}
    reaction(startup) -> y {=
        # ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}
target TypeScript
reactor A {
    input x:number
    output y:number
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:number
    output y:number
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A()
    b = new B()
    a.y -> b.x
    b.y -> a.x
}

target Rust;
reactor A {
    input x:u32;
    output y:u32;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:u32;
    output y:u32;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}

This program yields the following diagram:

Lingua Franca diagram

The diagram highlights a causality loop in the program. At each tag, in reactor B, the first reaction has to execute before the second if it is enabled, a precedence indicated with the red dashed arrow. But the first can’t execute until the reaction of A has executed, and that reaction cannot execute until the second reaction B has executed. There is no way to satisfy these requirements, so the tools refuse to generated code.

Cycles with Delays

One way to break the causality loop and get an executable program is to introduce a logical delay into the loop, as shown below:

target C;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x after 0;
    b.y -> a.x;
}
target Cpp;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x after 0;
    b.y -> a.x;
}
target Python;
reactor A {
    input x;
    output y;
    reaction(x) -> y {=
        # ... something here ...
    =}
}
reactor B {
    input x;
    output y;
    reaction(x) {=
        # ... something here ...
    =}
    reaction(startup) -> y {=
        # ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x after 0;
    b.y -> a.x;
}
target TypeScript
reactor A {
    input x:number
    output y:number
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:number
    output y:number
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A()
    b = new B()
    a.y -> b.x after 0
    b.y -> a.x
}
target Rust;
reactor A {
    input x:u32;
    output y:u32;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:u32;
    output y:u32;
    reaction(x) {=
        // ... something here ...
    =}
    reaction(startup) -> y {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x after 0;
    b.y -> a.x;
}

Lingua Franca diagram

Here, we have used a delay of 0, which results in a delay of one microstep. We could equally well have specified a positive time value.

Reaction Order

Frequently, a program will have such cycles, but you don’t want a logical delay in the loop. To get a cycle without logical delays, the reactions need to be reordered, as shown below:

target C;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(startup) -> y {=
        // ... something here ...
    =}
    reaction(x) {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}
target Cpp;
reactor A {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:int;
    output y:int;
    reaction(startup) -> y {=
        // ... something here ...
    =}
    reaction(x) {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}
target Python;
reactor A {
    input x;
    output y;
    reaction(x) -> y {=
        # ... something here ...
    =}
}
reactor B {
    input x;
    output y;
    reaction(startup) -> y {=
        # ... something here ...
    =}
    reaction(x) {=
        # ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}
target TypeScript
reactor A {
    input x:number
    output y:number
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:number
    output y:number
    reaction(startup) -> y {=
        // ... something here ...
    =}
    reaction(x) {=
        // ... something here ...
    =}
}
main reactor {
    a = new A()
    b = new B()
    a.y -> b.x
    b.y -> a.x
}
target Rust;
reactor A {
    input x:u32;
    output y:u32;
    reaction(x) -> y {=
        // ... something here ...
    =}
}
reactor B {
    input x:u32;
    output y:u32;
    reaction(startup) -> y {=
        // ... something here ...
    =}
    reaction(x) {=
        // ... something here ...
    =}
}
main reactor {
    a = new A();
    b = new B();
    a.y -> b.x;
    b.y -> a.x;
}

Lingua Franca diagram

There is no longer any causality loop.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (4)
PDPeter Donovan  (1)
SBSoroush Bateni  (1)

Last updated: Mar 30, 2023