enum Status { unissued, issued, cancelled }
let unchanged[s,r] = all x : s | x.(r)'=x.(r)
sig Card { var status : Status }
sig Customer { var cards: disj some Card }
enum Operation {Cancel, Stutter}
fun _cancel : Operation -> Card -> Card {
Cancel -> {card1: Card, card2 : Card | cancel[card1, card2] }
}
fun _no_change : Operation {
{ s : Stutter | no_change}
}
fun events : set Operation {
(_cancel).Card.Card + _no_change
}
pred cancel[card : Card, repl : Card] {
card.status = issued
repl.status = unissued
let customer = cards.(card) {
customer.cards' = customer.cards - card + repl
card.status' = cancelled
repl.status' = issued
unchanged[Card - repl - card, status]
unchanged[Customer - customer, cards]
}
}
pred traces {
Customer.cards.status = issued
(Card - Customer.cards).status = unissued
once no_change -- to make sure a Stutter event occurs
always (no_change or (one c1: Card | one c2 : Card | cancel[c1, c2] ))
}
pred no_change {
unchanged[Card, status]
unchanged[Customer, cards]
}
But I want to have the order of events, for example I want to create a predicate saying “if ever a stutter event has occured in the previous steps”. I tried the following :
pred events_ever_stutter {
historically (Stutter in events.prevs )
}
run {
traces
eventually (# status.cancelled = 3) and events_ever_stutter
} for 6 but exactly 2 Customer
It won’t find an instance for Stutter in this model, however, it will find an instance when I write for Cancel:
historically (Cancel in events.prevs )
I am not sure if the method is correct, but how can I do this for Stutter?
Operation is an enum so it’s ordered, but this order is arbitrary, it doesn’t correspond to anything happening temporally.
Writing some and disj for the multiplicity of cards is (in general) risky: by imposing these facts, you rule out some behaviors that the state machine (implemented by cancel and no_change) may in principle allow, It’s better to just put setand then check that it’s in fact disj some in all runs.
quoting the reference manual, once F is true in state i iff F is true in some state ≤ i. once no_change is stated in the initial state (no previous state) so you’re imposing no_change to happen in the first step of any trace.
using one is once again risky: you’re imposing constraints on events in all traces instead of checking that they hold for any trace.
Thank you very much for the comments, they are very useful.
I ended up something like below in the end. I actually wanted the ‘events’ could be navigated using a Customer, but I am still working on to get ‘one event was strictly happened before another event and there is no other event in between’. Something like a->b->c, I know b happened before c and there is no other event in between.
enum Status { unissued, issued, cancelled }
let unchanged[s,r] = all x : s | x.(r)'=x.(r)
sig Card { var status : Status }
sig Customer { var cards: set Card }
enum Operation {Add, Cancel, Stutter}
fun _cancel : Operation -> Customer -> Card -> Card {
Cancel -> {c: Customer, card1: Card, card2 : Card | c.cancel[card1, card2] }
}
fun _add : Operation -> Customer -> Card {
Add -> {c : Customer , card : Card | c.add[card] }
}
fun _no_change : Operation {
{ s : Stutter | no_change}
}
fun events[c : Customer] : set Operation {
(_cancel).Card.Card.c + (_add).Card.c + _no_change
}
pred Customer::cancel[card : Card, repl : Card] {
card.status = issued
repl.status = unissued
this.cards' = this.cards - card + repl
card.status' = cancelled
repl.status' = issued
unchanged[Card - repl - card, status]
unchanged[Customer - this, cards]
}
pred Customer::add[ card : Card] {
card.status = unissued
this.cards' = this.cards + card
card.status' = issued
unchanged[Card-card, status]
unchanged[Customer-this, cards]
}
pred traces {
#Customer.cards = 0
(Card - Customer.cards).status = unissued
always (no_change or ( one c : Customer | one c1 : c.cards, c2 : Card | c.cancel[c1, c2] ) or (one c : Customer | one c3: Card | c.add[c3] ) )
}
pred no_change {
unchanged[Card, status]
unchanged[Customer, cards]
}
pred events_ever_canceled[c: Customer] {
Cancel in c.events
}
pred disj_cards [c1: Customer]{
c1.cards !in (Customer-c1).cards
}
run {
traces
eventually (one c1, c2 : Customer | #c1.cards > 2 and events_ever_canceled[c1] and disj_cards[c1] and #c2.cards = 2 and c2.cards.status = issued )
} for 6 but exactly 2 Customer
“Philosophically”, I think expressing strict and precise ordering constraints is leaving the realm of temporal specification; for the most part, temporal spec shouldn’t be a step-by-step thing.
Technically now, expressing very precise ordering constraints in LTL can be difficult, if you have several states or events. You may rule out far too many possible runs. E.g. if you extend your spec, you may realize that it’s ok for event B not too precede event C just before, but possibly also if some events D or E happened… but D must be before E… and so on and so forth.
Anyway, here, you’re describing a precedence constraint which is easily described with before: B precedes C = always (C implies before B).
To learn/reflect more on spec with LTL, you may refer to the work of Dwyer et al on temporal specification patterns. I don’t think they considered past-time LTL but it’s very handy. Cabrera et al have extended (and fixed some of) Dwyer et al’s work.