Skip to content

Repository files navigation

INTRODUCTION

Dile simulator lets you sketch and visually test systems-thinking ideas (feedback loops, competing influences, threshold effects) before building something more rigorous in a proper modeling tool. Dile simulator started as a tool that was meant to take the activities of the brain and how they interact and simplify it into a simple node system (Node A pushes Node B UP or Down and vice versa).

It is basically a lightweight, abstracted modeling language for simplifying interacting systems down to push/pull/decay/compete logic. The system relies on Ticks which is like the simulation clock of events. Each Tick decay moves a node's dile by exactly 1 toward its baseline, so nodes can progressivly decay over each tick back to it's baseline value (node's default value).

Dile also has a simple conflict resolution system that resolves when two nodes are actively pushing towards another node at the same time. The system automatically picks the node with the strongest baseline value and rewards each node accordingly as a bonus (comes from the idea if a brain is hyperfixated on something and continues to reward it).

Dile comes with an internal call system and external call system. These systems were derived from the idea of how the brain makes internal call from one part to another while the external call is supposed to represent an external trigger like:

  • A scary event that trigers cortisol in the brain
  • An external push towards a node that has a pull relationship with another

Dile can be used for:-

  • Fast prototyping before building a real model - Can be used to prototype a model that requires the push and pull behaviour over a specific amount of ticks.
  • Game design utility AI - Can be use to showcase, in a simple visual way, how mechanics that interact with eachother evolves or behaves overtime when certain actions are taken at specifc point in time. Can be buffs, AI behaviour or even NPC's.
  • Conceptual/computational modeling of neural and physiological feedback systems - Can be used to sketch an hypothesis on a regulatory loop, teaching the specifics or for just rapid testing.

LANGUAGE SYNTAX

Nodes are written in .rd files, and to write a file, the language syntax is written in sections:

  1. Defination section: This section is where you define a node's id, name and nessesary variable.
@<id> node <Node_name>:
    state:dile = <baseline_value> {
       var <variable_name> = <value>
       var <variable_name> = <value>
    }

@id is written in int eg @1, @2 and so on. one file only needs one id as that file itself represents the node itself and Node_name can be any name of your choice.

You then set the node's state as dile represents it baseline value (int) and inside create as much variables as the node needs.

  1. Behavioural section: This section contains all the functions/behavior of the node.
@bah <function_name>(amount):
    dile = dile + amount
    ...
  1. Closing section: Every node file must have a closing sectio in order to work.
@bah_decay() -> tik

this makes the node have a natural decay every tick towards the baseline.

EXAMPLE CODE:

@1 node Cortisol:
  state:dile = 10 {
    var sensitivity = 2
    var recovery_rate = 1
    var max_threshold = 90
    var baseline_shift = 0
  }

  @bah trigger_stress_response(amount):
    dile = dile + amount

  @bah_decay() -> tik

WRITTING IF'S:

Example

IF { dile < 100 }:
        dile = dile + 1
ELIF { dile == 100 }:
        dile = dile + 1
ELSE:
    dile = dile * 1

If and elif have their conditions written inside a { } and they support conditions like <, >, ==, !=, <= and >=.

CALLING FUNCTIONS

You can call functions inside another function or on top level.

Example

 @2.push_self(30) -> <process> {tick:<when>}

The example above shows a node with id @2 with a function called push_self. When calling such function you have to set the process, there are 4 processes namely:

  • once - This means this function is called once and forgotten.
  • push - This means this function is called once every tick continously till the simulation ends.
  • active - This means the function is called once but holds which in turns prevent the target node from decay per tick (Useful when simulating how one node has a steady grip on another).
  • off - This turns off a function process (Mainly for push and active).

After setting the process you then have to set the tick time. There are two main ways of doing it:

{tick:<value>}

or

{tick:next}

You can set it by giving it a value for example 3, this means this function should be triggered at tick 3 while next triggers the function at the next coming tick.

Example

 @2.push_self(30) -> once {tick:next}

This means trigger this function once at the next comming tick

Also when calling a function inside the same node file or if you want to call a node's function inside another node, you just have to use the id.

Node id's can be cross-refrenced, meaning you can call any node function using the node's id just like in the example above.

Note

Node's variables in State are also cross-refrenceable by just using the parent node (that owns the var) id.

Calling function inside a function and top-level example

@2 node Hypothalamus:
   state:dile = 20 {
      var push = 10
   }

       
   @bah push_self(amount):
       dile = dile + amount

       IF { dile < 100 }:
           @3.push_self(push) -> once {tick:next}



   @3.push_self(push) -> once {tick:10}

   @bah_decay() -> tik

RUNNING THE SIMULATION

First in your project dir you need to create a folder called nodes, this folder will host all your node .rd files.

There are two main ways to run a simulation.

  1. Using a .sm file
  2. Using the terminal injection

.sm is the most recommended path beacuse this allows you to set up multiple external injections at different ticks and also allows you to set the amount of ticks the simulation should run on.

run 50 ticks
graph on

This says run for 50 ticks and export the result into a vusual graph.

run 50 ticks

inject @1.<function_name>() -> once {tick:6}

graph on

This runs for 50 ticks and also performs an external injection into node @1 specific function you might be targeting at tick 6.

once the .sm files has been written, remember to make sure it in root level and not inside you nodes folder. Then in your terminal you run:

dile run --sim <name_of_file>.sm 

You will get some logs printed on your terminal and a visual graph if you set it on.

USING TERMINAL INJECTION This process involving running everything directly from the terminal.

dile run [folder]

If you type dile run, dile automatically looks for a folder names nodes, once found it runs the simulation for 15 ticks by default. if your folder is named something else specify it when running.

If you want to set the amount of ticks:

dile run --ticks 30

This works if your folder is named nodes if not specify the folder name.

if you want to inject you do this with:

dile run --ticks 30 --inject "<tick_value>:@1.push_self(60):once" 

tick_value lets you specify what tick the injection should take place eg 7.

To export a graph you write:

dile run --ticks 30 --inject "1:@1.push_self(60):once" --graph

Overall the .sm file route is the better option.

PRACTICAL EXAMPLE

  1. From Walker, Terry & Lightman's work on HPA axis (hypothalamus-pituitary-adrenal) the loop that regulate cortisol, driven by CRH from the hypothalamus, ACTH from the pituitary, and cortisol itself from the adrenal glad, which then loops back to supress the hypothalamus. Does that closed loop settle back to a steady level on its own or does the time it takes for the signal to travel one full lap around the loop cause it to keep going instead, even with only a single starting nudge and nothing else pushing it.

Three .rd files are created, each to represent hypothalamus, pituitaryand adrenal. each with a simple behaviour of pushing the next node once and the next node doing the same back to back in a closed loop.

hypothalamus.rd

@2 node Hypothalamus:
    state:dile = 20 {
       var push = 10
    }

        
    @bah push_self(amount):
        dile = dile + amount

        IF { dile < 100 }:
            @3.push_self(push) -> once {tick:next}



    @3.push_self(push) -> once {tick:10}

    @bah_decay() -> tik

Since hypothalamus is the starting node of my choice, it called pituitary function which will start the closed loop.

pituitary.rd

@3 node Pituitary:
    state:dile = 20 {
        var push = 10
    }


    @bah push_self(amount):
        dile = dile + amount
        IF { dile < 100 }:
            @1.push_self(push) -> once {tick:next}


    @bah_decay() -> tik

adrenal.rd

@1 node Adrenal:
  state:dile = 20 {
    var push = 10
  }
    

  @bah push_self(amount):
    dile = dile + amount
    IF { dile < 100 }:
            @2.push_self(push) -> once {tick:next}

  @bah_decay() -> tik

stress_response.sm

run 50 ticks

graph on

Then run with dile run --sim stress_response.sm. Then the simulation outputs:

graph output

logs
[tick 1]
[tick 2]
[tick 3]
[tick 4]
[tick 5]
[tick 6]
[tick 7]
[tick 8]
[tick 9]
[tick 10]
  @2 -> Pituitary.push_self([10]) [once]
[tick 11]
  @3 -> Adrenal.push_self([10]) [once]
[tick 12]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 13]
  @2 -> Pituitary.push_self([10]) [once]
[tick 14]
  @3 -> Adrenal.push_self([10]) [once]
[tick 15]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 16]
  @2 -> Pituitary.push_self([10]) [once]
[tick 17]
  @3 -> Adrenal.push_self([10]) [once]
[tick 18]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 19]
  @2 -> Pituitary.push_self([10]) [once]
[tick 20]
  @3 -> Adrenal.push_self([10]) [once]
[tick 21]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 22]
  @2 -> Pituitary.push_self([10]) [once]
[tick 23]
  @3 -> Adrenal.push_self([10]) [once]
[tick 24]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 25]
  @2 -> Pituitary.push_self([10]) [once]
[tick 26]
  @3 -> Adrenal.push_self([10]) [once]
[tick 27]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 28]
  @2 -> Pituitary.push_self([10]) [once]
[tick 29]
  @3 -> Adrenal.push_self([10]) [once]
[tick 30]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 31]
  @2 -> Pituitary.push_self([10]) [once]
[tick 32]
  @3 -> Adrenal.push_self([10]) [once]
[tick 33]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 34]
  @2 -> Pituitary.push_self([10]) [once]
[tick 35]
  @3 -> Adrenal.push_self([10]) [once]
[tick 36]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 37]
  @2 -> Pituitary.push_self([10]) [once]
[tick 38]
  @3 -> Adrenal.push_self([10]) [once]
[tick 39]
  @1 -> Hypothalamus.push_self([10]) [once]
[tick 40]
  @2 -> Pituitary.push_self([10]) [once]
[tick 41]
[tick 42]
[tick 43]
[tick 44]
[tick 45]
[tick 46]
[tick 47]
[tick 48]
[tick 49]
[tick 50]

Results: The closed loop didn't settle down, each node held steady once pushed, then decayed back down unless the loop pushed it again. Since each hop fires at a tick, this create a gradual and steady staircase climb rather than an instant jump. This continues until whichever node reaches its threshold first stopping the push and breaking the chain.

INSTALLATION

To install dile you need to:

  1. Go to the release page of this GitHub repo and download the latest setup.
  2. Run the Installer and follow the instructions.
  3. After installing, open a fresh terminal and type
dile
  1. If you are greeted with a message, dile has been successfully installed.

About

Allows you to model multiple nodes, and how they interact together. What happens when a node influence another over a specific duration and how it reacts

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages