• Stars
    star
    232
  • Rank 172,847 (Top 4 %)
  • Language
    Clojure
  • Created over 11 years ago
  • Updated about 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Clojure library for generating streams of events based on stochastic state machines.

causatum

cau·sa·tum noun \kau̇ˈzätəm, kȯˈzāt-
pl causa·ta

: something that is caused : effect

Merriam-Webster Online Dictionary

A Clojure library designed to generate streams of timed events based on stochastic state machines.

Liveness Advisory

"I see the only thing you've changed in a year is the README. Is this project still alive?"

Yep. As of this writing (25-Sep-2014) I consider this library alive and well, and expect it to continue to be so. The reason there haven't been any updates is that although I can think of changes I'd like to make, the darn thing just works: the changes would essentially be cosmetic. I feel comfortable recommending it for your use as well.

Update 2017-Oct-16: Still true. :)

Quick Start

  • Add [org.craigandera/causatum "0.3.0"] to your dependencies in project.clj.
  • Create a simple model:
(def model {:graph {:a [{:b {:weight 1 :delay [:constant 1]}
                         :c {:weight 2 :delay [:constant 2]}}]
                    :b [{:a {:weight 0.5 :delay [:constant 3]}
                         :b {:weight 3.14 :delay [:constant 1]}
                         :c {:weight 1 :delay [:constant 3]}}]}
            :delay-ops {:constant (fn [rtime t] t)}})
  • Use it to produce a sequence of timestamped events from a sequence of "seed" events.
(require '[causatum.event-streams :as es])
(es/event-stream model [{:state :a :rtime 0}])

;; Produces a random stream that might look like this:
[{:state :a, :rtime 0}
 {:state :b, :rtime 1, :delay [:constant 1], :weight 1}
 {:state :b, :rtime 2, :delay [:constant 1], :weight 3.14}
 {:state :c, :rtime 5, :delay [:constant 3], :weight 1}]

Motivation (aka "Huh?")

Imagine that you were asked to model user behavior on a website. You might need to do this in order to drive some sort of test. One obvious way to do it would be with a state machine, wherein each page on the site was a state, and a map described the legal transitions:

{:home #{:home :product}
 :product #{:product :home :shopping-cart}}

That's somewhat lacking, however, since it captures neither the time these events occur (important when generating load tests) nor their relative likelihoods. Something more like this might be better:

{:home    {:home          {:weight 1 :delay 10}
           :product       {:weight 2 :delay 20}}
 :product {:product       {:weight 1 :delay 30}
           :home          {:weight 2 :delay 40}
           :shopping-cart {:weight 4 :delay 15}}}

Even that isn't quite right, since the delays are fixed, but in the real world delays vary. Further, there's no real reason that a state like :home can't have more than one follow-on state, for instance to model an asynchronous event.

causatum attempts to provide a library with exactly those capabilities.

Usage

The main activity performed by causatum is the generation of streams of events. An event is a map with at least :state and :rtime keys. A state is usually a keyword, and identifies the type of event that has occurred. The "r" in "rtime" stands for "relative", and captures an offset from some arbitrary point in time at which the event occurred.

Event streams are generated from models. A model is concretely a map with at least a :graph key. The graph describes the possible states and the legal transitions between them, including relative likelihoods and the distribution of inter-event times. Here is a simple model that attempts to capture the idea of a user arriving at a website, visiting the home page, possibly moving on to a product page, and maybe a shopping cart page. They can go back to previous pages, and they might leave forever (:gone).

{:graph {:home    [{:home    {:weight 3
                              :delay [:constant 2]}
                    :product {:weight 1
                              :delay [:constant 3]}
                    :gone    {:weight 10}}]
         :product [{:home    {:weight 1
                              :delay [:random 10]}
                    :cart    {:weight 3
                              :delay [:constant 4]}
                    :gone    {:weight 2}}]
         :cart    [{:home    {:weight 4
                              :delay [:constant 17]}
                    :gone    {:weight 1}}
                   {:process-order {}}]}
 :delay-ops {:constant (fn [rtime n] n)
             :random   (fn [rtime n] (rand n))}}

The value of the :graph key is a map of outbound states to vectors of maps. One new event will be generated for each of the maps in those vectors. So in our example above, :home events will always produce one outbound events, but a :cart event will produce two outbound events.

Only one event is chosen (randomly, according to the weights) from each map of outbound events. Note that if only one state is present in the map, the :weight key is optional. So in our example, exactly one event of :home, :cart, or :gone will follow an event of type :product.

The delay between events is determined by the :delay key. If absent, it defaults to zero. If present, it must be a sequence whose first element is present in the :delay-ops map of the model. The value for that entry must be a function that takes at least the rtime of the precedent event. Any other elements in the delay vector will also be passed. The delay function returns a number, which is the delay between the preceding and the new event. Passing the rtime to the delay function allows for time-varying delays, such as might be present when modeling a site that has high load during certain times of the day, week, or year.

The primary function used to generate streams of events is causatum.event-streams/event-stream, which takes both a model and another event stream. This allows the output of one model to function as the input of another. This input can come from anywhere, however, and need not be the return value of another call to event-stream. For instance:

(def model {:graph {:a [{:b {:weight 1 :delay [:constant 1]}
                         :c {:weight 1}}]
                    :b [{:a {:weight 2 :delay [:constant 2]}
                         :c {:weight 1}}]}
            :delay-ops {:constant (fn [_ x] x)}})

(event-stream model [{:state :a :rtime 0}
                     {:state :b :rtime 1.5}])

;; =>
[{:state :a, :rtime 0}
 {:weight 1, :state :c, :rtime 0}
 {:state :b, :rtime 1.5}
 {:delay [:constant 2], :weight 2, :state :a, :rtime 3.5}
 {:delay [:constant 1], :weight 1, :state :b, :rtime 4.5}
 {:delay [:constant 1], :weight 1, :state :c, :rtime 4.5}]

Note that the output events are in increasing order of rtime, and that they may include some information from the state transition.

Here, we used a simple vector of events to seed the event stream. We could just have easily used an infinite stream of events:

(->> (event-stream model
                   (map (fn [rtime]
                          {:state :a :rtime rtime})
                        (iterate inc 0)))
     (drop 1000)
     (take 5))

;; =>

({:state :a, :rtime 325}
 {:state :a, :rtime 325, :delay [:constant 2], :weight 2}
 {:state :a, :rtime 326}
 {:state :b, :rtime 326, :delay [:constant 1], :weight 1}
 {:state :b, :rtime 326, :delay [:constant 1], :weight 1})

There are enormous benefits to representing models as pure data. Not least among these is that it becomes trivial to serialize and store them. It also becomes possible to manipulate models using the same techniques we apply to other data. Functions for manipulating models to iterate towards a goal are defined in the causatum.evolution namespace. For an interesting example of that code, see doc/evolution-example.clj.

Advanced Usage

Event Constructors

Models may contain a :event-ctor key. If present, its value must be a function: the event constructor. The event constructor will be invoked whenever an event is created, and will be passed the outbound event and the candidate successor event. The return value will be used as the actual successor event.

One handy use for event constructors is to propagate context information along a causal chain. For instance, we might seed a model of user behavior with an event stream representing user arrivals, and tag the user arrival events with an identifier. The event constructor could then propagate that user ID through all events in a causal chain. Say, something like this:

(def model {:graph {:home [{:home {:weight 1 :delay [:constant 1]}
                            :page1 {:weight 1 :delay [:constant 2]}
                            :gone {:weight 1}}]
                    :page1 [{:home {:weight 1 :delay [:constant 1]}
                             :page1 {:weight 1 :delay [:constant 2]}
                             :gone {:weight 1}}]}
            :delay-ops {:constant (fn [rtime delay] delay)}
            :event-ctor merge})

(->> (event-stream model
                   (map
                    ;; User ID is just their arrival time.
                    ;; A new user arrives at the home page
                    ;; every one second
                    (fn [rtime] {:state :home
                                 :rtime rtime
                                 :user-id rtime})
                    (iterate inc 0)))
     (drop 10000)
     (map #(select-keys % [:state :rtime :user-id]))
     (take 5))

;; =>

({:user-id 2559, :rtime 2559, :state :home}
 {:user-id 2557, :rtime 2559, :state :page1}
 {:user-id 2553, :rtime 2559, :state :page1}
 {:user-id 2552, :rtime 2559, :state :page1}
 {:user-id 2558, :rtime 2559, :state :home})

Of course, this is identical to what the default event constructor does, so there's really no need to call it out explicitly. But if you have other event creation logic you'd like to run at event generation time, you can do it here.

It's probably best to avoid using event constructors if at all possible, as any advanced usage of them moves important information about your model from data into code. Prefer to model the underlying process and then provide post-processing over the event stream when possible.

Support for event constructors may be removed in future releases of causatum. Feedback welcome.

License

Copyright © 2013 Craig Andera

Distributed under the Eclipse Public License, the same as Clojure.

More Repositories

1

shadowspawn

A Windows utility that mounts a shadow copy of the disk at a drive letter and then spawns an arbitrary command.
C++
184
star
2

hobocopy

An open source backup tool for Windows
C++
175
star
3

dynne

A Clojure library for working with audio.
Clojure
100
star
4

khordr

A keyboard chording application: supercharge your typing by pressing more than one key at once.
Clojure
34
star
5

reasoned-schemer

A project in which I work my way through a Clojure version of "The Reasoned Schemer"
Clojure
32
star
6

emacs

My emacs setup
Emacs Lisp
16
star
7

podcastifier

Process a pile of wav files into something like a final podcast episode.
Java
11
star
8

vmt

Virtual Mission Tools (VMT) for Falcon BMS. Examine the battlefield, generate weather, and more.
Clojure
8
star
9

show-keys.el

Display what keys are being typed in emacs in real-time.
Emacs Lisp
8
star
10

gherkin-mode

An emacs major mode for editing gherkin files.
Emacs Lisp
8
star
11

artifact

The Artifact board game
Clojure
8
star
12

aws-stats

A simple project for analyzing the log files that S3 produces. Written in Clojure.
Clojure
8
star
13

sudo

A very simple sudo for Windows
C#
7
star
14

weathergen

A program to randomly generate convincing weather for Falcon BMS.
JavaScript
6
star
15

falconpanel

Arduino program for turning physical switches, knobs, and buttons into a DirectX game controller.
C++
6
star
16

clojure-concurrency

A presentation on concurrency primitives in Clojure
4
star
17

doc-browse

Generates an HTML page describing a set of Clojure libraries
Clojure
4
star
18

cccp-emacs

An emacs client for the Common Collaborative Coding Protocol.
Emacs Lisp
4
star
19

HelloPhoneGapAndroid

A simple PhoneGap application making use of ClojureScript
JavaScript
3
star
20

lsobot

A carrier landing grading tool for Falcon BMS.
Clojure
3
star
21

kchordr

This project has moved. New home: https://github.com/candera/khordr
C#
3
star
22

stopwatch

A simple stopwatch application with count up and countdown functionality.
C#
3
star
23

reponomicon

A git server that stores its metadata in Datomic.
Clojure
2
star
24

cs-atom

Export a CommunityServer SQL Server database into Atom suitable for import into Blogger
Clojure
2
star
25

eliza-clj

A simple wrapper around a Java implementation of Eliza.
Java
2
star
26

geirrod

Web front end for GitHub issues providing better grouping and sorting
Clojure
2
star
27

clojure-data-structures

A presentation about the immutable, persistent collections in Clojure
CSS
2
star
28

HelloPhoneGapple

A simple PhoneGap application that shows how to integrate ClojureScript (iOS version)
JavaScript
2
star
29

explosion-man

A Clojure version of BomberMan.
Clojure
2
star
30

backup

My backup script
Shell
2
star
31

slideshow

Clojure slideshow app
Clojure
1
star
32

geirrod-rails

A Rails implementation of the geirrod issue tracking system.
Ruby
1
star
33

tpanl

iPhone/Smart Phone keypress relay.
C#
1
star
34

csv-bom-converter

Converts BOM output from Fusion 360 into a cutlist that CutList Optimizer can read
Clojure
1
star
35

3d-models

Miscellaneous 3D models
OpenSCAD
1
star
36

namespaces-symbols-vars

A presentation about namespaces, symbols, and vars in Clojure
JavaScript
1
star
37

trempature

Displays the current outdoor temperature in the tray.
C#
1
star
38

icfp-2017

Entry in the ICFP 2017 contest for Team "Drop Table Team"
Clojure
1
star
39

scripts

Various useful scripts
Shell
1
star
40

logos-play

Clojure
1
star
41

foobar-the-second

Test project
1
star
42

clojuredc-xml

Slides from my 2015-05-26 presentation at clojuredc.org
1
star
43

cccp-mode

An emacs plugin for the Common Colaborative Coding Protocol.
Emacs Lisp
1
star
44

hotelibot

A Slack bot
Clojure
1
star
45

swivelbot

A remote-controlled turntable
Clojure
1
star
46

strangeloop-2013-datomic

My 2013 Strange Loop presentation entitled "Real-World Datomic: An Experience Report"
CSS
1
star
47

kpanl

Clojure-powered application for building graphical panels on your mobile device to send keystokes to an application.
Clojure
1
star
48

timekeeper

A Clojure application that will beep to indicate a series of on-off cycles. I use it to time exercises - 30 seconds on, 20 seconds off, five repeats. That sort of thing.
Clojure
1
star