#| probmachine.lisp: a generic nondeterministic state machine in Common Lisp last modified 02.04.2001 lcb The function state-machine implements a generic nondeterministic state machine. The machine is specified with an association list. Each element of the list should be of the form: (state-name . ((input-1 . ((probability-1 . (target-state-a . output-a)) (probability-2 . (target-state-b . output-b)) ... (probability-n . (target-state-n . output-n)))) ... (input-n . ((probability-n . (target-state-n . output-n)))))) There can be any number of states, any number of inputs per state, and any number of possible target-states per input. Note that probabilities must be specified as fractions of one (1/5 or 0.2) and that all probabilities must sum to one. (This is a wart -- it would be easier to modify the statemachine on the fly if this requirement did not exist). (select-next) is an associated function which generates a random value and tests it against the first (state, input, probability) triple look for the next target and output. (select-next) calls (try-next-selection) to do the recursive "hard work" of testing additional triples if the first probability is less than the random value generated. (get-current-probability), (get-next-pstate), and (get-next-poutput) are associated functions which traverse the probability-machine data structure. Notice that these are more complicated than their companions for the state-machine (and that I've changed the names of the state and output functions so that the deterministic and nondeterministic machines may be run side by side). In general, this code is not so elegant -- can you improve it? probability-machine takes a list of inputs and returns a list of outputs -- which can then be applied in any way. |# (defun probability-machine (input current-state machine-structure) (if input (let* ((current-input (car input)) (remaining-input (cdr input)) (next-choice (select-next current-input current-state machine-structure)) (target-state (get-next-pstate next-choice current-input current-state machine-structure)) (next-output (get-next-poutput next-choice current-input current-state machine-structure))) (append (list next-output) (probability-machine remaining-input target-state machine-structure))))) (defun select-next (input state machine-structure) (let* ((random-value (random 1.0)) (first-probability (get-current-probability 0 input state machine-structure))) (if (> random-value first-probability) (try-another-selection random-value first-probability 1 input state machine-structure) 0))) (defun try-another-selection (random-value lower-bound index input state machine-structure) (let* ((current-probability (+ lower-bound (get-current-probability index input state machine-structure)))) (if (> random-value current-probability) (try-another-selection random-value current-probability (+ index 1) input state machine-structure) index))) (defun get-current-probability (index input state machine-structure) (car (nth index (cdr (assoc input (cdr (assoc state machine-structure))))))) (defun get-next-pstate (index input state machine-structure) (car (cdr (nth index (cdr (assoc input (cdr (assoc state machine-structure)))))))) (defun get-next-poutput (index input state machine-structure) (cdr (cdr (nth index (cdr (assoc input (cdr (assoc state machine-structure)))))))) #| simple "does it work?" example: (setf pmachine-1 '((state-1 . ((0 . ((0.5 . (state-2 . 0)) (0.5 . (state-2 . 1)))) (1 . ((0.2 . (state-2 . 2)) (0.3 . (state-2 . 3)) (0.5 . (state-2 . 4)))))) (state-2 . ((0 . ((0.5 . (state-2 . 5)) (0.5 . (state-2 . 6)))) (1 . ((0.5 . (state-1 . 7)) (0.5 . (state-1 . 8)))))))) (probability-machine (list 0 0 1 1 0) 'state-1 pmachine-1) note that multiple calls to probability-machine with the same input, start-state and machine-structure should produce different results; if you want the same results every time, stick with the deterministic state machine.... |#