#+TITLE: Examples of usage of =dds= #+PROPERTY: header-args:racket :prologue "#lang racket\n(require graph dds/networks.rkt dds/utils.rkt)" * 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= <> ** 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 ones. 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: ** Reading Org-mode tables<> 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: # :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 <> #+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= 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. ** 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 <> #+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 <> #+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 <> #+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 <> #+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 <> #+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 <> #+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 <> #+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: 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 . #) (b . #) (c . #)) :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 <> #+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 <> #+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. * 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 <> #+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: