1024 lines
30 KiB
Org Mode
1024 lines
30 KiB
Org Mode
#+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-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-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 interaction graph of this network:
|
||
#+NAME: simple-bn-ig
|
||
#+BEGIN_SRC racket :results silent :var simple-bn=munch-sexp(simple-bn)
|
||
(dotit (build-interaction-graph (unorgv simple-bn)))
|
||
#+END_SRC
|
||
|
||
#+BEGIN_SRC dot :file dots/examplejTo8XT.svg :results raw drawer :cmd sfdp :noweb yes
|
||
<<simple-bn-ig()>>
|
||
#+END_SRC
|
||
|
||
#+RESULTS:
|
||
:RESULTS:
|
||
[[file:dots/examplejTo8XT.svg]]
|
||
:END:
|
||
|
||
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-boolean-signed-interaction-graph/form (unorgv simple-bn)))
|
||
#+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:
|
||
|
||
For the interaction a \to b, note indeed that when c is #f, b is
|
||
always #f (positive interaction). On the other hand, when c is #t,
|
||
b becomes (not a) (negative interaction). Therefore, the influence
|
||
of a on b is neither activating nor inhibiting.
|
||
|
||
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.
|
||
|
||
** Threshold Boolean functions (TBF)
|
||
=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:
|
||
|
||
* 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 we need to keep the full context sequence in the name of
|
||
each state to avoid merging states with the same result and
|
||
contexts, but which occur at different steps of the evolution.
|
||
|
||
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:
|