dds/example/example.org

39 KiB

Examples of usage of dds

Introduction

This document shows some examples of usage of the modules in dds with Org-mode. It relies on emacs-ob-racket.

The following section describes how Org-mode can interact with Racket, and how this interaction can be used for a fluid workflow with dds. In particular, the code block munch-sexp is defined in this section.

The subsequent sections show off some the functionalities of the submodules of dds.

Org-mode, Racket, and dds <<intro>>

Installing dds locally

To install dds locally, you can simply run the following command in dds.

raco pkg install

After this installation, you can import dds modules by simply doing the following:

#lang racket
(file dds/networks)
(file dds/utils)

Importing a module from file   old

To require the modules from the files of dds, you can use the following code (I only reset the prelude here because I set at the top of this file):

#lang racket
(require (file "~/Candies/prj/racket/dds/networks.rkt"))
(require (file "~/Candies/prj/racket/dds/utils.rkt"))

Note that this code will not work with :results value. I think it is because in this case the code is not really evaluated at top level.

These initialisation lines can be put into the prologue of every code block in a subtree by setting :prologue via :header-args:racket: in the properties drawer. Check out the properties drawer of this section for an example.

Alternatively, this property can be set via a #+PROPERTY line at the top the file. For example, this file has such a line. Whenever this property line changes, refresh the setup of the file by hitting C-c C-c on the property line. This will update the prologue for all racket code blocks.

Finally, you can also set :prologue (and other properties with long values) in the following way:

(make-state '((a . 1)))

'#hash((a . 1))

Output formats for results of evaluation of code blocks

This section of the Org manual describes various different formats for presenting the results of code blocks. I find the following three particularly useful as of [2020-02-22 Sat]: output, list, and table.

The output result format is the simplest and the most natural one. It works as if the code block were inserted into a module which would then be evaluated.

(println "This is the first line of output.")
(println (+ 1 2))
(println "This the third line of output.")

"This is the first line of output." 3 "This the third line of output."

The list result format typesets the result of the last line in the code block as a list:

'(1 "hello" (and x y))
  • 1
  • "hello"
  • (and x y)

Note how nested lists are not recursively shown as nested Org-mode lists.

For some reason, the list output format does not work with the result drawer:

'(1 "hello" (and x y))
  • (1 "\"hello\"" (and x y))

Finally, the table result format typesets the output as a table:

'((a . #t) (b . #f))
a #t
b #f

This is clearly very useful for printing states (and hash tables, more generally):

(make-state '((a . 1) (b . #f) (c . "hello")))
a 1
b #f
c "hello"

A note about printing update function forms

Automatic table typesetting may go in the way of readability for hash tables whose values are lists, as the following example shows:

#hash((a . (and a b)) (b . (not b)))
a and a b
b not b

To tackle this issue, dds/utils provides stringify-variable-mapping (with the shortcut sgfy) which converts all the values of a given variable mapping to strings:

(stringify-variable-mapping #hash((a . (and a b)) (b . (not b))))
a "(and a b)"
b "(not b)"

Passing values between code blocks

The :var header argument allows using the output of a code block as an input of another one. For example:

'(4 2)

'(4 2)

Here's how you use its output:

input

"'(4 2)\n"

The parentheses when calling block-1 in the header of the second code block are optional.

There are a two main problems with what we see in the second code block: there is a trailing newline and a leading quote. The trailing newline is not hard to drop, but the leading quote is trickier:

(unorg input)

''(4 2)

I had much trouble understanding how to get rid of the second quote, I even thought it was impossible. To understand the solution, replace ' with quote: ''(4 2) becomes (quote (quote (4 2))). Keeping in mind that (quote (4 2)) is the same thing as '(4 2), this expression effectively defines a list whose first element is quote and whose second element is (4 2). Therefore, to get '(4 2) out of ''(4 2), I need to simply get the second element out of the list with double quote:

(cadr (unorg input))

'(4 2)

There's a simpler way to avoid having to deal with the double quote altogether: use value instead of output in :results.

'(4 2)
(unorg input)

'(4 2)

The only way to pass values between Org-babel code blocks is by writing them to strings. I have looked around for passing the values natively, but it doesn't seem possible, and it actually makes sense: code blocks may be written in different languages, and the most natural way to pass values between them is by serialising to strings.

A good option for passing around native values is by using Noweb references. It does require adapting both code blocks however.

Reading Org-mode tables<<tabread>>

Org-mode allows supplying tables as arguments for code blocks.

a "(and a b)"
b (or b (not a))
tab

((a (and a b)) (b (or b (not a))))

Unfortunately, the same trick does not work with Racket directly, because Racket interprets the first elements in the parentheses as function applications:

tab

application: not a procedure; expected a procedure that can be applied to arguments given: "a" arguments…: "(and a b)" context…: "/tmp/babel-qkvrRR/org-babel-c4wuju.rkt": [running body] temp37_0 for-loop run-module-instance!125 perform-require!78

Fortunately, we can easily remedy this problem by creating a named parameterised Elisp source block which will explicitly convert the table to a string:

(prin1 sexp)

(("a" "(and a b)") ("b" "(or b (not a))"))

We can now correctly receive this table in a Racket source code block by threading it through munch-sexp:

(println tab)

"((\"a\" \"(and a b)\") (\"b\" \"(or b (not a))\"))"

dds/utils has several functions for parsing such strings, and notably read-org-variable-mapping, with the shortcut unorgv:

(unorgv tab)

'#hash((a . (and a b)) (b . (or b (not a))))

Of course, we can use munch-sexp to prepare any other table than test-table for use with Racket:

a (not a)
b (and a c)
c (and a (not b))
(unorgv tab)

'#hash((a . (not a)) (b . (and a c)) (c . (and a (not b))))

Inline graph visualisation with Graphviz

Some functions in dds build graphs:

(build-syntactic-interaction-graph (unorgv bf))

#<unweighted-graph>

The graph library allows building a Graphviz description of the constructed graph. (Note that you have to install the graph library by running raco pkg install graph and require it. The long property line at the top of this file defining the prologue for racket source code blocks takes care of requiring graph.)

(display (graphviz (build-syntactic-interaction-graph (unorgv bf))))

digraph G { node0 [label="c"]; node1 [label="b"]; node2 [label="a"]; subgraph U { edge [dir=none]; node0 -> node1; node2 -> node2; } subgraph D { node2 -> node0; node2 -> node1; } }

You can have an inline drawing of this graph by calling the previous code block (igraph) via a noweb reference in Graphviz/DOT source block:

<<igraph()>>

/scolobb/dds/media/branch/master/example/dots/exampleBQNp7Z.svg

Note that the graph library draws self-loops as undirected edges. It also draws double-sided edges as undirected edges (e.g., in the preceding graph, b depends on c and c depends on b).

dds/networks and dds/functions

The dds/networks is a module for working with different network models. A network is a set of variables which are updated according to their corresponding update functions. The variables to be updated at each step are given by the mode. This model can generalise Boolean networks, TBANs, multivalued networks, etc.

dds/functions is a module for working with the functions underlying the network models. Similarly to dds/networks, it provides primitives for tabulating functions, reconstructing functions from tables, generating random functions, etc.

Boolean networks

Consider the following Boolean network:

a b
b (and (not a) c)
c (not c)

Note that if you define the formula of a as 0, it will set a to 1, because 0 is not #f. For example, (if 0 1 2) evaluates to 1, and not to 2.

Here's the unsigned syntactic interaction graph of this network:

((compose
dotit
build-syntactic-interaction-graph
make-boolean-network-form
unorgv)
simple-bn)
<<simple-bn-syig()>>

/scolobb/dds/media/branch/master/example/dots/examplejTo8XT.svg

Note that, while this definition is an easy one to check structurally, this is not how interaction graphs are typically defined. An interaction graph is usually defined based on the dynamics of the network: an arrow from a variable x to a variable y means that varying x and only x may have an influence on the value of y. It is easy to imagine a situation in which the syntactic interaction graph does not in fact agree with this criterion, the simplest example being the network y = x ∧ ¬ x.

Here is the unsigned interaction graph of the same network, this time constructed according to the canonical definition:

((compose
dotit
build-interaction-graph/form
make-boolean-network-form
unorgv)
simple-bn)
<<simple-bn-ig()>>

/scolobb/dds/media/branch/master/example/dots/example1FH1rZ.svg

In this particular case, the syntactic interaction graph is the same as the interaction graph constructed according to the conventional definition. This however may not necessarily be the case for all networks.

The function build-interaction-graph/form builds the interaction graph from the syntactic definition of the network. For an already built network, you can use build-interaction-graph.

Here's the signed interaction graph of this network:

((compose
dotit
build-signed-interaction-graph/form
make-boolean-network-form
unorgv)
simple-bn)
<<simple-bn-sig()>>

/scolobb/dds/media/branch/master/example/dots/exampledpQygl.svg

Here is the full state graph of this network under the asynchronous dynamics:

((compose
dotit
pretty-print-state-graph
build-full-state-graph
make-asyn-dynamics
forms->boolean-network
unorgv)
simple-bn)
<<simple-bn-sg()>>

/scolobb/dds/media/branch/master/example/dots/examplem7LpTs.svg

Alternatively, you may prefer a slighty more compact representation of Boolean values as 0 and 1:

((compose
dotit
pretty-print-boolean-state-graph
build-full-state-graph
make-asyn-dynamics
forms->boolean-network
unorgv)
simple-bn)
<<simple-bn-sg-bool()>>

/scolobb/dds/media/branch/master/example/dots/examplex1Irnk.svg

Consider the following state (appearing in the upper left corner of the state graph):

a 0
c 1
b 1

These are the states which can be reached from it in at most 2 steps:

(let* ([bn (forms->boolean-network (unorgv simple-bn))]
    [bn-asyn (make-asyn-dynamics bn)]
    [s0 (booleanize-state (unorgv some-state))])
(dotit (pretty-print-boolean-state-graph (dds-build-n-step-state-graph bn-asyn (set s0) 2))))
<<simple-bn-some-state()>>

/scolobb/dds/media/branch/master/example/dots/examplecHA6gL.svg

Here is the complete state graph with edges annotated with the modality leading to the update.

((compose
dotit
pretty-print-boolean-state-graph
build-full-state-graph-annotated
make-asyn-dynamics
forms->boolean-network
unorgv)
simple-bn)
<<simple-bn-sg-bool-ann()>>

/scolobb/dds/media/branch/master/example/dots/examplei4we6j.svg

For some networks, a single transition between two states may be due to different modalities. Consider the following network:

a (not b)
b b
((compose
dotit
pretty-print-boolean-state-graph
build-full-state-graph-annotated
make-asyn-dynamics
forms->boolean-network
unorgv)
input-bn)
<<bn2-sgr()>>

/scolobb/dds/media/branch/master/example/dots/examplehsuRqc.svg

Tabulating functions and networks

Here's how you can tabulate a function. The domain of x is {1, 2}, and the domain of y is {0, 2, 4}. The first column in the output corresponds to x, the second to y, and the third corresponds to the value of the function.

(tabulate (λ (x y) (+ x y)) '((1 2) (0 2 4)))
1 0 1
1 2 3
1 4 5
2 0 2
2 2 4
2 4 6

Here's how you tabulate a Boolean function:

(tabulate/boolean (λ (x y) (and x y)))
#f #f #f
#f #t #f
#t #f #f
#t #t #t

You can tabulate multiple functions taking the same arguments over the same domains together.

(tabulate*/boolean `(,(λ (x y) (and x y)) ,(λ (x y) (or x y))))
#f #f #f #f
#f #t #f #t
#t #f #f #t
#t #t #t #t

Here's how to tabulate the network simple-bn, defined at the top of this section:

(tabulate-network (forms->boolean-network (unorgv in-bn)))
a b c f-a f-b f-c
#f #f #f #f #f #t
#f #f #t #f #t #f
#f #t #f #t #f #t
#f #t #t #t #t #f
#t #f #f #f #f #t
#t #f #t #f #f #f
#t #t #f #t #f #t
#t #t #t #t #f #f

Random functions and networks

To avoid having different results every time a code block in this section is run, every code block seeds the random number generator to 0.

dds/networks can generate random functions, given a domain for each of its arguments and for the function itself. Consider the following domains:

a (#f #t)
b (1 2)
c (cold hot)

Here's a random function taking values in the codomain (4 5 6):

(random-seed 0)
(define rnd-func (random-function/state (unorgv simple-domains) '(4 5 6)))
(tabulate-state rnd-func (unorgv simple-domains))
a b c f
#f 1 cold 4
#f 1 hot 5
#f 2 cold 4
#f 2 hot 4
#t 1 cold 5
#t 1 hot 6
#t 2 cold 4
#t 2 hot 5

We can build an entire random network over these domains:

(random-seed 0)
(define n (random-network (unorgv simple-domains)))
(tabulate-network n)
a b c f-a f-b f-c
#f 1 cold #f 2 hot
#f 1 hot #f 2 cold
#f 2 cold #t 1 cold
#f 2 hot #t 2 hot
#t 1 cold #f 2 cold
#t 1 hot #t 1 cold
#t 2 cold #f 2 hot
#t 2 hot #t 1 cold

Let's snapshot this random network and give it a name.

a b c f-a f-b f-c
#f 1 cold #f 2 hot
#f 1 hot #f 2 cold
#f 2 cold #t 1 cold
#f 2 hot #t 2 hot
#t 1 cold #f 2 cold
#t 1 hot #t 1 cold
#t 2 cold #f 2 hot
#t 2 hot #t 1 cold

Here's how we can read back this table as a network:

(string->any rnd-network)

'(("a" "b" "c" "f-a" "f-b" "f-c") ("#f" 1 "cold" "#f" 2 "hot") ("#f" 1 "hot" "#f" 2 "cold") ("#f" 2 "cold" "#t" 1 "cold") ("#f" 2 "hot" "#t" 2 "hot") ("#t" 1 "cold" "#f" 2 "cold") ("#t" 1 "hot" "#t" 1 "cold") ("#t" 2 "cold" "#f" 2 "hot") ("#t" 2 "hot" "#t" 1 "cold"))

You can use table->network to convert a table such as rnd-network to a network.

(table->network (unorg rnd-network))

(network '#hash((a . #<procedure:…ds/functions.rkt:145:4>) (b . #<procedure:…ds/functions.rkt:145:4>) (c . #<procedure:…ds/functions.rkt:145:4>)) '#hash((a . (#f #t)) (b . (1 2)) (c . (cold hot))))

Here's the state graph of rnd-network.

((compose
dotit
pretty-print-state-graph
build-full-state-graph-annotated
make-asyn-dynamics
table->network
unorg)
rnd-network)
<<rnd-network-sg()>>

/scolobb/dds/media/branch/master/example/dots/exampleHc023j.svg

Here's the signed interaction graph of rnd-network.

((compose
dotit
build-signed-interaction-graph
table->network
unorg)
rnd-network)
<<rnd-network-ig()>>

/scolobb/dds/media/branch/master/example/dots/examplePIN5ac.svg

Standalone threshold Boolean functions (TBF)

Note: Before using the objects described in this section, consider whether the objects from the section on TBN aren't a better fit.

dds includes some useful definitions for working with threshold Boolean functions (TBF). A TBF is defined as a vector of weights and a threshold. For example, the following defines a TBF implementing the logical AND.

(tbf #(1 1) 1)

(tbf '#(1 1) 1)

This TBF only returns 1 when both inputs are activated, which brings their weighted some above 1: 1 ⋅ 1 + 1 ⋅ 1 = 2 > 1.

(apply-tbf (tbf #(1 1) 1) #(1 1))

1

Let's actually check out the truth table of this TBF.

(tbf-tabulate (tbf #(1 1) 1))
0 0 0
0 1 0
1 0 0
1 1 1

This truth table corresponds indeed to the logical AND.

dds allows reading TBFs from Org tables. In this case, the last column in each row is treated as the threshold, while the first values are taken to be the weights. Consider, for example, the following table:

1 1 0
1 1 1

You can read the two TBFs defined in this table in the following way:

(read-org-tbfs simple-tbf)

(list (tbf '#(1 1) 0) (tbf '#(1 1) 1))

The first TBF implements the logical OR of its inputs, while the second TBF implements the logical AND. Let's check it by tabulating both functions:

(tbf-tabulate* (read-org-tbfs simple-tbf))
0 0 0 0
0 1 1 0
1 0 1 0
1 1 1 1

The first two columns of this table give the values of the two inputs. The third column gives the values of the first TBF, and the fourth column gives the values of the second TBF.

dds also provides a couple shortcuts to deal with SBF—sign Boolean functions. SBF are TBF with threshold equal to 0:

(sbf? (tbf #(1 1) 1))
(sbf? (tbf #(1 1) 0))

#f #t

You can read SBFs from Org tables, like TBFs:

1 -1
2 2
(read-org-sbfs some-sbfs)

(list (tbf '#(1 -1) 0) (tbf '#(2 2) 0))

Threshold Boolean networks (TBN) <<tbn>>

dds includes a number of useful definitions for working with threshold Boolean networks: networks of threshold Boolean functions. Since, standalone TBF do give names to their inputs, dds also defines TBF operating on states:

(make-tbf/state '((a . 1) (b . 1)) 1)

(tbf/state '#hash((a . 1) (b . 1)) 1)

As the example standalone TBF, this TBF only returns 1 when both inputs are activated:

(apply-tbf/state (make-tbf/state '((a . 1) (b . 1)) 1)
              (make-state '((a . 1) (b . 1))))

1

Here's how you can read this TBF from an Org-mode table:

a b θ
1 1 1
(read-org-tbfs/state simple-tbf/state)

(list (tbf/state '#hash((a . 1) (b . 1)) 1))

Note that the header of the rightmost column is discarded.

read-org-tbfs/state can also read multiple TBFs at once:

a b θ
1 1 1
-2 1 0
(read-org-tbfs/state simple-tbfs/state)

(list (tbf/state '#hash((a . 1) (b . 1)) 1) (tbf/state '#hash((a . -2) (b . 1)) 0))

You can print a list of TBFs in the following way:

(print-org-tbfs/state (read-org-tbfs/state simple-tbfs/state))
a b θ
1 1 1
-2 1 0

All TBFs given to print-org-tbfs/state mush have exactly the same inputs. This function does not check this property.

Here's how you can tabulate both of these TBFs in the same table (e.g., to compare their truth tables):

(tbf/state-tabulate* (read-org-tbfs/state simple-tbfs/state))
a b f1 f2
0 0 0 0
0 1 0 1
1 0 0 0
1 1 1 0

dds also includes functions for dealing with SBF operating on states. In particular, to create an SBF, you can do:

(make-sbf/state '((a . -1) (b . 1)))

(tbf/state '#hash((a . -1) (b . 1)) 0)

Most of the functions operating on TBF can be directly applied to SBF and therefore have no specialization for this particular case. However, there are variants of reading and printing functions for SBF.

Consider the following table giving the weights of two SBF:

a b
1 1
-2 1

You can read these SBFs in the following way:

(read-org-sbfs/state simple-sbfs/state)

(list (tbf/state '#hash((a . 1) (b . 1)) 0) (tbf/state '#hash((a . -2) (b . 1)) 0))

You can print them back to an Org-mode table as follows:

(print-org-sbfs/state (read-org-sbfs/state simple-sbfs/state))
a b
1 1
-2 1

Finally, dds defines utilities for working with TBN (networks of TBF). For example, here is how you can define and read a TBN from a table:

- x y θ
y -1 0 -1
x 0 -1 -1
(read-org-tbn tbfs-nots)

(hash 'x (tbf/state '#hash((x . 0) (y . -1)) -1) 'y (tbf/state '#hash((x . -1) (y . 0)) -1))

To take a look at the behaviour of this TBN, you can convert it to a network using tbn->network and build its state graph:

((compose
dotit
build-tbn-state-graph
read-org-tbn)
tbfs-nots)
<<tbfs-nots-sg()>>

/scolobb/dds/media/branch/master/example/dots/examplew206DH.svg

For convenience, there is a similar function read-org-sbn which allows reading an SBN.

- A B C
A -1 1 2
B 2 -2 -2
C -1 2 -1
(dotit (build-tbn-state-graph (read-org-sbn sbn-figure2)))
<<sbn-figure2-sg()>>

/scolobb/dds/media/branch/master/example/dots/sbn-figure2-sg.svg

You can print TBNs using print-org-tbn in the following way:

(print-org-tbn (read-org-sbn sbn-figure2))
- A B C θ
A -1 1 2 0
B 2 -2 -2 0
C -1 2 -1 0

For convenience, dds also includes print-org-sbn, which allows you to chop off the threshold column, which only contains zeros anyway for the case of SBN:

(print-org-sbn (read-org-sbn sbn-figure2))
- A B C
A -1 1 2
B 2 -2 -2
C -1 2 -1

dds also defines functions to draw the interaction graphs of TBNs:

(dotit (tbn-interaction-graph (read-org-tbn tbfs-nots)))
<<tbfs-nots-ig()>>

/scolobb/dds/media/branch/master/example/dots/exampletCklZa.svg

tbn-interaction-graph can optionally omit edges with zero weight:

(dotit (tbn-interaction-graph (read-org-tbn tbfs-nots) #:zero-edges #f))
<<tbfs-nots-ig-no0()>>

/scolobb/dds/media/branch/master/example/dots/exampleudm6jn.svg

You can print the note labels in a slightly prettier way using pretty-print-tbn-interaction-graph:

((compose
dotit
pretty-print-tbn-interaction-graph
tbn-interaction-graph
read-org-tbn)
tbfs-nots)
<<tbfs-nots-ig-pp()>>

/scolobb/dds/media/branch/master/example/dots/exampleQLHMVK.svg

As usual, dds includes a specific function for constructing the interaction graph of SBN. This function does not include the thresholds of the SBF in the interaction graph.

(dotit (sbn-interaction-graph (read-org-sbn sbn-figure2)))
<<sbn-figure2-ig()>>

/scolobb/dds/media/branch/master/example/dots/exampleaSeyzw.svg

Reaction systems

Consider the following reaction system:

a x t y z
b x q z

Here is how we read this reaction into Racket code:

(read-org-rs input-rs)

(hash 'a (reaction (set 'x 't) (set 'y) (set 'z)) 'b (reaction (set 'x) (set 'q) (set 'z)))

Here is how we can put it back into an Org-mode table:

(rs->ht-str-triples (read-org-rs input-rs))
a "t x" "y" "z"
b "x" "q" "z"

Here is how we can apply this reaction system to a state:

(let ([rs (read-org-rs input-rs)])
(apply-rs rs (set 'x 't)))

(set 'z)

Let's see which reactions got applied:

(let ([rs (read-org-rs input-rs)])
(list-enabled rs (set 'x 't)))
  • a
  • b

You can also give a name to a list and read it with munch-sexp:

  • x y
  • z
  • t
(read-context-sequence input-ctx)

(list (set 'x 'y) (set 'z) (set) (set 't))

Let's see what the evolution of rs1 looks like with the context sequence ctx1.

(dotit (pretty-print-state-graph (build-interactive-process-graph (read-org-rs input-rs) (read-context-sequence input-ctx))))

digraph G { node0 [label="C:{z}{}{t}\nD:{z}"]; node1 [label="C:{}{t}\nD:{}"]; node2 [label="C:{x y}{z}{}{t}\nD:{}"]; node3 [label="C:{t}\nD:{}"]; node4 [label="C:\nD:{}"]; subgraph U { edge [dir=none]; node4 -> node4 [label="{}"]; } subgraph D { node0 -> node1 [label="{}"]; node1 -> node3 [label="{}"]; node2 -> node0 [label="{b}"]; node3 -> node4 [label="{}"]; } }

<<rs1-sgr()>>

/scolobb/dds/media/branch/master/example/dots/examplevvXFaI.svg

Note that this graph includes the full context sequence in the name of each state, which is how the states are represented in the dynamics of reaction systems. You can use build-reduced-state-graph to construct a similar graph, but without the context sequences.

(dotit (pretty-print-reduced-state-graph
      (build-reduced-state-graph (read-org-rs input-rs)
                                 (read-context-sequence input-ctx))))
<<rs1-sgr-no-ctx()>>

/scolobb/dds/media/branch/master/example/dots/exampleLGKcXp.svg

The graphical presentation for interactive processes is arguably less readable than just listing the contexts and the results explicitly. Here is how you can do it.

(build-interactive-process (read-org-rs input-rs) (read-context-sequence input-ctx))
(y x) nil
(z) (z)
nil nil
(t) nil
nil nil

The first column of this table shows the current context. The second column shows the result of application of the reactions to the previous state. The interactive process contains one more step with respect to the context sequence. This is to show the effect of the last context.

Note that empty sets are printed as nil.