A few beginner questions

Hey everyone I’ve just started playing around with Alloy and I have a few beginner questions.

Context

To start off with, let’s say I’m trying to model something near and dear to my heart (and liver): whiskey. I think about this as follows:

  • A bottle of whiskey is essentially its contents, so we’ll model the contents.
  • The contents of a whiskey is made up of a blend of one or more “maturations.”
  • A maturation is a relation between a distillate (the distilled liquid) and a barrel.
  • A distillate is a particular batch of liquid, and as such should only appear in a single barrel at a time.

At some point I’d like to model the fact that you can take the liquid out of one barrel, split it into different batches, mature each of those batches in separate barrels, then blend those batches together along with a completely different distillate that came from somewhere else. In short, the maturations that make up the contents of a whiskey form a directed acyclic graph. I want to get here eventually, but we’ll set this part aside for the moment.

The first pass I made at modeling the bulleted list above looks like this:

-- It's a barrel
sig Barrel {}

-- The distilled liquid
sig Distillate {}

-- A period of time that a distillate spent maturing in a particular barrel
sig Maturation {
	barrel: one Barrel,
	distillate: one Distillate,
}

-- Simplified contents of a bottle of whiskey
sig Contents {
	maturations: set Maturation
}

Workflow

As you can imagine, with no checks, facts, etc defined you get nonsense if you run this.

Question: Which parts of the model do you encode as facts, which do you encode as predicates, and which things do you check?

Seeing that the unconstrained model produces nonsense, it’s easy to identify a few constraints, such as:

  • A distillate should only be found in a single Maturation
  • A maturation should only be found in a single Contents
  • A Barrel should be found in some Maturation (barrels can be reused)

I’ve written these down as predicates:

pred distillate_found_in_maturation[d: Distillate] {
	one m: Maturation | m.distillate = d
}

pred barrel_found_in_maturation[b: Barrel] {
	some m: Maturation | m.barrel = b
}

pred maturation_found_in_contents[m: Maturation] {
	one c: Contents | m in c.maturations
}

pred no_orphans {
	all d: Distillate | distillate_found_in_maturation[d]
	all b: Barrel     | barrel_found_in_maturation[b]
	all m: Maturation | maturation_found_in_contents[m]
}

I’ve created a predicate no_orphans that tie these three other predicates together as well. I called this no_orphans because I first identified that without constraints you got distillates not found in maturations, maturations not found in contents, etc.

At this point do you just run { no_orphans } and inspect the visualization to see that it makes sense? Is there a more idiomatic way to say “these are the logical constraints I’ve identified, check that there are no logical errors”?

Visualizer

If I run the visualizer with the above predicates and run { no_orphans } I get the following result:

Question: What are the $barrel_found_in_maturation_m connections and how do I hide them? They obviously have a similar name to one of the predicates, so I suppose they’re related somehow, but they just seem to point the opposite direction of the maturation->barrel relation and generally clutter up the visualization.

DAG

Now let’s say we come back to wanting to model blending different batches of distillates. I think the way I’d model this is:

  • A distillate can be produced by blending multiple maturations
  • A maturation can be split into multiple distillates

So, I would model this as follows:

-- The distilled liquid
sig Distillate {
	blended_from: set Maturation
}

-- A period of time that a distillate spent maturing in a particular barrel
sig Maturation {
	barrel: one Barrel,
	distillate: one Distillate,
	split_into: set Distillate,
}

My first constraint would be to enforce that every Distillate split out from a maturation must then belong to a new Maturation. In pseudo-code that would look like:

for any Maturation m:
    for any Distillate d:
        if d in m.split_into:
            d belongs to some maturation

I’m not confident I know how to model that in Alloy, but this is what I came up with:

pred splits_belong_to_maturations {
	all disjoint m1, m2: Maturation |
    all d: Distillate |
        d in m1.split_into implies m2.distillate = d
}

Question: Is this how you would model that?

Next I’d want to model the constraint that there are no cycles i.e. no Distillate is split into a Maturation that will somehow be blended together to make the original Distillate.

This I have no idea how to do. My intuition says that there’s some way to accomplish this with one of the transitive closure operators. My best guess is something like:

pred no_cycles {
	all d: Distillate | no d & d.^(blended_from.distillate)
}

Question: How would you model this constraint?

The next one is that any Maturation in a blend should be a part of the set of Maturations that make up a Contents:

pred all_blend_components_belong_to_contents {
    all c: Contents   |
    all m: Maturation |
		m.distillate.blended_from in c.maturations
}

The last bit I want to model is that it doesn’t make sense for a distillate to be “blended” from a single Maturation i.e. Distillate.blended_from should have a cardinality of 0 or >2. This seems like something simple to model but I don’t actually know how you access the cardinality of a set in Alloy.

Question: How do you constrain the number of atoms in a signature to specific values e.g. 0 and >2?

Wrap up

That ended up being a small novel. If you read this far you’re a saint and I appreciate your help. I’m trying to learn Alloy so that I can slowly introduce it to friends and coworkers.

Hi,

You should use facts to specify your assumptions and checks to verify properties that should be entailed by those assumptions. For example, checks can be used to detect redundant assumptions. Predicates are just reusable (parametrised) formulas to be used somewhere else. I believe your predicate no_orphans should be a fact in this example, because the properties specified in that predicate are assumptions that should always hold. To get any valid instance that satisfies your assumptions (specified in facts) you can use an empty run command.

When you have an assumption some x : A | P the Analyzer will show you which particular A in the instance made this property true (the witness to the existencial quantifier). That A will be named $x. If the assumption is inside some predicate this name of the witness will also include the predicate name. Likewise, if you have an assumption all x : A | some y : B | P the Analyzer will show you which B made this property true for every possible A. The witness is now a binary relation that associates each A with a B and will be named $y. In the theme customisation panel these witness relations also appear and you can choose to hide them.

To help with the remaining questions I need to know more about whiskey, because I’m a bit confused with your model. In particular, your constraints entail that there is a one to one relationship between destillates and maturations. If that is the case then there is no need to have both in the model, as they are indistinguishable. However, I find that a bit strange: can’t the same destillate be matured for different periods of time? That is, should’t a destillate be allowed to appear in more than one maturation? I also find the choice of name Contents a bit confusing - why not Blend? And why can’t we use the same maturation in more than one blend?

Best,
Alcino

Thanks for your response :slight_smile: the help is much appreciated.

I also find the choice of name Contents a bit confusing - why not Blend?

The contents aren’t necessarily a blend, they could be the result of a single maturation, which is why I call it Contents rather than Blend (it wouldn’t make sense to have a blend with a single component).

I think another piece of context I failed to properly communicate is that a single Contents represents a particular product, so you’d have a Contents for Glenlivet 12, but you wouldn’t have a separate Contents for each bottle of Glenlivet 12.

In particular, your constraints entail that there is a one to one relationship between destillates and maturations. If that is the case then there is no need to have both in the model, as they are indistinguishable.

I think it depends on what you mean by “one to one relationship”. A Maturation contains a Distillate, but a Maturation is not itself a Distillate. A Maturation should only ever contain a single Distillate, though. It’s entirely possible that there’s a bug in my model that doesn’t accurately reflect all of this.

A Maturation is composed of distinct pieces of information, for example: the distillate, the barrel, and the duration the distillate was in the barrel. Each of these three pieces of information are simplified in the model I presented, and the Maturation itself could also have more information associated with it, like a location (different climates affect the maturation process). I left off a duration field from the Maturation signature by mistake, which may have added to your confusion.

However, I find that a bit strange: can’t the same destillate be matured for different periods of time? That is, should’t a destillate be allowed to appear in more than one maturation?

I think a central question I don’t have a great answer for is how to represent two different Maturations that contain identical distillate that was split off from a parent distillate. In other words if I empty a barrel and split the contents into two batches, should those batches be the same Distillate atom since they have identical properties, or should I have one for atom for each since they’re different physical batches of liquid?

I was trying to encode a “conservation of mass” constraint in the sense that if I split a parent distillate into two child distillates I would get two new Distillate atoms to reflect that these are two different physical batches of liquid. I want there to be some record that X% of Distillate A became Distillate B and Y% became Distillate C. I’ve left off those fractions for simplicity here, but that’s something I’d like to keep track of since that will be useful information when it comes time to represent the components of a blend.

And why can’t we use the same maturation in more than one blend?

It depends on what you mean by “blend” here. If you mean Contents, then that’s just an assertion that I’m making. In essence the distillates that make up one product shouldn’t appear in another product.

If by “blend” you mean “A Distillate constructed from other Distillates” 1) it’s important to note that it’s the Distillates that are blended, not the Maturation, and 2) that goes back to the previous question of whether splitting a Distillate into N batches creates N new Distillate atoms.

If splitting a Distillate creates N atoms, one for each physical batch of liquid, then one of these child distillates shouldn’t appear in more than one Maturation because you can’t put the same physical batch of liquid in more than one barrel at a time.

Let’s call it Contents then.

Understood. That’s the reason why Contents is not the same as a bottle.

You can have models for both interpretations of Destillate, but the one that allows you to encode the “conservation of mass” would be more complex to do in Alloy. For the moment I would focus on getting the simpler interpretation right, namely the one where Destillate means parent destillate. This would imply relaxing the distillate_found_in_maturation constraint to have a some instead of a one.

I meant Contents and it’s now clear that the constraint that one maturation appears exactly in one contents is just your assumption, which is fine.

Following the intepretation that Destillate means parent destillate, I think that blended_from and split_into are redundant, in the sense that the latter contains exactly the same information of the former, just from the perspective of the maturations. In Alloy we would have blended_from = ~split_into (~ is the converse operator, that reverses all tuples of a relation). As such, I would get rid of the split_into relation, unless you want to keep it to make some constraints more readable.

You can use the # (cardinality) operator to determine the size of a set. You constraint would be all d : Destillate | no d.blended_from or #d.blended_from > 1 (you mean >1 not >2 right?).

This constraint looks fine to me.

This one does not look fine to me, because you are requiring the maturations in all blends to belong to the maturations of all contents, which is not what you want. I believe the correct formulation would be:

pred all_blend_components_belong_to_contents {
    all c: Contents  | c.maturations.distillate.blended_from in c.maturations
}

Best,
Alcino

Thanks for your help, this is what I have now:

-------------------------------------------------------------------------------
-- Entities
-------------------------------------------------------------------------------

-- It's a barrel
sig Barrel {}

-- The distilled liquid, unique to a Contents
sig Distillate {
	parents: set Maturation
}

-- A period of time that a distillate spent maturing in a particular barrel
sig Maturation {
	barrel: one Barrel,
	distillate: one Distillate,
	duration: one Duration,
}
sig Duration {}

-- Simplified contents of a bottle of whiskey
sig Contents {
	maturations: set Maturation
}

-------------------------------------------------------------------------------
-- Facts: no orphan entities
-------------------------------------------------------------------------------

fact no_orphan_maturations {
	all m: Maturation | m in Contents.maturations
}
fact no_orphan_distillates {
	all d: Distillate | d in Maturation.distillate
}
fact no_orphan_barrels {
	all b: Barrel | b in Maturation.barrel
}
fact contents_have_at_least_one_maturation {
	all c: Contents | #(c.maturations) > 0
}

-------------------------------------------------------------------------------
-- Facts: blending
-------------------------------------------------------------------------------

fact maturation_belongs_to_one_contents {
	all m: Maturation | one c: Contents | m in c.maturations
}
fact distillate_belongs_to_one_contents {
	all d: Distillate | one c: Contents | d in c.maturations.distillate
}
fact barrel_belongs_to_one_contents {
	all b: Barrel | one c: Contents | b in c.maturations.barrel
}
fact no_cycles {
	all d: Distillate | no d & d.^(parents.distillate)
}
fact parent_maturation_not_in_other_contents {
	all c: Contents | c.maturations.distillate.parents in c.maturations
}

-------------------------------------------------------------------------------
-- Commands
-------------------------------------------------------------------------------

check no_sharing {
	all disj c1, c2: Contents |
		(no c1.maturations & c2.maturations) and
		(no c1.maturations.distillate & c2.maturations.distillate)
}

run can_find_example {} for 5 but exactly 3 Contents

I only have one check at this point and it feels pretty redundant since it’s more or less checking that the facts about orphans hold. I think at this stage I have a better hold on the modeling part of the workflow, but I don’t yet have a good handle on the validation part of the workflow. What would I check that isn’t already in a fact? As you discover things that you want to check do you first write them as a check, find any inconsistencies in the model, fix that (potentially adding facts to tighten up the model), and then delete the check if it’s redundant with a fact you just created?