dds/example/example.org

1371 lines
40 KiB
Org Mode
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: Examples of usage of =dds=
#+PROPERTY: header-args:racket :prologue "#lang racket\n(require graph dds/networks dds/utils dds/functions)"
* Introduction
This document shows some examples of usage of the modules in =dds=
with Org-mode. It relies on [[https://github.com/hasu/emacs-ob-racket][emacs-ob-racket]].
The [[intro][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 [[tabread][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 [[/home/scolobb/Candies/prj/racket/dds/][=dds=]].
#+BEGIN_SRC shell
raco pkg install
#+END_SRC
After this installation, you can import =dds= modules by simply
doing the following:
#+BEGIN_SRC racket :results output drawer
#lang racket
(file dds/networks)
(file dds/utils)
#+END_SRC
** Importing a module from file :old:
:PROPERTIES:
:header-args:racket: :prologue "#lang racket\n(require (file \"~/Candies/prj/racket/dds/networks.rkt\"))"
:END:
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):
#+BEGIN_SRC racket :results output :prologue ""
#lang racket
(require (file "~/Candies/prj/racket/dds/networks.rkt"))
(require (file "~/Candies/prj/racket/dds/utils.rkt"))
#+END_SRC
#+RESULTS:
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:
#+HEADER: :prologue "#lang racket\n(require (file \"~/Candies/prj/racket/dds/networks.rkt\"))"
#+BEGIN_SRC racket :results output drawer
(make-state '((a . 1)))
#+END_SRC
#+RESULTS:
:RESULTS:
'#hash((a . 1))
:END:
** Output formats for results of evaluation of code blocks
[[https://orgmode.org/manual/Results-of-Evaluation.html#Results-of-Evaluation][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.
#+BEGIN_SRC racket :results output drawer
(println "This is the first line of output.")
(println (+ 1 2))
(println "This the third line of output.")
#+END_SRC
#+RESULTS:
:RESULTS:
"This is the first line of output."
3
"This the third line of output."
:END:
The =list= result format typesets the result of the last line in the
code block as a list:
#+BEGIN_SRC racket :results list
'(1 "hello" (and x y))
#+END_SRC
#+RESULTS:
- 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:
#+BEGIN_SRC racket :results list drawer
'(1 "hello" (and x y))
#+END_SRC
#+RESULTS:
:RESULTS:
- (1 "\"hello\"" (and x y))
:END:
Finally, the =table= result format typesets the output as a table:
#+BEGIN_SRC racket :results table drawer
'((a . #t) (b . #f))
#+END_SRC
#+RESULTS:
:RESULTS:
| a | #t |
| b | #f |
:END:
This is clearly very useful for printing states (and hash tables,
more generally):
#+BEGIN_SRC racket :results table drawer
(make-state '((a . 1) (b . #f) (c . "hello")))
#+END_SRC
#+RESULTS:
:RESULTS:
| a | 1 |
| b | #f |
| c | "hello" |
:END:
*** 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:
#+BEGIN_SRC racket :results table drawer
#hash((a . (and a b)) (b . (not b)))
#+END_SRC
#+RESULTS:
:RESULTS:
| a | and | a | b |
| b | not | b | |
:END:
To tackle this issue, [[../utils.rkt][=dds/utils=]] provides
=stringify-variable-mapping= (with the shortcut =sgfy=) which
converts all the values of a given variable mapping to strings:
#+BEGIN_SRC racket :results table drawer
(stringify-variable-mapping #hash((a . (and a b)) (b . (not b))))
#+END_SRC
#+RESULTS:
:RESULTS:
| a | "(and a b)" |
| b | "(not b)" |
:END:
** 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:
#+NAME: block-1
#+BEGIN_SRC racket :results output drawer
'(4 2)
#+END_SRC
#+RESULTS: block-1
:RESULTS:
'(4 2)
:END:
Here's how you use its output:
#+NAME: block-2
#+BEGIN_SRC racket :results output drawer :var input=block-1()
input
#+END_SRC
#+RESULTS:
:RESULTS:
"'(4 2)\n"
:END:
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:
#+BEGIN_SRC racket :results output drawer :var input=block-1()
(unorg input)
#+END_SRC
#+RESULTS:
:RESULTS:
''(4 2)
:END:
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:
#+BEGIN_SRC racket :results output drawer :var input=block-1()
(cadr (unorg input))
#+END_SRC
#+RESULTS:
:RESULTS:
'(4 2)
:END:
There's a simpler way to avoid having to deal with the double quote
altogether: use =value= instead of =output= in =:results=.
#+NAME: block-1-value
#+BEGIN_SRC racket :results value drawer
'(4 2)
#+END_SRC
#+BEGIN_SRC racket :results output drawer :var input=block-1-value
(unorg input)
#+END_SRC
#+RESULTS:
:RESULTS:
'(4 2)
:END:
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.
#+NAME: test-table
| a | "(and a b)" |
| b | (or b (not a)) |
#+BEGIN_SRC elisp :var tab=test-table :results output drawer
tab
#+END_SRC
#+RESULTS:
:RESULTS:
:END:
((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:
#+BEGIN_SRC racket :results output drawer :var tab=test-table
tab
#+END_SRC
#+RESULTS:
:RESULTS:
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
:END:
Fortunately, we can easily remedy this problem by creating a named
parameterised Elisp source block which will explicitly convert the
table to a string:
#+NAME: munch-sexp
#+BEGIN_SRC elisp :results output drawer :var sexp=test-table
(prin1 sexp)
#+END_SRC
#+RESULTS: munch-sexp
:RESULTS:
(("a" "(and a b)") ("b" "(or b (not a))"))
:END:
We can now correctly receive this table in a Racket source code
block by threading it through =munch-sexp=:
#+BEGIN_SRC racket :results output drawer :var tab=munch-sexp(sexp=test-table)
(println tab)
#+END_SRC
#+RESULTS:
:RESULTS:
"((\"a\" \"(and a b)\") (\"b\" \"(or b (not a))\"))"
:END:
[[../utils.rkt][=dds/utils=]] has several functions for parsing such strings, and
notably =read-org-variable-mapping=, with the shortcut =unorgv=:
#+BEGIN_SRC racket :results output drawer :var tab=munch-sexp(sexp=test-table)
(unorgv tab)
#+END_SRC
#+RESULTS:
:RESULTS:
'#hash((a . (and a b)) (b . (or b (not a))))
:END:
Of course, we can use =munch-sexp= to prepare any other table than
=test-table= for use with Racket:
#+NAME: another-test-table
| a | (not a) |
| b | (and a c) |
| c | (and a (not b)) |
#+BEGIN_SRC racket :results output drawer :var tab=munch-sexp(sexp=another-test-table)
(unorgv tab)
#+END_SRC
#+RESULTS:
:RESULTS:
'#hash((a . (not a)) (b . (and a c)) (c . (and a (not b))))
:END:
** Inline graph visualisation with Graphviz
Some functions in =dds= build graphs:
#+BEGIN_SRC racket :results output drawer :var bf=munch-sexp(another-test-table)
(build-syntactic-interaction-graph (unorgv bf))
#+END_SRC
#+RESULTS:
:RESULTS:
#<unweighted-graph>
:END:
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=.)
#+NAME: igraph
#+BEGIN_SRC racket :results output drawer :var bf=munch-sexp(another-test-table)
(display (graphviz (build-syntactic-interaction-graph (unorgv bf))))
#+END_SRC
#+RESULTS: igraph
:RESULTS:
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;
}
}
:END:
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:
#+BEGIN_SRC dot :file dots/exampleBQNp7Z.svg :results raw drawer :cmd sfdp :noweb yes
<<igraph()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/exampleBQNp7Z.svg]]
:END:
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 [[../networks.rkt][=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.
[[file:../functions.rkt][=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:
#+NAME: simple-bn
| 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:
#+NAME: simple-bn-syig
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(dotit (build-syntactic-interaction-graph (unorgv simple-bn)))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplejTo8XT.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-syig()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/examplejTo8XT.svg]]
:end:
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 \wedge \neg x.
Here is the unsigned interaction graph of the same network, this
time constructed according to the canonical definition:
#+NAME: simple-bn-ig
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(dotit (build-interaction-graph/form (unorgv simple-bn) (make-boolean-domains '(a b c))))
#+END_SRC
#+BEGIN_SRC dot :file dots/example1FH1rZ.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-ig()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/example1FH1rZ.svg]]
:end:
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:
#+NAME: simple-bn-sig
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(dotit (build-signed-interaction-graph/form (unorgv simple-bn) (make-boolean-domains '(a b c))))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampledpQygl.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-sig()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampledpQygl.svg]]
:end:
Here is the full state graph of this network under the asynchronous
dynamics:
#+NAME: simple-bn-sg
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(let* ([bn (network-form->network (unorgv simple-bn))]
[bn-asyn (make-asyn-dynamics bn)])
(dotit (pretty-print-state-graph (build-full-boolean-state-graph bn-asyn))))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplem7LpTs.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-sg()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/examplem7LpTs.svg]]
:END:
Alternatively, you may prefer a slighty more compact representation
of Boolean values as 0 and 1:
#+NAME: simple-bn-sg-bool
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(let* ([bn (network-form->network (unorgv simple-bn))]
[bn-asyn (make-asyn-dynamics bn)])
(dotit (pretty-print-boolean-state-graph (build-full-boolean-state-graph bn-asyn))))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplex1Irnk.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-sg-bool()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/examplex1Irnk.svg]]
:END:
Consider the following state (appearing in the upper left corner of
the state graph):
#+NAME: some-state
| a | 0 |
| c | 1 |
| b | 1 |
These are the states which can be reached from it in at most 2
steps:
#+NAME: simple-bn-some-state
#+HEADER: :var simple-bn=munch-sexp(simple-bn)
#+HEADER: :var some-state=munch-sexp(some-state)
#+BEGIN_SRC racket :results silent
(let* ([bn (network-form->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))))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplecHA6gL.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-some-state()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/examplecHA6gL.svg]]
:END:
Here is the complete state graph with edges annotated with the
modality leading to the update.
#+NAME: simple-bn-sg-bool-ann
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
(let* ([bn (network-form->network (unorgv simple-bn))]
[bn-asyn (make-asyn-dynamics bn)])
(dotit (pretty-print-boolean-state-graph (build-full-boolean-state-graph-annotated bn-asyn))))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplei4we6j.svg :results raw drawer :cmd sfdp :noweb yes
<<simple-bn-sg-bool-ann()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/examplei4we6j.svg]]
:end:
For some networks, a single transition between two states may be
due to different modalities. Consider the following network:
#+NAME: bn2
| a | (not b) |
| b | b |
#+NAME: bn2-sgr
#+BEGIN_SRC racket :results silent :var input-bn=munch-sexp(bn2)
(let* ([bn (network-form->network (unorgv input-bn))]
[bn-asyn (make-asyn-dynamics bn)])
(dotit (pretty-print-boolean-state-graph (build-full-boolean-state-graph-annotated bn-asyn))))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplehsuRqc.svg :results raw drawer :cmd dot :noweb yes
<<bn2-sgr()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/examplehsuRqc.svg]]
:end:
** 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.
#+BEGIN_SRC racket :results table drawer
(tabulate (λ (x y) (+ x y)) '((1 2) (0 2 4)))
#+END_SRC
#+RESULTS:
:RESULTS:
| 1 | 0 | 1 |
| 1 | 2 | 3 |
| 1 | 4 | 5 |
| 2 | 0 | 2 |
| 2 | 2 | 4 |
| 2 | 4 | 6 |
:END:
Here's how you tabulate a Boolean function:
#+BEGIN_SRC racket :results table drawer
(tabulate/boolean (λ (x y) (and x y)))
#+END_SRC
#+RESULTS:
:RESULTS:
| #f | #f | #f |
| #f | #t | #f |
| #t | #f | #f |
| #t | #t | #t |
:END:
You can tabulate multiple functions taking the same arguments over
the same domains together.
#+BEGIN_SRC racket :results table drawer
(tabulate*/boolean `(,(λ (x y) (and x y)) ,(λ (x y) (or x y))))
#+END_SRC
#+RESULTS:
:RESULTS:
| #f | #f | #f | #f |
| #f | #t | #f | #t |
| #t | #f | #f | #t |
| #t | #t | #t | #t |
:END:
Here's how to tabulate the network =simple-bn=, defined at the top
of this section:
#+BEGIN_SRC racket :results table drawer :var in-bn=munch-sexp(simple-bn)
(tabulate-boolean-network (network-form->network (unorgv in-bn)))
#+END_SRC
#+RESULTS:
:RESULTS:
| 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 |
:END:
** 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:
#+NAME: simple-domains
| a | (#f #t) |
| b | (1 2) |
| c | (cold hot) |
Here's a random function taking values in the codomain =(4 5 6)=:
#+BEGIN_SRC racket :results table drawer :var simple-domains=munch-sexp(simple-domains)
(random-seed 0)
(define rnd-func (random-function/state (unorgv simple-domains) '(4 5 6)))
(tabulate-state rnd-func (unorgv simple-domains))
#+END_SRC
#+RESULTS:
:RESULTS:
| 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 |
:END:
We can build an entire random network over these domains:
#+BEGIN_SRC racket :results table drawer :var simple-domains=munch-sexp(simple-domains)
(random-seed 0)
(define n (random-network (unorgv simple-domains)))
(tabulate-network n (unorgv simple-domains))
#+END_SRC
#+RESULTS:
:RESULTS:
| 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 |
:END:
Let's snapshot this random network and give it a name.
#+NAME: 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 |
Here's how we can read back this table as a Boolean network:
#+HEADER: :var rnd-network=munch-sexp(rnd-network)
#+BEGIN_SRC racket :results output drawer
(string->any rnd-network)
#+END_SRC
#+RESULTS:
:RESULTS:
'(("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"))
:END:
You can use =table->network= to convert a table such as [[rnd-network][rnd-network]]
to a network.
#+HEADER: :var rnd-network=munch-sexp(rnd-network)
#+BEGIN_SRC racket :results output drawer
(table->network (unorg rnd-network))
#+END_SRC
#+RESULTS:
:RESULTS:
'#hash((a . #<procedure:...dds/networks.rkt:518:4>) (b . #<procedure:...dds/networks.rkt:518:4>) (c . #<procedure:...dds/networks.rkt:518:4>))
:END:
Here's the state graph of [[rnd-network][rnd-network]].
#+NAME: rnd-network-sg
#+HEADER: :var rnd-network=munch-sexp(rnd-network)
#+HEADER: :var simple-domains=munch-sexp(simple-domains)
#+BEGIN_SRC racket :results silent drawer
(define n (table->network (unorg rnd-network)))
(define rnd-asyn (make-asyn-dynamics n))
(define states (list->set (build-all-states (unorgv simple-domains))))
(dotit (pretty-print-state-graph (dds-build-state-graph-annotated rnd-asyn states)))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampleHc023j.svg :results raw drawer :cmd sfdp :noweb yes
<<rnd-network-sg()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/exampleHc023j.svg]]
:END:
Here's the signed interaction graph of [[rnd-network][rnd-network]].
#+NAME: rnd-network-ig
#+HEADER: :var rnd-network=munch-sexp(rnd-network)
#+HEADER: :var simple-domains=munch-sexp(simple-domains)
#+BEGIN_SRC racket :results silent drawer
(define n (table->network (unorg rnd-network)))
(dotit (build-signed-interaction-graph n (unorgv simple-domains)))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplePIN5ac.svg :results raw drawer :cmd sfdp :noweb yes
<<rnd-network-ig()>>
#+END_SRC
#+RESULTS:
:RESULTS:
[[file:dots/examplePIN5ac.svg]]
:END:
Note that =build-signed-interaction-graph= only includes the + and
the - arcs in the graph, as it does not have access to the symbolic
description of the function.
** Standalone threshold Boolean functions (TBF)
/Note:/ Before using the objects described in this section,
consider whether the objects from the [[tbn][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.
#+BEGIN_SRC racket :results output drawer
(tbf #(1 1) 1)
#+END_SRC
#+RESULTS:
:RESULTS:
(tbf '#(1 1) 1)
:END:
This TBF only returns 1 when both inputs are activated, which
brings their weighted some above 1: 1 \cdot 1 + 1 \cdot 1 = 2 > 1.
#+BEGIN_SRC racket :results output drawer
(apply-tbf (tbf #(1 1) 1) #(1 1))
#+END_SRC
#+RESULTS:
:RESULTS:
1
:END:
Let's actually check out the truth table of this TBF.
#+BEGIN_SRC racket :results table drawer
(tbf-tabulate (tbf #(1 1) 1))
#+END_SRC
#+RESULTS:
:RESULTS:
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
:END:
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:
#+NAME: simple-tbf
| 1 | 1 | 0 |
| 1 | 1 | 1 |
You can read the two TBFs defined in this table in the following
way:
#+BEGIN_SRC racket :results output drawer :var simple-tbf=munch-sexp(simple-tbf)
(read-org-tbfs simple-tbf)
#+END_SRC
#+RESULTS:
:RESULTS:
(list (tbf '#(1 1) 0) (tbf '#(1 1) 1))
:END:
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:
#+BEGIN_SRC racket :results table drawer :var simple-tbf=munch-sexp(simple-tbf)
(tbf-tabulate* (read-org-tbfs simple-tbf))
#+END_SRC
#+RESULTS:
:RESULTS:
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 1 | 1 |
:END:
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:
#+BEGIN_SRC racket :results output drawer
(sbf? (tbf #(1 1) 1))
(sbf? (tbf #(1 1) 0))
#+END_SRC
#+RESULTS:
:RESULTS:
#f
#t
:END:
You can read SBFs from Org tables, like TBFs:
#+NAME: some-sbfs
| 1 | -1 |
| 2 | 2 |
#+BEGIN_SRC racket :results output drawer :var some-sbfs=munch-sexp(some-sbfs)
(read-org-sbfs some-sbfs)
#+END_SRC
#+RESULTS:
:RESULTS:
(list (tbf '#(1 -1) 0) (tbf '#(2 2) 0))
:END:
** 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:
#+BEGIN_SRC racket :results output drawer
(make-tbf/state '((a . 1) (b . 1)) 1)
#+END_SRC
#+RESULTS:
:results:
(tbf/state '#hash((a . 1) (b . 1)) 1)
:end:
As the example standalone TBF, this TBF only returns 1 when both
inputs are activated:
#+BEGIN_SRC racket :results output drawer
(apply-tbf/state (make-tbf/state '((a . 1) (b . 1)) 1)
(make-state '((a . 1) (b . 1))))
#+END_SRC
#+RESULTS:
:results:
1
:end:
Here's how you can read this TBF from an Org-mode table:
#+NAME: simple-tbf/state
| a | b | θ |
| 1 | 1 | 1 |
#+BEGIN_SRC racket :results output drawer :var simple-tbf/state=munch-sexp(simple-tbf/state)
(read-org-tbfs/state simple-tbf/state)
#+END_SRC
#+RESULTS:
:results:
(list (tbf/state '#hash((a . 1) (b . 1)) 1))
:end:
Note that the header of the rightmost column is discarded.
=read-org-tbfs/state= can also read multiple TBFs at once:
#+NAME: simple-tbfs/state
| a | b | θ |
| 1 | 1 | 1 |
| -2 | 1 | 0 |
#+BEGIN_SRC racket :results output drawer :var simple-tbfs/state=munch-sexp(simple-tbfs/state)
(read-org-tbfs/state simple-tbfs/state)
#+END_SRC
#+RESULTS:
:results:
(list (tbf/state '#hash((a . 1) (b . 1)) 1) (tbf/state '#hash((a . -2) (b . 1)) 0))
:end:
You can print a list of TBFs in the following way:
#+BEGIN_SRC racket :results table drawer :var simple-tbfs/state=munch-sexp(simple-tbfs/state)
(print-org-tbfs/state (read-org-tbfs/state simple-tbfs/state))
#+END_SRC
#+RESULTS:
:results:
| a | b | θ |
| 1 | 1 | 1 |
| -2 | 1 | 0 |
:end:
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):
#+BEGIN_SRC racket :results table drawer :var simple-tbfs/state=munch-sexp(simple-tbfs/state)
(tbf/state-tabulate* (read-org-tbfs/state simple-tbfs/state))
#+END_SRC
#+RESULTS:
:results:
| a | b | f1 | f2 |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 0 |
| 1 | 1 | 1 | 0 |
:end:
=dds= also includes functions for dealing with SBF operating on
states. In particular, to create an SBF, you can do:
#+BEGIN_SRC racket :results output drawer
(make-sbf/state '((a . -1) (b . 1)))
#+END_SRC
#+RESULTS:
:results:
(tbf/state '#hash((a . -1) (b . 1)) 0)
:end:
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:
#+NAME: simple-sbfs/state
| a | b |
| 1 | 1 |
| -2 | 1 |
You can read these SBFs in the following way:
#+BEGIN_SRC racket :results output drawer :var simple-sbfs/state=munch-sexp(simple-sbfs/state)
(read-org-sbfs/state simple-sbfs/state)
#+END_SRC
#+RESULTS:
:results:
(list (tbf/state '#hash((a . 1) (b . 1)) 0) (tbf/state '#hash((a . -2) (b . 1)) 0))
:end:
You can print them back to an Org-mode table as follows:
#+BEGIN_SRC racket :results table drawer :var simple-sbfs/state=munch-sexp(simple-sbfs/state)
(print-org-sbfs/state (read-org-sbfs/state simple-sbfs/state))
#+END_SRC
#+RESULTS:
:results:
| a | b |
| 1 | 1 |
| -2 | 1 |
:end:
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:
#+NAME: tbfs-nots
| - | x | y | θ |
| y | -1 | 0 | -1 |
| x | 0 | -1 | -1 |
#+BEGIN_SRC racket :results output drawer :var tbfs-nots=munch-sexp(tbfs-nots)
(read-org-tbn tbfs-nots)
#+END_SRC
#+RESULTS:
:results:
(hash 'x (tbf/state '#hash((x . 0) (y . -1)) -1) 'y (tbf/state '#hash((x . -1) (y . 0)) -1))
:end:
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:
#+NAME: tbfs-nots-sg
#+BEGIN_SRC racket :results silent drawer :var tbfs-nots=munch-sexp(tbfs-nots)
(dotit (build-tbn-state-graph (read-org-tbn tbfs-nots)))
#+END_SRC
#+BEGIN_SRC dot :file dots/examplew206DH.svg :results raw drawer :cmd sfdp :noweb yes
<<tbfs-nots-sg()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/examplew206DH.svg]]
:end:
For convenience, there is a similar function =read-org-sbn= which
allows reading an SBN.
#+NAME: sbn-figure2
| - | A | B | C |
| A | -1 | 1 | 2 |
| B | 2 | -2 | -2 |
| C | -1 | 2 | -1 |
#+NAME: sbn-figure2-sg
#+BEGIN_SRC racket :results silent drawer :var sbn-figure2=munch-sexp(sbn-figure2)
(dotit (build-tbn-state-graph (read-org-sbn sbn-figure2)))
#+END_SRC
#+BEGIN_SRC dot :file dots/sbn-figure2-sg.svg :results raw drawer :cmd sfdp :noweb yes
<<sbn-figure2-sg()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/sbn-figure2-sg.svg]]
:end:
You can print TBNs using =print-org-tbn= in the following way:
#+BEGIN_SRC racket :results table drawer :var sbn-figure2=munch-sexp(sbn-figure2)
(print-org-tbn (read-org-sbn sbn-figure2))
#+END_SRC
#+RESULTS:
:results:
| - | A | B | C | θ |
| A | -1 | 1 | 2 | 0 |
| B | 2 | -2 | -2 | 0 |
| C | -1 | 2 | -1 | 0 |
:end:
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:
#+BEGIN_SRC racket :results table drawer :var sbn-figure2=munch-sexp(sbn-figure2)
(print-org-sbn (read-org-sbn sbn-figure2))
#+END_SRC
#+RESULTS:
:results:
| - | A | B | C |
| A | -1 | 1 | 2 |
| B | 2 | -2 | -2 |
| C | -1 | 2 | -1 |
:end:
=dds= also defines functions to draw the interaction graphs
of TBNs:
#+NAME: tbfs-nots-ig
#+BEGIN_SRC racket :results silent drawer :var tbfs-nots=munch-sexp(tbfs-nots)
(dotit (tbn-interaction-graph (read-org-tbn tbfs-nots)))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampletCklZa.svg :results raw drawer :cmd dot :noweb yes
<<tbfs-nots-ig()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampletCklZa.svg]]
:end:
=tbn-interaction-graph= can optionally omit edges with zero weight:
#+NAME: tbfs-nots-ig-no0
#+BEGIN_SRC racket :results silent drawer :var tbfs-nots=munch-sexp(tbfs-nots)
(dotit (tbn-interaction-graph (read-org-tbn tbfs-nots) #:zero-edges #f))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampleudm6jn.svg :results raw drawer :cmd dot :noweb yes
<<tbfs-nots-ig-no0()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampleudm6jn.svg]]
:end:
You can print the note labels in a slightly prettier way using
=pretty-print-tbn-interaction-graph=:
#+NAME: tbfs-nots-ig-pp
#+BEGIN_SRC racket :results silent drawer :var tbfs-nots=munch-sexp(tbfs-nots)
(dotit (pretty-print-tbn-interaction-graph (tbn-interaction-graph (read-org-tbn tbfs-nots))))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampleQLHMVK.svg :results raw drawer :cmd dot :noweb yes
<<tbfs-nots-ig-pp()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampleQLHMVK.svg]]
:end:
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.
#+NAME: sbn-figure2-ig
#+BEGIN_SRC racket :results silent drawer :var sbn-figure2=munch-sexp(sbn-figure2)
(dotit (sbn-interaction-graph (read-org-sbn sbn-figure2)))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampleaSeyzw.svg :results raw drawer :cmd sfdp :noweb yes
<<sbn-figure2-ig()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampleaSeyzw.svg]]
:end:
* Reaction systems
:PROPERTIES:
:header-args:racket: :prologue "#lang racket\n(require graph dds/rs dds/utils)"
:END:
Consider the following reaction system:
#+NAME: rs1
| a | x t | y | z |
| b | x | q | z |
Here is how we read this reaction into Racket code:
#+BEGIN_SRC racket :results output drawer :var input-rs=munch-sexp(rs1)
(read-org-rs input-rs)
#+END_SRC
#+RESULTS:
:RESULTS:
(hash 'a (reaction (set 'x 't) (set 'y) (set 'z)) 'b (reaction (set 'x) (set 'q) (set 'z)))
:END:
Here is how we can put it back into an Org-mode table:
#+BEGIN_SRC racket :results table drawer :var input-rs=munch-sexp(rs1)
(rs->ht-str-triples (read-org-rs input-rs))
#+END_SRC
#+RESULTS:
:RESULTS:
| a | "t x" | "y" | "z" |
| b | "x" | "q" | "z" |
:END:
Here is how we can apply this reaction system to a state:
#+BEGIN_SRC racket :results output drawer :var input-rs=munch-sexp(rs1)
(let ([rs (read-org-rs input-rs)])
(apply-rs rs (set 'x 't)))
#+END_SRC
#+RESULTS:
:RESULTS:
(set 'z)
:END:
Let's see which reactions got applied:
#+BEGIN_SRC racket :results list :var input-rs=munch-sexp(rs1)
(let ([rs (read-org-rs input-rs)])
(list-enabled rs (set 'x 't)))
#+END_SRC
#+RESULTS:
- a
- b
You can also give a name to a list and read it with =munch-sexp=:
#+NAME: ctx1
- x y
- z
-
- t
#+BEGIN_SRC racket :results output drawer :var input-ctx=munch-sexp(ctx1)
(read-context-sequence input-ctx)
#+END_SRC
#+RESULTS:
:RESULTS:
(list (set 'x 'y) (set 'z) (set) (set 't))
:END:
Let's see what the evolution of =rs1= looks like with the context
sequence =ctx1=.
#+NAME: rs1-sgr
#+HEADER: :var input-rs=munch-sexp(rs1) :var input-ctx=munch-sexp(ctx1)
#+BEGIN_SRC racket :results output drawer
(dotit (pretty-print-state-graph (build-interactive-process-graph (read-org-rs input-rs) (read-context-sequence input-ctx))))
#+END_SRC
#+RESULTS: rs1-sgr
:RESULTS:
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="{}"];
}
}
:END:
#+BEGIN_SRC dot :file dots/examplevvXFaI.svg :results raw drawer :cmd circo :noweb yes
<<rs1-sgr()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/examplevvXFaI.svg]]
:end:
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.
#+NAME: rs1-sgr-no-ctx
#+HEADER: :var input-rs=munch-sexp(rs1) :var input-ctx=munch-sexp(ctx1)
#+BEGIN_SRC racket :results silent drawer
(dotit (pretty-print-reduced-state-graph
(build-reduced-state-graph (read-org-rs input-rs)
(read-context-sequence input-ctx))))
#+END_SRC
#+BEGIN_SRC dot :file dots/exampleLGKcXp.svg :results raw drawer :cmd sfdp :noweb yes
<<rs1-sgr-no-ctx()>>
#+END_SRC
#+RESULTS:
:results:
[[file:dots/exampleLGKcXp.svg]]
:end:
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.
#+NAME: rs1-ip
#+HEADER: :var input-rs=munch-sexp(rs1) :var input-ctx=munch-sexp(ctx1)
#+BEGIN_SRC racket :results table drawer
(build-interactive-process (read-org-rs input-rs) (read-context-sequence input-ctx))
#+END_SRC
#+RESULTS: rs1-ip
:RESULTS:
| (y x) | nil |
| (z) | (z) |
| nil | nil |
| (t) | nil |
| nil | nil |
:END:
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=.
* Local Variables :noexport:
# Local Variables:
# eval: (auto-fill-mode)
# ispell-local-dictionary: "en"
# org-link-file-path-type: relative
# End: