Skip to content

Continuous Simulation Single Server System Example

Colin Kahn edited this page Sep 19, 2018 · 2 revisions

simulation-graph

A simple machine simulation where jobs arrive, are loaded by an idle machine, processed, and unloaded. This example uses continuous simulation using clojure.core.async.

{:deps
 {org.clojure/clojurescript {:mvn/version "1.10.238"}
  org.clojars.protocol55/step  {:mvn/version "0.2.0"}
  org.clojure/core.async {:mvn/version "0.4.474"}
  fsmviz {:mvn/version "0.1.3"}}}
(ns simulation.core
  (:require [clojure.spec.alpha :as s]
            [clojure.core.async :as a]
            [fsmviz.core :as fsmviz]
            [protocol55.step.alpha :refer [stepdef]]))

(s/def ::initialize #{:initialize})
(s/def ::arrive #{:arrive})
(s/def ::load #{:load})
(s/def ::unload #{:unload})

(s/def ::m #{0 1})
(s/def ::q (s/int-in 0 1e9))
(s/def ::state (s/keys :req-un [::m ::q]))

(s/def :buffer-empty/q #{0})
(s/def :some-buffer/q (s/int-in 1 1e9))
(s/def :idle/m #{1})
(s/def :busy/m #{0})

(s/def :empty/state (s/and map? empty?))
(s/def :idle/state (s/keys :req-un [:idle/m :some-buffer/q]))
(s/def :idle.buffer-empty/state (s/keys :req-un [:idle/m :buffer-empty/q]))
(s/def :busy/state (s/keys :req-un [:busy/m :some-buffer/q]))
(s/def :busy.buffer-empty/state (s/keys :req-un [:busy/m :buffer-empty/q]))

(stepdef ::step
  {:data-def step-data}
  (:empty/state
    ::initialize (:idle/state))
  (:idle/state
    ::arrive (:idle/state)
    ::load (:busy/state :busy.buffer-empty/state))
  (:idle.buffer-empty/state
    ::arrive (:idle/state))
  (:busy/state
    ::arrive (:busy/state)
    ::unload (:busy/state :idle/state :idle.buffer-empty/state))
  (:busy.buffer-empty/state
    ::arrive (:busy/state)
    ::unload (:idle.buffer-empty/state)))

(def ta 500)
(def ts 200)

(defn idle? [{:keys [m]}]
  (> m 0))

(defn buffer-empty? [{:keys [q]}]
  (zero? q))

(def buffer-not-empty? (complement buffer-empty?))

(defn dispatch*
  [state action]
  (case action
    :initialize
    {:m 1 :q 3}

    :arrive
    (update state :q inc)

    :load
    (-> state
        (update :m dec)
        (update :q dec))

    :unload
    (update state :m inc)))

(defn dispatch
  [state action]
  (let [state' (dispatch* state action)
        step [state action state']]
    (assert (s/valid? ::step step)
            (str (s/explain-str ::step step) " " (pr-str step)))
    (println
      (let [[state [action [state']]] (s/conform ::step step)]
        [state action state']))
    state'))

(defn work [state]
  (a/go-loop []
    (if (buffer-not-empty? @state)
      (do
        (swap! state dispatch :load)
        (a/<! (a/timeout ts))
        (swap! state dispatch :unload)
        (recur)))))

(defn run []
  (let [state (atom {})]
    (swap! state dispatch :initialize)

    (a/go-loop []
      (swap! state dispatch :arrive)
      (when (idle? @state)
        (work state))
      (a/<! (a/timeout ta))
      (recur))))

(defn graphify-state [k]
  (->> (clojure.string/split (namespace k) #"[\.-]")
       (map clojure.string/capitalize)
       (clojure.string/join)))

(defn generate-image! []
  (fsmviz/generate-image
    (mapcat
      (fn [[state m]]
        (mapcat
          (fn [[action states']]
            (map #(mapv keyword %&)
                 (repeat (graphify-state state))
                 (repeat (name action))
                 (map graphify-state states')))
          m))
      step-data)
    "simulation-graph"))

(defn -main [& _]
  (println "Generating graph image...")
  (generate-image!)
  (println "Running simulation...")
  (run)
  (while true nil))

We can run our simulation using the clj cli like so:

clj -m simulation.core

Doing so will give us the following output:

Generating graph image...
Running simulation...
[:empty :initialize :idle]
[:idle :arrive :idle]
[:idle :load :busy]
[:busy :unload :idle]
[:idle :load :busy]
[:busy :unload :idle]
[:idle :load :busy]
[:busy :arrive :busy]
[:busy :unload :idle]
[:idle :load :busy]
[:busy :unload :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :arrive :busy]
[:busy :unload :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
[:idle.buffer-empty :arrive :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
[:idle.buffer-empty :arrive :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
[:idle.buffer-empty :arrive :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
[:idle.buffer-empty :arrive :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
[:idle.buffer-empty :arrive :idle]
[:idle :load :busy.buffer-empty]
[:busy.buffer-empty :unload :idle.buffer-empty]
...

Clone this wiki locally