Exercises
Exercise 1: Turnstile / access-gate automaton (≈30 min)
Goal: practice extracting the formal tuple G = \{\mathcal X, \mathcal X_0, \mathcal E, \mathcal F\} (and optionally \mathcal X_\mathrm{m}) from an informal description, and draw the state-transition diagram.
Part A (pen and paper)
A subway turnstile starts locked. Inserting a valid coin unlocks it. A person then either pushes through (relocking the turnstile) or, if nobody pushes within some time, an attendant can reset it back to locked without anyone passing (model this as an ordinary untimed event for now — ignore the timing aspect). If a coin is inserted while already unlocked, the turnstile simply ejects it (no state change — add a self-loop or explicitly state that the event is not active in that state).
- Identify \mathcal X, \mathcal X_0, \mathcal E, and write out \mathcal F as a set of triples (or as the transition function f).
- Decide whether
coinwhile unlocked should appear in \mathcal F at all, or whether it is simply outside \Gamma(\text{unlocked}) (the active event set). Justify your choice. - Draw the digraph (nodes, edges, labels). Mark the initial state.
- Is
lockeda reasonable marked/accepting state \mathcal X_\mathrm{m}? What language would that correspond to (informally — sequences of events that bring the turnstile back to locked)? - Identify the active event function \Gamma(x) for each state.
Part B (computer implementation, choose one)
- Stateflow: Build the turnstile as a chart with two states,
lockedandunlocked. Use two input “events” (Boolean triggers or Stateflow events)coinandpush, and implement the self-loop/reject behavior forcoininunlocked. Add a counter (extended state, e.g., integern_passengers) that increments on every successfulpush, to connect to the “extended-state automaton” concept from the lecture. - Julia/MATLAB (low-level, no GUI): Represent the automaton exactly as in the lecture’s Dijkstra example: a struct holding the state, and a function
update(state, event) -> new_stateimplementing f directly from your Part A transition set. Write a short driver script that feeds in a sequence of events (e.g.,[:coin, :push, :coin, :coin, :push]) and prints the resulting path (sequence of visited states), mirroring “Path of an automaton” from the notes.
Part C — Optional extension (if a group finishes early): closer to a real fare gate. Real subway/turnstile gates are usually built this way for good reason, so the extension below is not artificial — it reflects how such gates are actually specified:
- Real gates have a passage sensor (an infrared beam or pressure pad), separate from the mechanical unlocking. This means
pushshould really be split into two distinct events:enter(sensor detects someone stepping into the gate) andexit(sensor detects them leaving the gate area), since some gates re-lock only after the person has fully passed through, not the instant they push. - Add an
alarmevent/output: if the sensor detects someone entering while the gate is stilllocked(fare evasion / jumping the gate), the automaton should go to a new state, saybreach, and emit an alarm, rather than silently ignoring the event as in the basic version. - Optionally add a
faultstate reachable from any state on amotor_jamevent, with areset_by_technicianevent returning tolocked— gates do jam, and this is exactly the kind of state a maintenance technician needs represented separately fromlocked/unlocked.
Redo Part A.1–A.5 for this extended version (it will now have 4–5 states instead of 2), and, time permitting, extend your Part B implementation accordingly. This version is also a nicer setup for revisiting later in the course when timed automata are introduced (e.g., “alarm if breach is not cleared within 5 seconds”).
Exercise 2: Vending machine with two products and a Moore output (≈30 min)
Goal: practice modeling with marked states, recognize when a Moore-machine (state-labeled output) view is natural, and implement a slightly larger automaton (more states/events) with explicit guard-like reasoning on an extended integer variable.
Part A (pen and paper)
Design (don’t just look up) a state automaton for a simplified snack vending machine that accepts only 5- and 10-crown coins (events insert5, insert10), sells a single snack for exactly 15 crowns, gives no change, and has a cancel button that returns to the start at any point before purchase (refund not modeled — just state reset).
- Decide whether to model this with a plain automaton where each state is an explicit “amount inserted so far” (0, 5, 10) or with an extended-state automaton (a single state
idle/vendingplus an integer variablecredit). Justify your choice and give the definition both ways if time allows, to compare scalability — this connects directly to “State as the value of a state variable” in the notes. - Write \mathcal X, \mathcal X_0, \mathcal E, and \mathcal F (or the guarded transitions, if you picked the extended-state version).
- Add a Moore-style output: label each state with
NO SNACKorDISPENSE, analogous to the valve-control Moore machine example. Where exactly is the output produced — on entering the state, or during the transition? Contrast this with what a Mealy-machine version (output on the transition, e.g.,insert5 / no_snack) would look like for the same machine. - Draw the digraph for whichever version (plain or extended) you find clearer to draw.
Part B (computer implementation, choose one)
- Stateflow: Implement the chart using an extended state variable
credit(data of type integer local to the chart) and guarded transitions of the form[credit + 5 >= 15] / {credit = 0; dispense();}, closely following the “Counting up to 10” extended-state example from the lecture. Use a Stateflow output port or a logged signal for the Moore-style output (NO SNACK/DISPENSE). - Julia/MATLAB (low-level): Implement a function
f(x, e)(orupdate!) operating on a small struct/state holding the mode and thecreditvariable, plus a separateoutput(x)function returning"NO SNACK"or"DISPENSE", mirroring theupdate!/outputsplit used in the Dijkstra example in the notes. Simulate a sequence of coin insertions and a cancel, and print the resulting trace of (state, output) pairs.
Exercise 3: Composing two automata + blocking analysis (≈30 min)
Goal: practice the concept of composing automata via shared/synchronized events (the !/? convention from the notes), and identify blocking, deadlock, and livelock states — a topic not covered by either demo example.
Part A (pen and paper)
Consider two simple automata that must be composed:
- Producer G_1: states
idle,producing,done; it cyclesidle —start→ producing —finish!→ idle, wherefinish!is a generated (sent) event signaling that an item is ready. - Buffer G_2: a one-slot buffer with states
empty,full; transitionempty —finish?→ full(receiving the item) andfull —take→ empty(a consumer removing it). Assume the consumer’stakeevent is not synchronized with anything else for this exercise (free event).
- Write out \mathcal X, \mathcal X_0, \mathcal E, \mathcal F for G_1 and G_2 separately, and draw each digraph.
- Form the composed (product) automaton G = G_1 \| G_2 by synchronizing on the shared event
finish(i.e., a transition in the product happens only when both automata’sfinish!/finish?fire together; all other events occur independently). List the resulting product states (pairs) and transitions, and draw the composed digraph. (Hint: not all of the 3×2 = 6 combinations need be reachable — find \mathcal X_0 first and explore outward.) - Now suppose the consumer’s
takeevent is accidentally removed from the model (forgotten event). Re-examine the composed automaton: is there now a blocking state? Is it a deadlock (no outgoing transition at all) or a livelock (the system loops forever among non-marked states without ever reaching a marked one)? Pick a marked state \mathcal X_\mathrm{m} (e.g.,(idle, empty)) to make this precise, and justify your classification using the definitions from the lecture.
Part B (computer implementation, choose one)
Stateflow: Implement G_1 and G_2 as two parallel (AND) states or as two separate charts communicating via a Stateflow event/signal representing
finish, similar in spirit to the synchronized-automata figure in the notes. Simulate and observe whether removing thetaketransition (as in Part A.3) causes the buffer chart to visibly stall.Julia/MATLAB (low-level): Implement each automaton as its own transition function, then write a small composition driver: at each step, check whether the producer wants to emit
finish!and step the buffer’sfinish?transition only when that happens; steptakeindependently/randomly. Run the simulation for the “broken” version withtakeremoved and confirm in code (e.g., by checking that the buffer state never changes again after the firstfinish) that it deadlocks. Print the full path of composed states to support your conclusion.Suggested primary pattern (synchronous, single-threaded — matches the Dijkstra style in the notes): keep a
Composedstruct holding both sub-states, and write onestepfunction where the shared event is derived rather than supplied as an input — it fires automatically and only when both sides are simultaneously enabled:struct Composed producer_state::Symbol # :idle or :producing buffer_state::Symbol # :empty or :full end function step(c::Composed; start::Bool=false, take::Bool=false) p, b = c.producer_state, c.buffer_state finish_fires = (p == :producing) && (b == :empty) # the synchronization condition p_next = finish_fires ? :idle : (p == :idle && start) ? :producing : p b_next = finish_fires ? :full : (b == :full && take) ? :empty : b return Composed(p_next, b_next) endDeadlock in the broken (
take-removed) variant is then trivial to detect in code: step in a loop and check whetherstep(c) == c— no change means the system is stuck, and you can print which automaton is the blocked one.Optional “idiomatic Julia” bonus, for groups that finish early: Julia has language-level primitives for exactly this kind of synchronization — an unbuffered
Channelis a synchronous rendezvous (put!blocks until a matchingtake!), which is thefinish!/finish?pair rather than a simulation of it. Run each automaton as its ownTaskvia@async:function producer(ch::Channel, n::Int) for i in 1:n println("producing...") put!(ch, :item) # blocks until the buffer calls take! -- this *is* finish!/finish? println("idle") end close(ch) end function buffer(ch::Channel) for _item in ch # take!, looping until ch is closed println("full") sleep(0.01) # the independent `take` event println("empty") end end ch = Channel{Symbol}(0) # capacity 0 = unbuffered = synchronous rendezvous t1 = @async producer(ch, 3) t2 = @async buffer(ch) wait(t1); wait(t2)Caution if you try this for the broken variant: removing the consumer’s
takedoesn’t give you an inspectable deadlocked state the way the synchronous version does —put!just blocks forever andwaitnever returns. That’s a faithful illustration of what deadlock means for real concurrent processes, but it’s the synchronous version above that you should use to actually answer question A.3.