Modeling Dynamic Systems
Symmetry-breaking * Alloy tries to get rid of isomorphic instances * Can't catch them all (actually harder computationally than SAT)
Last time, things got interesting when we tried to define a CourseState
.
With CourseState.waitlist
, we saw the seq
keyword for the first time. Going back to the first week of class,
we saw util/ordering
and discussed its tradeoffs. With seq
, we have another set of tradeoffs, and these two ways
of ordering sets are appropriate for different situations.
Tim: What are some constraints to make sure the waitlist is actually a waitlist?
Student: There shouldn't be any intersection with the enrolled students.
We can use sig facts to more cleanly express facts about our sig.
sig CourseState {
...
} {
no waitlist.elems & enrolled
}
We didn't have to say someting like all cs : CourseState | <things with cs....>
Q: What is elems
Tim: You can think of it as a field that's defined for every sequence. It contains the set of elements of the sequence.
Q: Is it a relation?
Tim: It's a relation under the hood, but it's easier to think of as a field.
We can get an instance and see what's in these relations.
run { some cs : CourseState | some cs.enrolled } for 4
Notice the skolemization $cs
.
How many columns do we expect enrolled
to have?
Student: 2, from CourseState
to Student
How many columns should waitlist
have? The leftmost column should be CourseList
. When we show it in the evaluator,
it has three columns: CourseState->Int->Student
. Now, $cs.waitlist
gives us a function from an index to a student.
Things we can do:
CourseState$0.waitlist.first
to get the first element of the set
CourseState$0.waitlist[0]
to also get the first element.
Q: Does it maintain uniqueness also? Can we get Student$0
at position 0 and 2?
Tim: When we use util/ordering
, there are a couple constraints from it being a total ordering. We can't have duplicates
and we force everything to exist. A sequence gives us something weaker, so we can have duplicates. It also doesn't have
to contain everything. That means we can slice things off, like CourseState$0.waitlist.rest
. We get more
flexibility, but it comes with a performance hit.
Q: Can you intersect two sequences?
Tim: I can take their elems and intersect them, but it's not immediately clear how to preserve the ordering.
Sequences are not documented in the book. There's a reference page with everything you can do with them.
Now let's talk about changing the state, like enrolling in a course. We're going to use this transition predicate model.
``` pred enroll[pre : CourseState, s : Student, post : CourseState] { // We have a design choice about what to return for a student that's already enrolled. This one's sort of a // pre-condition, but the other constraints are more definitional. s not in pre.enrolled
// Conditions like this are called "framing conditions". If we don't specify, Alloy is free to give us instances
// where enrolling a student moves the course. In general, this is helpful because it helps us debug our intuition,
// but we really don't want it to here.
post.where = pre.where
// Thinking about the differences between Alloy and programming languages, could we write this as `pre.when = post.when`? Yes!
// `=` in Alloy is true equality, not assignment.
post.when = pre.when
pre.instructor = post.instructor
post.tas = pre.tas
post.id = pre.id
post.enrolled = pre.enrolled + s
// Alright, so what's up with the waitlist?
some pre.waitlist implies {
// You can always group code into an implicit and with these curly braces.
s = pre.waitlist.first
post.waitlist = pre.waitlist.rest
}
no pre.waitlist implies {
no post.waitlist
}
// With double implication like this, we have to make sure that our cases are mutually exclusive and exhaustive.
} ```
Q: Could you talk a little bit about the difference between brackets and parens?
Tim: It used to be that you could use brackets and parens interchangeably. They changed it so you have to use brackets in certain situations, and now I use parens only for grouping.
Q: Is this student enrolling from the waitlist or are they an arbitrary student?
Tim: Both! We're going to bake in support for both cases and branch the predicate. If the student's on the waitlist, there are some side effects that should happen.
Q: Without a cap, how do you know if someone needs to be put on the waitlist?
Tim: For now, we're going to completely sidestep that question! In modeler speak, we're going to abstract that out. It might come back and cause trouble later.
Q: What if you enroll a TA?
Tim: What should happen? Have you been in a class with an MTA enrolled? Or with a grad TA?
Q: Are you talking about a TA in general or a TA for the course?
Tim: I'm actually talking about this course. In the past, this very class has had (grad) TAs enrolled as students. For now, we won't specify what should happen and we'll wait to see the actual instances.
Q: Could you say pre.enrolled = post.enrolled - s
?
Tim: I think the answer is yes, and we could give it a test, but I'm worried about time.
Q: If you have a really big sig, is there a way to say that all but a few properties should be the same?
Tim: Unfortunately, Alloy doesn't have a way to specify "all else being equal".
Q: Couldn't you say that pre.waitlist
is a subset of post.waitlist
?
Tim: You could - there are a couple ways of getting at this. I like to put it as, "in this case, this happens".