ExTLA.Set (ExTLA v0.6.0)

Copy Markdown View Source

Set operations corresponding to TLA+ set theory operators. Builds on Elixir's MapSet with additional TLA+ operations.

Summary

Functions

CHOOSE x ∈ S : P(x) — deterministic selection.

S1 \X S2 — Cartesian product. The set of all tuples {a, b} where a ∈ S1 and b ∈ S2.

N-ary Cartesian product: S1 \X S2 \X ... \X Sn.

UNION S — distributed union. Flattens a set of sets.

\E x \in S : P(x) — check if the predicate holds for at least one element.

Set filtering: {x ∈ S : P(x)}.

\A x \in S : P(x) — check if the predicate holds for all elements.

Set mapping: {f(x) : x ∈ S}.

SUBSET S — the power set (set of all subsets).

Integer range as a MapSet: a..b.

Functions

choose(set, predicate \\ fn _ -> true end)

CHOOSE x ∈ S : P(x) — deterministic selection.

Returns the least satisfying element in Erlang term order. TLA+ requires only that CHOOSE be a function of its arguments, not that it pick any particular element, so any total order will do — but it must be an order that does not depend on how the set was built or which OTP release is running. Enumeration order is not such an order, and a CHOOSE that varied between runs would make the reachable state graph, and every result read from it, vary between runs.

Raises ArgumentError if no element satisfies the predicate; CHOOSE over an empty set is undefined in TLA+ and silently returning nil would put an unspecified value into a state variable.

iex> ExTLA.Set.choose(MapSet.new([3, 1, 2]))
1

iex> ExTLA.Set.choose(MapSet.new([3, 1, 2]), &(&1 > 1))
2

cross(s1, s2)

S1 \X S2 — Cartesian product. The set of all tuples {a, b} where a ∈ S1 and b ∈ S2.

iex> ExTLA.Set.cross(MapSet.new([1, 2]), MapSet.new([:a]))
MapSet.new([{1, :a}, {2, :a}])

cross_many(sets)

N-ary Cartesian product: S1 \X S2 \X ... \X Sn.

The product of no sets is the set containing the empty tuple, not the empty set. The empty tuple is the identity of the product: appending the factors one at a time has to start from one empty combination, or nothing is ever built. The empty set is what any empty factor gives.

This is the independent product, a fixed list of sets. ExTLA.ModelChecker.bindings/3 is a different operation: each parameter's values may depend on the state and on the parameters bound before it.

iex> ExTLA.Set.cross_many([MapSet.new([1, 2]), MapSet.new([:a, :b])]) |> MapSet.size()
4

iex> ExTLA.Set.cross_many([])
MapSet.new([{}])

distributed_union(set_of_sets)

UNION S — distributed union. Flattens a set of sets.

iex> ExTLA.Set.distributed_union(MapSet.new([MapSet.new([1, 2]), MapSet.new([2, 3])]))
MapSet.new([1, 2, 3])

exists?(set, predicate)

\E x \in S : P(x) — check if the predicate holds for at least one element.

False for the empty set.

iex> ExTLA.Set.exists?(MapSet.new([1, 2]), &(&1 > 1))
true

filter(set, predicate)

Set filtering: {x ∈ S : P(x)}.

iex> ExTLA.Set.filter(MapSet.new([1, 2, 3, 4]), &(rem(&1, 2) == 0))
MapSet.new([2, 4])

for_all?(set, predicate)

\A x \in S : P(x) — check if the predicate holds for all elements.

Vacuously true for the empty set.

iex> ExTLA.Set.for_all?(MapSet.new([2, 4]), &(rem(&1, 2) == 0))
true

map(set, fun)

Set mapping: {f(x) : x ∈ S}.

The result may be smaller than the input — a non-injective f collapses elements, exactly as in TLA+.

iex> ExTLA.Set.map(MapSet.new([1, 2, 3]), &rem(&1, 2))
MapSet.new([0, 1])

powerset(set)

SUBSET S — the power set (set of all subsets).

Warning: exponential in |S|. Use with small sets only — a 20-element set has a million subsets, and each one is a MapSet.

iex> ExTLA.Set.powerset(MapSet.new([1, 2])) |> Enum.sort_by(&MapSet.size/1)
[MapSet.new([]), MapSet.new([1]), MapSet.new([2]), MapSet.new([1, 2])]

range(a, b)

Integer range as a MapSet: a..b.

Empty when a > b, as in TLA+.

iex> ExTLA.Set.range(1, 3)
MapSet.new([1, 2, 3])

iex> ExTLA.Set.range(3, 1)
MapSet.new([])