dds/scribblings/tbn.scrbl
2023-08-08 16:29:14 +02:00

602 lines
19 KiB
Racket

#lang scribble/manual
@(require scribble/example racket/sandbox
(for-label typed/racket/base
"../tbn.rkt"
"../networks.rkt"
"../utils.rkt"
"../functions.rkt"
"../dynamics.rkt"))
@(define tbn-evaluator
(parameterize ([sandbox-output 'string]
[sandbox-error-output 'string]
[sandbox-memory-limit 500])
(make-evaluator 'typed/racket #:requires '("tbn.rkt"))))
@(define-syntax-rule (ex . args)
(examples #:eval tbn-evaluator . args))
@(define-syntax-rule (deftypeform . args)
(defform #:kind "type" . args))
@(define-syntax-rule (deftype . args)
(defidform #:kind "type" . args))
@title[#:tag "tbn"]{dds/tbn: Threshold and Sign Boolean Networks (TBN and SBN)}
@defmodule[dds/tbn]
@section{TBFs and states}
This module defines threshold Boolean networks (TBN), as well as sign
Boolean networks (SBN). The update functions in such networks are
respectively @seclink["tbf" #:doc '(lib
"dds/scribblings/dds.scrbl")]{threshold Boolean functions} and sign
Boolean functions.
@defproc[(apply-tbf-to-state [a-tbf TBF] [st (State (U Zero One))])
(U Zero One)]{
Applies a TBF to a state.
The values of the variables of the state are ordered by
@racket[hash-map] and fed to the TBF in order. The number of the
inputs of the TBF must match the number of variables in the state.
@ex[
(require "functions.rkt")
(apply-tbf-to-state (tbf #(1 1) 1) (hash 'x1 0 'x2 1))
]}
@defstruct*[tbf/state ([weights (VariableMapping Real)]
[threshold Real])]{
A state TBF is a @racket[TBF] with named inputs. A state TBF can be
applied to states in an unambiguous ways.
}
@deftype[TBF/State]{
The type of the instances of @racket[tbf/state].
}
@deftogether[(@defproc[(tbf/state-w [tbfs TBF/State]) (VariableMapping Real)]
@defproc[(tbf/state-θ [tbfs TBF/State]) Real])]{
Shorter synonyms for field accessors of @racket[tbf/state].
@ex[
(let ([tbfs (tbf/state (hash 'a 1 'b 1) 1)])
(values (tbf/state-w tbfs)
(tbf/state-θ tbfs)))
]}
@defproc[(make-tbf/state [pairs (Listof (Pairof Variable Real))]
[threshold Real])
TBF/State]{
Makes a @racket[TBF/State] from a list of pairs of names of variables
and weights, as well as a threshold.
@ex[
(make-tbf/state '((x1 . 1) (x2 . 1)) 1)
]}
@defproc[(sbf/state? [tbfs TBF/State]) Boolean]{
A state sign Boolean function (SBF) is a @racket[TBF/State] whose
threshold is 0.
@ex[
(sbf/state? (tbf/state (hash 'a -1 'b 1) 0))
(sbf/state? (tbf/state (hash 'a -1 'b 1) 1))
]}
@defproc[(apply-tbf/state [tbfs TBF/State]
[st (State (U Zero One))])
(U Zero One)]{
Applies a @racket[TBF/State] to its inputs given by the state
@racket[st].
Applying a TBF consists in multiplying the weights by the
corresponding inputs and comparing the sum of the products to
the threshold.
This function is similar to @racket[apply-tbf], but because it applies
a @racket[TBF/State] to a @racket[(State (U Zero One))], it avoids
potential mismatches between weights and the corresponding
input values.
@ex[
(apply-tbf/state (tbf/state (hash 'a 2 'b -2) 1)
(hash 'a 1 'b 0 'c 1))
]}
@defproc[(compact-tbf [tbf TBF/State]) TBF/State]{
Compacts (and denormalizes) a TBF by removing all inputs which are 0.
@ex[
(compact-tbf (tbf/state (hash 'a 0 'b 1 'c 2 'd 0) 2))
]}
@section{Reading and printing TBFs and SBFs}
@defproc[(lists+vars->tbfs/state [vars (Listof Variable)]
[lsts (Listof (Listof Real))])
(Listof TBF/State)]{
Reads a list of @racket[TBF/State] from a list of list of
@racket[Real]s.
The last element of each list is taken to be the threshold of the
TBFs, and the rest of the elements are taken to be the weights.
@ex[
(lists+vars->tbfs/state '(x y) '((1 2 3) (1 1 2)))
]}
@defproc[(lists+headers->tbfs/state [lsts+headers (Pairof (Listof Variable) (Listof (Listof Real)))])
(Listof TBF/State)]{
Like @racket[lists+vars->tbfs/state], but the names of the variables
are taken from the first line of @racket[lsts+headers].
All the lines in @racket[lsts+headers] are assumed to be of the same
lenght, which means in particular that the last element of the first
line (the threshold column) is discarded.
@ex[
(lists+headers->tbfs/state '((x y f) (1 2 3) (1 1 2)))
]}
@defproc[(lists->tbfs/state [lsts (Listof (Liostf Real))])
(Listof TBF/State)]{
Like @racket[lists+vars->tbfs/state], but the names of the variables
are generated as @tt{xi}, where @italic{i} is the index of the
variable, starting from 0.
@ex[
(lists->tbfs/state '((1 2 3) (1 1 2)))
]}
@defproc[(lists->tbfs/state/opt-headers
[lsts (Listof (Listof (U Variable Real)))]
[#:headers hdr Boolean])
(Listof TBF/State)]{
This function allows selecting between @racket[lists->tbfs/state] and
@racket[lists+headers->tbfs/state] based on the value of @racket[hdr].
If @racket[hdr] is @racket[#f], then @racket[lists->tbfs/state] is
applied to @racket[lsts], otherwise @racket[lists+headers->tbfs/state]
is applied.
@ex[
(lists->tbfs/state/opt-headers '((1 2 3) (1 1 2)) #:headers #f)
(lists->tbfs/state/opt-headers '((x y f) (1 2 3) (1 1 2)) #:headers #t)
]}
@deftogether[(@defproc[(lists+vars->sbfs/state [vars (Listof Variable)]
[lsts (Listof (Listof Real))])
(Listof TBF/State)]
@defproc[(lists+headers->sbfs/state
[lsts (Pairof (Listof Variable) (Listof (Listof Real)))])
(Listof TBF/State)]
@defproc[(lists->sbfs/state [lsts (Listof (Listof Real))])
(Listof TBF/State)])]{
Like the corresponding TBF-related functions, but which create SBFs.
In other words, the input lists are treated as lists of weights, and
the thresholds are set to 0.
@ex[
(lists+vars->sbfs/state '(x y) '((1 2) (1 1)))
(lists+headers->sbfs/state '((x y) (1 2) (1 1)))
(lists->sbfs/state '((1 2) (1 1)))
]}
@defproc[(read-org-tbfs/state [str String]) (Listof TBF/State)]{
Reads a list of @racket[TBF/State] from an Org-mode string containing
a sexp, containing a list of lists of numbers. As in
@racket[lists->tbfs/state], the last element of each list is taken to
be the threshold of the TBF, and the rest of the elements are taken to
be the weights.
Similarly to @racket[lists->tbfs/state], the names of the variables
are generated as @tt{xi}, where @italic{i} is the index of the
variable, starting from 0.
@ex[
(read-org-tbfs/state "((1 2 3) (1 1 2))")
]}
@defproc[(read-org-tbfs/state+headers [str String]) (Listof TBF/State)]{
Like @racket[read-org-tbfs/state], but the first list in @racket[str]
is taken to contain the names of the variables, similarly to
@racket[lists+headers->tbfs/state].
@ex[
(read-org-tbfs/state+headers "((a b f) (1 2 3) (1 1 2))")
]}
@defproc[(tbfs/state->lists [tbfs (Listof TBF/State)]) (Listof (Listof Real))]{
Given a list of @racket[TBF/State], produces a sexp that Org-mode can
interpret as a table.
All @racket[TBF/State] in the list must have the same inputs.
The function does not check this property.
@ex[
(tbfs/state->lists (list (tbf/state (hash 'a 1 'b 2) 3)
(tbf/state (hash 'a -2 'b 1) 1)))
]}
@defproc[(tbfs/state->lists+headers [tbfs (Listof TBF/State)])
(Pairof (Listof Variable) (Listof (Listof Real)))]{
Like @racket[tbfs/state->lists+headers], but the first list of the
result is the list of input names of the first @racket[TBF/State] in
@racket[tbfs]. The last element of this first list is @racket['θ] and
corresponds to the column giving the thresholds of the TBFs.
@ex[
(tbfs/state->lists+headers
(list (tbf/state (hash 'a 1 'b 2) 3)
(tbf/state (hash 'a -2 'b 1) 1)))
]}
@defproc[(sbfs/state->lists [sbfs (Listof TBF/State)])
(Listof (Listof Real))]{
Like @racket[tbfs/state->lists], but the thresholds are omitted.
@ex[
(sbfs/state->lists (list (tbf/state (hash 'a 1 'b 2) 0)
(tbf/state (hash 'a -2 'b 1) 0)))
]
Note that this function just drops the threshold, without checking
whether it is actually 0:
@ex[
(sbfs/state->lists (list (tbf/state (hash 'a 1 'b 2) 3)))
]}
@defproc[(sbfs/state->lists+headers [sbfs (Listof TBF/State)])
(Pairof (Listof Variable) (Listof (Listof Real)))]{
Like @racket[sbfs/state->lists], but also shows the names of the
variables as column headers.
@ex[
(sbfs/state->lists+headers (list (tbf/state (hash 'a 1 'b 2) 0)
(tbf/state (hash 'a -2 'b 1) 0)))
]}
@section{Tabulating TBFs and SBFs}
@defproc[(tabulate-tbfs/state [tbfs (Listof TBF/State)]) (Listof (Listof Real))]{
Tabulates a list of @racket[TBF/State].
As in the case of @racket[tbf-tabulate*], the result is a list of
lists giving the truth tables of the given TBFs. The first elements
of each row give the values of the inputs, while the last elements
give the values of each function corresponding to the input.
All the TBFs must have exactly the same inputs. This function does
not check this property.
@ex[
(tabulate-tbfs/state
(list (tbf/state (hash 'a 1 'b 2) 2)
(tbf/state (hash 'a -2 'b 2) 1)))
]}
@defproc[(tabulate-tbfs/state+headers [tbfs (Listof TBF/State)])
(Pairof (Listof Variable)
(Listof (Listof Real)))]{
Like @racket[tabulate-tbfs/state], but the first list of the result is
a gives the names of the variables appearing in the inputs of
@racket[(car tbfs)], followed by function names. The function names
are generated as @tt{fi}, where @tt{i} is the number of the TBF in
the list.
@ex[
(tabulate-tbfs/state+headers
(list (tbf/state (hash 'a 1 'b 2) 2)
(tbf/state (hash 'a -2 'b 2) 1)))
]}
@deftogether[(@defproc[(tabulate-tbf/state [tbf TBF/State])
(Listof (Listof Real))]
@defproc[(tabulate-tbf/state+headers [tbf TBF/State])
(Pairof (Listof Variable) (Listof (Listof Real)))])]{
Like @racket[tabulate-tbfs/state] and
@racket[tabulate-tbfs/state+headers], but only tabulate single TBFs.
@ex[
(tabulate-tbf/state (tbf/state (hash 'a 1 'b 2) 2))
(tabulate-tbf/state+headers (tbf/state (hash 'a 1 'b 2) 2))
]}
@section{TBNs and SBNs}
@deftype[TBN]{
The type of a TBN, i.e. a mapping assigning to each variable
a @racket[TBF/State].
}
@defproc[(sbn? [tbn TBN]) Boolean]{
A SBN is a @racket[TBN] in which all @racket[TBF/State]s satisfy
@racket[sbf/state?].
All functions in @racket[tbn] must only reference variables appearing
in the network. This function does not check this condition.
@ex[
(let ([f1 (tbf/state (hash 'a -1 'b 1) 0)]
[f2 (tbf/state (hash 'a -1 'b 1) 1)])
(values (sbn? (hash 'a f1 'b f1))
(sbn? (hash 'a f1 'b f2))))
]}
@defproc[(tbn->network [tbn TBN]) (Network (U Zero One))]{
Constructs a @racket[Network] out of the given @racket[tbn].
@ex[
(require (only-in "networks.rkt" update))
(let* ([tbn-form (hash 'a (tbf/state (hash 'a -1 'b 1) 0)
'b (tbf/state (hash 'a -1 'b 1) 1))]
[tbn (tbn->network tbn-form)]
[s (hash 'a 0 'b 1)])
(update tbn s '(a b)))
]}
@defproc[(build-tbn-state-graph [tbn TBN]) Graph]{
Builds the state graph of a @racket[TBN].
This function constructs a @racket[(Network (U Zero One))] from
@racket[tbn], then builds the state graph of its synchronous dynamics,
and pretty-prints the node labels.
@ex[
(require (only-in "utils.rkt" dotit))
(dotit (build-tbn-state-graph
(hash 'a (tbf/state (hash 'a -1 'b 1) 0)
'b (tbf/state (hash 'a -1 'b 1) 1))))
]}
@defproc[(normalized-tbn? [tbn TBN]) Boolean]{
Checks whether @racket[tbn] is normalized: whether all of the
functions have the same inputs, and whether these inputs are exactly
the variables of @racket[tbn].
@ex[
(normalized-tbn?
(hash 'x (tbf/state (hash 'x 0 'y -1) -1)
'y (tbf/state (hash 'x -1 'y 0) -1)))
(normalized-tbn?
(hash 'x (tbf/state (hash 'x 0 ) -1)
'y (tbf/state (hash 'y 0) -1)))
]}
@defproc[(normalize-tbn (tbn TBF)) TBN]{
Normalizes @racket[tbn]: for every @racket[TBF/State], removes the
inputs that are not in the variables of @racket[tbn], and adds missing
inputs with 0 weight.
@ex[
(normalize-tbn (hash 'x (tbf/state (hash 'x 2) -1)
'y (tbf/state (hash 'y 3) 1)))
]}
@defproc[(compact-tbn [tbn TBN]) TBN]{
Compacts the @racket[tbn] by removing all inputs which are 0 or which
are not variables of the network.
@ex[
(compact-tbn (hash 'a (tbf/state (hash 'a 0 'b 1 'c 3 'd 0) 0)
'b (tbf/state (hash 'a -1 'b 1) -1)))
]}
@defproc[(tbn-interaction-graph [tbn TBN]
[#:zero-edges zero-edges Boolean #t])
Graph]{
Constructs the interaction graph of @racket[tbn]. The nodes of this
graph are labeled with pairs (variable name, threshold), while the
edges are labeled with the weights.
If @racket[#:zero-edges] is @racket[#t], the edges with zero weights
will also appear in the interaction graph.
@ex[
(dotit (tbn-interaction-graph (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) -1))))
(dotit (tbn-interaction-graph (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) -1))
#:zero-edges #f))
]}
@defproc[(pretty-print-tbn-interaction-graph [ig Graph]) Graph]{
Pretty prints the node labels of the interaction graph of a TBN.
@ex[
(dotit (pretty-print-tbn-interaction-graph
(tbn-interaction-graph (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) -1)))))
]}
@defproc[(sbn-interaction-graph [sbn TBN]) Graph]{
Constructs the interaction graph of @racket[sbn], like
@racket[tbn-interaction-graph], but the nodes of the graph are labeled
with variable names only. This is an adaptation to SBNs, in which all
weights are 0. The function does not check whether @racket[sbn] is
indeed an SBN.
@ex[
(dotit (sbn-interaction-graph (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) -1))))
]}
@section{Reading and printing TBNs and SBNs}
@defproc[(parse-org-tbn [tab (Listof (Listof (U Symbol Real)))]
[#:headers headers Boolean #t]
[#:func-names func-names Boolean #t])
TBN]{
Reads a TBN from a list of lists of numbers or symbols, which may
represent an Org-mode table. As in @racket[lists->tbfs/state], the
last element of each list is taken to be the threshold of the TBF, and
the rest of the elements are taken to be the weights.
If @racket[headers] is @racket[#t], the names of the variables to
appear as the inputs of the TBF are taken from the first list.
The last element of this list (corresponding to the column giving the
threshold) is discarded. If @racket[headers] is @racket[#f], the
names of the variables are generated as @tt{xi}, where @tt{i} is
the index of the variable.
If @racket[func-names] is @racket[#t], the first element in every row
except the first one are taken to be the name of the variable to which
the TBF should be associated. If @racket[func-names] is @racket[#f],
the functions are assigned to variables in lexicographic order.
@racket[func-names] cannot be @racket[#t] if @racket[headers] is
@racket[#f]. The function does not check this condition.
This is a helper function for @racket[read-org-tbn] and
@racket[read-org-sbn].
@ex[
(parse-org-tbn '((1 2 3) (3 2 1)) #:headers #f #:func-names #f)
(parse-org-tbn '((a b θ) (1 2 3) (3 2 1)) #:headers #t #:func-names #f)
(parse-org-tbn '((dummy a b θ) (b 3 2 1) (a 1 2 3)) #:headers #t #:func-names #t)
]}
@defproc[(read-org-tbn [str String]
[#:headers headers Boolean #t]
[#:func-names func-names Boolean #t])
TBN]{
Reads a TBN from an string containing a sexp, containing a list of
lists of numbers and possibly symbols. This string may be produced by
Org-mode.
As in @racket[lists->tbfs/state], the last element of each list is
taken to be the threshold of the TBFs, and the rest of the elements
are taken to be the weights.
As in @racket[parse-org-tbn], if @racket[headers] is @racket[#t], the
names of the variables to appear as the inputs of the TBF are taken
from the first list. The last element of this list is discarded.
If @racket[headers] is @racket[#f], the names of the variables are
generated as @tt{xi}, where @tt{i} is the index of the variable.
If @racket[func-names] is @racket[#t], the first element in every row
except the first one, are taken to be the name of the variable to
which the TBF should be associated. If @racket[func-names] is
@racket[#f], the functions are assigned to variables in
alphabetical order.
As in @racket[parse-org-tbn], @racket[func-names] cannot be
@racket[#t] if @racket[headers] is @racket[#f]. The function does not
check this condition.
@ex[
(read-org-tbn "((\"-\" \"x\" \"y\" \"θ\") (\"y\" -1 0 -1) (\"x\" 0 -1 -1))")
]}
@defproc[(read-org-sbn [str String]
[#:headers headers Boolean #t]
[#:func-names func-names Boolean #t])
TBN]{
Like @racket[read-org-tbn], but reads an SBN from the input string,
i.e. all the numbers are taken to be the weights, and the threshold is
set to 0.
@ex[
(read-org-sbn "((\"-\" \"x\" \"y\") (\"y\" -1 0) (\"x\" 0 -1))")
]}
@defproc[(tbn->lists [tbn TBN]
[#:headers headers Boolean #t]
[#:func-names func-names Boolean #t])
(Listof (Listof (U Symbol Real)))]{
Given a @racket[tbn], produces a list of lists of numbers or symbols,
containing the description of the functions of the TBN. This list can
be read back by @racket[parse-org-tbn], and Org-mode can interpret it
as a table.
Similarly to @racket[parse-org-tbn], if @racket[#:headers] is
@racket[#f], this function does not print the names of the inputs of
the TBFs. If @racket[#:headers] is @racket[#t], the output starts by
a list giving the names of the variables, as well as the symbol
@racket['θ] to represent the column giving the thresholds of the TBF.
If @racket[#:func-names] is @racket[#t], the first column of the table
gives the name of the variable which the corresponding TBF updates.
If both @racket[#:func-names] and @racket[#:headers] are @racket[#t],
the first cell of the first column contains the symbol
@racket['-].
@ex[
(tbn->lists (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) -1)))
]}
@defproc[(sbn->lists [sbn TBN]
[#:headers headers Boolean #t]
[#:func-names func-names Boolean #t])
(Listof (Listof (U Symbol Real)))]{
Like @racket[tbn->lists], but does not show the thresholds—an
adaptation for printing SBNs.
@ex[
(sbn->lists (hash 'a (tbf/state (hash 'b 1) 0)
'b (tbf/state (hash 'a -1) 0)))
]}
@section{Miscellaneous utilities}
@defproc[(group-truth-table-by-nai [tt (Listof (Listof Integer))])
(Listof (Listof (Listof Integer)))]{
Given the truth table @racket[tt] of a Boolean function, groups the
lines by the @italic{N}umber of @italic{A}ctivated @italic{I}nputs—the
number of inputs which are 1 in the input vector.
@ex[
(group-truth-table-by-nai '((0 0 0 1)
(0 0 1 1)
(0 1 0 0)
(0 1 1 1)
(1 0 0 0)
(1 0 1 0)
(1 1 0 1)
(1 1 1 0)))
]}