What is the inductive invariant of the simple concurrent program? - concurrency

Here is a simple concurrent program from the article Teaching Concurrency by Leslie Lamport.
Consider N processes numbered from 0 through N-1 in which each process i executes
x[i] := 1
y[i] := x[(i - 1) % N]
and stops, where each x[i] initially equals 0. (The reads and writes of each x[i] are assumed to be atomic.)
This algorithm satisfies the following property: after every process has stopped, y[i] equals 1 for at least
one process i. It is easy to verify: The last process i to write y[i] must
set it to 1.
Then, Lamport remarks that
But that process doesn't set y[i] to 1 because it was the last process to write y.
The algorithm satisfies this property because it maintains an inductive invariant. Do you know what that invariant is? If not, then you do not completely understand why the algorithm satisfies this property.
Therefore,
What is the inductive invariant of the concurrent program?

TL;DR
An inductive invariant of this program, in TLA+ syntax, is:
/\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
/\ (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1
What is an inductive invariant?
An inductive invariant is an invariant that satisfies the following two
conditions:
Init => Inv
Inv /\ Next => Inv'
where:
Inv is the inductive invariant
Init is the predicate that describes the initial state
Next is the predicate that describes state transitions.
Why use inductive invariants?
Note that an inductive invariant is only about current state and next state. It
makes no references to the execution history,
it's not about the past behavior of the system.
In section 7.2.1 of Principles and Specifications of Concurrent Systems
(generally known as The TLA+ Hyperbook),
Lamport describes why he prefers using inductive invariants over behavioral
proofs (i.e., those that make reference to execution history).
Behavioral proofs can be made more formal, but I don’t know any practical way
to make them completely formal—that is, to write executable descriptions of
real algorithms and formal behavioral proofs that they satisfy correctness
properties. This is one reason why, in more than 35 years of writing
concurrent algorithms, I have found behavioral reasoning to be unreliable for
more complicated algorithms. I believe another reason to be that behavioral
proofs are inherently more complex than state-based ones for sufficiently
complex algorithms. This leads people to write less rigorous behavioral
proofs for those algorithms—especially with no completely formal proofs to
serve as guideposts.
To avoid mistakes, we have to think in terms of states,
not in terms of executions... Still, behavioral reasoning provides a
different way of thinking about an algorithm, and thinking is always helpful.
Behavioral reasoning is bad only if it is used instead of state-based
reasoning rather than in addition to it.
Some preliminaries
The property we are interested in proving is (in TLA+ syntax):
(\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1
Here I'm using the PlusCal convention of describing the control state of each
process using a variable named "pc" (I think of it as "program counter").
This property is an invariant, but it's not an inductive invariant, because it
doesn't satisfy the conditions above.
You can use an inductive invariant to prove a property by writing a proof that look like this:
1. Init => Inv
2. Inv /\ Next => Inv'
3. Inv => DesiredProperty
To come up with an inductive invariant, we need to give labels to each step of
the algorithm, let's call them "s1", "s2", and "Done", where "Done" is the
terminal state for each process.
s1: x[self] := 1;
s2: y[self] := x[(self-1) % N]
Coming up with an inductive invariant
Consider the state of the program when it is in the penultimate (second-to-last) state of an
execution.
In the last execution state, pc[i]="Done" for all values of i. In the
penultimate state, pc[i]="Done" for all values of i except one, let's call it
j, where pc[j]="s2".
If a process i is in the "Done" state, then it must be true that x[i]=1, since the process must have executed statement "s1".
Similarly, the process j that is in the state "s2" must also have executed
statement "s1", so it must be true that x[j]=1.
We can express this as an invariant, which happens to be an inductive
invariant.
\A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
PlusCal model
To prove that our invariant is an inductive invariant, we need a proper model that has an
Init state predicate and a Next state predicate.
We can start by describing the algorithm in PlusCal. It's a very simple
algorithm, so I'll call it "Simple".
--algorithm Simple
variables
x = [i \in 0..N-1 |->0];
y = [i \in 0..N-1 |->0];
process Proc \in 0..N-1
begin
s1: x[self] := 1;
s2: y[self] := x[(self-1) % N]
end process
end algorithm
Translating to TLA+
We can translate the PlusCal model into TLA+. Here's what it looks like when we
translate PlusCal into TLA+ (I've left out the termination condition, because
we don't need it here).
------------------------------- MODULE Simple -------------------------------
EXTENDS Naturals
CONSTANTS N
VARIABLES x, y, pc
vars == << x, y, pc >>
ProcSet == (0..N-1)
Init == (* Global variables *)
/\ x = [i \in 0..N-1 |->0]
/\ y = [i \in 0..N-1 |->0]
/\ pc = [self \in ProcSet |-> "s1"]
s1(self) == /\ pc[self] = "s1"
/\ x' = [x EXCEPT ![self] = 1]
/\ pc' = [pc EXCEPT ![self] = "s2"]
/\ y' = y
s2(self) == /\ pc[self] = "s2"
/\ y' = [y EXCEPT ![self] = x[(self-1) % N]]
/\ pc' = [pc EXCEPT ![self] = "Done"]
/\ x' = x
Proc(self) == s1(self) \/ s2(self)
Next == (\E self \in 0..N-1: Proc(self))
\/ (* Disjunct to prevent deadlock on termination *)
((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars)
Spec == Init /\ [][Next]_vars
=============================================================================
Note how it defines the Init and Next state predicates.
The inductive invariant in TLA+
We can now specify the inductive invariant we want to prove. We also want our
inductive invariant to imply the property we're interested in proving, so we
add it as a conjunction.
Inv == /\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
/\ (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1
Informal handwaving "proof"
1. Init => Inv
It should be obvious why this is true, since the antecedents in Inv are all false if Init is true.
2. Inv /\ Next => Inv'
The first conjunct of Inv'
(\A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1))'
The interesting case is the one where pc[i]="s1" and pc'[i]="s2" for some i. By
the definition of s1, it should be clear why this is true.
The second conjunct of Inv'
((\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1)'
The interesting case is the one we discussed earlier, where pc[i]="Done" for
all values of i except one, j, where pc[j]="s2".
By the first conjunct of Inv, we know that x[i]=1 for all values of i.
By s2, y'[j]=1.
3. Inv => DesiredProperty
Here, our desired property is
(\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1
Note that we've just anded the property that we are interested in to the
invariant, so this is trivial to prove.
Formal proof with TLAPS
You can use the TLA+ Proof
System (TLAPS) to write
a formal proof that can be checked mechanically to determine if it is correct.
Here's a proof that I wrote and verified using TLAPS that uses an inductive invariant to
prove the desired property. (Note: this is the first proof I've ever written
using TLAPS, so keep in mind this has been written by a novice).
AtLeastOneYWhenDone == (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1
TypeOK == /\ x \in [0..N-1 -> {0,1}]
/\ y \in [0..N-1 -> {0,1}]
/\ pc \in [ProcSet -> {"s1", "s2", "Done"}]
Inv == /\ TypeOK
/\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
/\ AtLeastOneYWhenDone
ASSUME NIsInNat == N \in Nat \ {0}
\* TLAPS doesn't know this property of modulus operator
AXIOM ModInRange == \A i \in 0..N-1: (i-1) % N \in 0..N-1
THEOREM Spec=>[]AtLeastOneYWhenDone
<1> USE DEF ProcSet, Inv
<1>1. Init => Inv
BY NIsInNat DEF Init, Inv, TypeOK, AtLeastOneYWhenDone
<1>2. Inv /\ [Next]_vars => Inv'
<2> SUFFICES ASSUME Inv,
[Next]_vars
PROVE Inv'
OBVIOUS
<2>1. CASE Next
<3>1. CASE \E self \in 0..N-1: Proc(self)
<4> SUFFICES ASSUME NEW self \in 0..N-1,
Proc(self)
PROVE Inv'
BY <3>1
<4>1. CASE s1(self)
BY <4>1, NIsInNat DEF s1, TypeOK, AtLeastOneYWhenDone
<4>2. CASE s2(self)
BY <4>2, NIsInNat, ModInRange DEF s2, TypeOK, AtLeastOneYWhenDone
<4>3. QED
BY <3>1, <4>1, <4>2 DEF Proc
<3>2. CASE (\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars
BY <3>2 DEF TypeOK, vars, AtLeastOneYWhenDone
<3>3. QED
BY <2>1, <3>1, <3>2 DEF Next
<2>2. CASE UNCHANGED vars
BY <2>2 DEF TypeOK, vars, AtLeastOneYWhenDone
<2>3. QED
BY <2>1, <2>2
<1>3. Inv => AtLeastOneYWhenDone
OBVIOUS
<1>4. QED
BY <1>1, <1>2, <1>3, PTL DEF Spec
Note that in a proof using TLAPS, you need to have a type checking invariant (it's called TypeOK above), and you also need to handle "stuttering states" where none of the variables change, which is why we use [Next]_vars instead of Next.
Here's a gist with the complete model and proof.

The xs model the following behavior: x[i] is 1 if and only if the process i has already run. Naturally, after all processes have completed, all xs are thus set to 1.
The ys are a bit trickier: y[i] is set if x[i-1] was set, that is, y[i] is 1 if and only if the predecessor of i had already run when i was doing its write to y.
The program invariant is: If a process has set y[i], it must already have set x[i] to 1. This is true regardless whether y[i] is set to 0 or 1.
Proving this invariant is quite easy: In the beginning, none of the ys is set, so it holds trivially. During program execution, each write to y[i] is sequenced after a write to x[i]. Therefore the invariant also holds for every step of the program afterwards.
The further reasoning goes like this: The last process to finish sets y[i] to 1 because, by definition of being the last process, its predecessor must have already finished execution at that point (ie. its y value is already set). Which means, because of the invariant, its x value (which determines the last process' y value) has to be 1.
Another way to look at it: You cannot find an execution order in which all ys are set to 0. Doing so would require all processes to execute before their predecessor. However, since our processes are arranged in a ring (that is, if I follow the predecessor chain I will eventually end up at my starting point again), this leads to the contradiction that at least one process must have finished executing before it wrote to y.

Related

"Generating Numbers" Puzzle

I have come across the following puzzle and couldn't formulate a solution in Picat:
You will generate 5-digit numbers, where each digit is in 1..5 and different from the others, with the constraint that any three adjacent digits used in one number can’t be used in another number.
How many different numbers can be obtained according to this rule?
For example, if we generated the number 12345, the other numbers CANNOT contain 123, 345, or 456, so all numbers of the following form are banned from the chain:
123AB, A123B, AB123,
234AB, A234B, AB234,
345AB, A345B, AB345,
I got really confused on how to store these "forbidden" sublists and how to check each number against them as I build the list of numbers.
My attempt:
I think I managed to generate the valid "candidate" for a given chain state, but I can't figure out how to generate the chain like this.
import cp.
import util.
valid(Ls, Cd) ?=>
% verify that the head of the chain is correct?
% so the chain consists of permutations of 12345
foreach (L in Ls)
len(L) = 5,
permutation(L, [1,2,3,4,5])
end,
% generate the candidate
Cd = new_list(5),
permutation(Cd, [1,2,3,4,5]),
% check the candidate against the head of the chain
foreach (L in Ls)
not sublist([L[1],L[2],L[3]], Cd),
not sublist([L[2],L[3],L[4]], Cd),
not sublist([L[3],L[4],L[5]], Cd)
end,
solve(Ls),
printf("Cd: %w\n", Cd),
fail,
nl.
% so that 3 element sublists of 12345 are 123,234 and 345.
sublist(X, S) =>
append(_, T , S),
append(X, _ , T),
X.len #>= 0.
% seems to work, the candidates don't have the banned triplets as sublists.
% so in this case the banned triplets would be
% 123,234,345,543,432,321
go => valid([[1,2,3,4,5], [5,4,3,2,1]], _).
main => go.
Comment: It is indeed very interesting that the situation is not symmetric. If we analyze the state:
[12345,12435,12534,13245,13425,13524,14235,
14325,14523,21543,24153,25413,35421,43152]
we see that the three candidates which are valid/can be appended to this chain are:
Cd1: [5,3,2,1,4]
Cd2: [4,5,3,1,2]
Cd3: [4,5,3,2,1]
Obviously, if we choose Cd3, since it contains both 453 and 532 it disallows us from choosing any candidate after it, so the chain ends at N=15.
If we choose Cd1, it excludes Cd3 but still keeps Cd2, so the chain goes on to N=16.
Similarly if we choose Cd2, it excludes Cd3 but still keeps Cd1, so again N=16 is possible.
So it seems that in general some candidates contain(and therefore exclude) others, and the length of the chain depends on whether we choose these candidates or not.
Here's the Picat model with the models in Update 4 and Update 5 and Update 6: http://hakank.org/picat/generating_numbers.pi
Update 6: This is probably the constraint model I would have written if not gotten astray from the beginning with wrong assumptions about the problem... It's a more direct approach (from a constraint programmer's perspective) and don't use permutations/1 etc.
It is slightly slower than Update 5 (3.7s using the sat solver vs 3.3s for the Update 4 model). The cp solver is, however, much slower on this model.
In the Picat program cited above it's model go3/0. (The fastest model is go/0.)
The approach:
create an 20 x 5 matrix with domain 1..5.
for each row ensure that it's distinct numbers
and in the loop ensure that there are no common triplets
The model:
go3 ?=>
nolog,
N = 5,
M = 20,
X = new_array(M,N),
X :: 1..N,
% symmetry breaking
X[1,1] #= 1,X[1,2] #= 2,X[1,3] #= 3,X[1,4] #= 4,X[1,5] #= 5,
foreach(I in 1..M)
all_distinct([X[I,K] : K in 1..N]),
foreach(J in 1..I-1)
foreach(A in 0..2)
foreach(B in 0..2)
sum([X[I,K+A] #= X[J,K+B] : K in 1..3]) #< 3
end
end
end
end,
solve($[ff,split],X),
foreach(P in X)
println(P.to_list)
end,
println(numbers=[[I.to_string : I in T].join('').to_int : T in X]),
nl.
go3 => true.
First solution (3.7s with sat):
[12345,35421,23154,25314,43512,32415,32541,12453,21534,14523,
34251,14235,54312,45132,51432,52134,53214,34125,41352,15243]
Update 5 Here's a much faster approach: About 3.3s to find the first solutions, compared to 1min25s for the approach in Update 4.
The approach here is:
Preprocessing step: From the 120 permutations (Ps), build a 120 x 120 matrix A of 0/1 where A[P1,P2] = 1 means that Ps[P1] and Ps[P2] are compatible, i.e. that they have no common triplet
The model: Create a 0/1 list X of length 120, where X[I] = 1 means that the permutations Ps[I] should be in the sequence (or rather "set" since the order of the permutations don't make a difference).
In the foreach loop, X[I]*X[J] #= 1 #=> A[I,J] is a "strange" way of saying that both X[I] and X[J] should be in the sequence if A[I,J] #= 1.
The cp solver takes about 3.3s to find the first length 20 solution. The sat solver is slower for this model: 4.8s (so it's still much faster than the Update 4 version).
Here the complete model:
go ?=>
N = 5,
Ps = permutations(1..N),
PsLen = Ps.len,
% Compatibility matrix:
% A[P1,P2] = 1 if they don't have any common triple
A = new_array(PsLen,PsLen),
bind_vars(A,0),
foreach(P1 in 1..PsLen)
A[P1,P1] := 1,
foreach(P2 in 1..PsLen, P1 < P2)
if check_perms(Ps[P1],Ps[P2]) then
A[P1,P2] := 1,
A[P2,P1] := 1
end
end
end,
M = 20, % length 20 sequence
println(m=M),
% List of 0/1:
% 1 means that it should be in the sequence
X = new_list(PsLen),
X :: 0..1,
sum(X) #= M, % We want M 1s
X[1] #= 1, % symmetry breaking
foreach(I in 1..PsLen)
foreach(J in 1..I-1)
X[I]*X[J] #= 1 #=> A[I,J]
end
end,
solve($[degree,updown],X),
println(x=X),
Perms = [Ps[I] : I in 1..PsLen, X[I]==1],
foreach(P in Perms)
println(P)
end,
println(numbers=[[I.to_string : I in T].join('').to_int : T in Perms]),
% println("Checking:"),
% foreach(I in 1..Perms.len, J in 1..I-1)
% if not check_perms(Perms[I],Perms[J]) then
% println("ERROR!"=Perms[I]=Perms[J])
% end
% end,
nl,
% fail,
nl.
go4 => true.
% list version
check2(Forbidden,Tri) =>
foreach(PP in Tri)
not membchk(PP,Forbidden)
end.
check_perms(Perm1,Perm2) =>
tri(Perm1,Tri1),
tri(Perm2,Tri2),
foreach(PP in Tri2)
not membchk(PP,Tri1)
end,
foreach(PP in Tri1)
not membchk(PP,Tri2)
end.
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].
Here's the first solution:
x = [1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1]
[1,2,3,4,5]
[3,2,4,1,5]
[3,4,2,5,1]
[2,1,4,3,5]
[4,3,1,2,5]
[4,1,3,5,2]
[2,4,5,1,3]
[4,2,1,5,3]
[4,5,2,3,1]
[1,4,5,3,2]
[2,3,5,4,1]
[1,3,2,5,4]
[3,5,1,2,4]
[3,1,5,4,2]
[2,5,3,1,4]
[5,2,1,3,4]
[5,3,4,1,2]
[1,5,2,4,3]
[5,1,4,2,3]
[5,4,3,2,1]
numbers = [12345,32415,34251,21435,43125,41352,24513,42153,45231,14532,23541,13254,35124,31542,25314,52134,53412,15243,51423,54321]
CPU time 3.325 seconds. Backtracks: 233455
Update 4 As mentioned in the comments, here's a constraint model which find an sequence of length 20.
A seq of 20 is optimal with the following reasoning: There are 60 possible triplets in the collection of the 120 permutations of 1..5. Each number consists of 3 unique triplets each. Thus, there can not be more than 60 / 3 = 20 numbers in such a sequence.
Here's a 20 number sequence:
[12345,32451,43125,15423,23541,41532,52134,
24135,14352,31524,54321,25314,42513,51243,
34215,53412,45231,35142,21453,13254]
This model using the sat solver takes about 1min25 to first this sequence. It's a little more elaborated than the "simple" use of list handling in the previous versions which use backtracking, and that was the problem in these approaches to get a sequence of maximum length.
Some comments:
matrix_element/4 is used to connect the triplets in the Y matrix and the numbers in Z.
the triplets are represented as a number 123..543 (in Z) and thus we can make sure that they are distinct.
as usual Picat's cp module is faster on simpler instances (e.g. lengths up to 16), but for larger instances (>16) then sat tends to be much better.
The model:
import sat, util.
go3 ?=>
nolog,
N = 5,
Ps = permutations(1..N),
PLen = Ps.len,
% Find the triplets
TripletsMap = new_map(),
foreach(P in Ps)
tri(P,Tri),
foreach(T in Tri) TripletsMap.put(T,1) end
end,
% Convert to numbers (123..543)
Triplets = [T[1]*100+T[2]*10+T[3] : T in keys(TripletsMap)].sort,
% length of sequence
member(M,20..20),
println(m=M),
% Indices of the selected permutation
X = new_list(M),
X :: 1..PLen,
% The triplets
Z = new_list(M*3),
Z :: Triplets,
% Y contains the "shortcuts" to the permutations
Y = new_array(M,5),
Y :: 1..N,
all_distinct(X),
all_distinct(Z),
X[1] #= 1, % symmetry breaking
% Fill Y
foreach(I in 1..M)
element(I,X,II),
foreach(K in 1..5)
matrix_element(Ps,II,K,Y[I,K])
end
end,
% Convert triplet list in Y <-> triplet number in Z
C = 1,
foreach(I in 1..M)
foreach(J in 1..3)
to_num([Y[I,J+K] : K in 0..2],10,Z[C]),
C := C+1
end
end,
Vars = Z ++ X ++ Y.vars,
solve($[constr,updown,split],Vars) % split (SAT)
PsX = [Ps[I] : I in X],
println(numbers=[[I.to_string : I in Ps[T]].join('').to_int : T in X]),
nl.
go3 => true.
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].
% converts a number Num to/from a list of integer
% List given a base Base
to_num(List, Base, Num) =>
Len = length(List),
Num #= sum([List[I]*Base**(Len-I) : I in 1..Len]).
And I still think that there is some algorithmic approach which solves this problem in notime...
Update3 Sigh, the program in Update2 was still wrong since it only picked numbers that were later in the permutation list. This third version use permutation(1..5,Next) so all numbers has a change to be picked.
go2 ?=>
Ps = permutations(1..5),
Forbidden = [],
gen(Ps,Forbidden,L),
println([[I.to_string : I in C].join('').to_int : C in L]),
println(len=L.len),
nl,
fail,
nl.
go2 => true.
%
% Create triplets (Tri) from the permutation P
%
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].
% list version
check2(Forbidden,Tri) =>
foreach(PP in Tri)
not membchk(PP,Forbidden)
end.
% list version
add_forbidden_triplets2(Forbidden,Triplets) = F =>
foreach(T in Triplets)
Forbidden := Forbidden ++ [T]
end,
F = Forbidden.
gen([],_Forbidden,[]).
gen(Ps,Forbidden,[Next|L]) :-
permutation(1..5,Next),
not membchk(Next,L),
tri(Next,Tri),
check2(Forbidden,Tri),
% Forbidden := add_forbidden_triplets2(Forbidden,Tri),
Forbidden2 = add_forbidden_triplets2(Forbidden,Tri), % better
Ps2 = [PP : PP in Ps, PP != Next],
gen(Ps2,Forbidden2,L).
gen(_Ps,Forbidden,[]) :-
not (permutation(1..5,Next),
tri(Next,Tri),
check2(Forbidden,Tri)).
The first solution is of length 16:
[12345,12435,12534,13245,13425,13524,14235,14325,
14523,21543,24153,25413,35421,43152,45312,53214]
The next solution (via backtracking) is - however - of length 15:
[12345,12435,12534,13245,13425,13524,14235,14325,
14523,21543,24153,25413,35421,43152,45321]
So I'm - still - not sure if 16 is the maximum length.
Update2: The version in Update was not completely correct (in fact it was dead wrong), since I forgot to add the triplet to Forbidden in the loop (add_forbidden_triplets(Forbidden, Triplets). The program is updated below.
The first solution with 12345 are start number is:
[12345,23145,13245,13425,34125,12435,24135,14235,
14325,43152,42153,45213,45312,53214]
len = 14
And now it's getting interesting since the length of the other sequences (with different start numbers) are around 12..17 numbers. And that's contra intuitive since these things should be symmetric, shouldn't it?
Update: Since I first missed one important constraint in the instructions, here's an adjusted program based on the first approach. It yield a sequence of length 107. The basic - and quite simple - change is that the forbidden triplets are now saved in the hash table Forbidden. The sequence is finished when there's not any available number (when Found is false).
go ?=>
N = 5,
Ps = permutations(1..N),
select(P,Ps,Ps2),
L = [P],
tri(P,Triplets),
Forbidden = new_map(), % keep forbidden triplets in a hash table
add_forbidden_triplets(Forbidden, Triplets), % added in **Update2**
Found = true,
while(Found == true)
if select(NextP,Ps2,Ps3), tri(NextP,PTri), check(Forbidden,PTri) then
L := L ++ [NextP],
add_forbidden_triplets(Forbidden, PTri),
P := NextP,
Ps2 := Ps3
else
Found := false
end
end,
println([[I.to_string : I in C].join('').to_int : C in L]),
println(len=L.len),
nl,
% fail, % generate a new solution
nl.
go => true.
%
% Create triplets (Tri) from the permutation P
%
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].
%
% Check if Tri contain some forbidden triplet
%
check(Forbidden,Tri) =>
foreach(PP in Tri)
not Forbidden.has_key(PP)
end.
%
% Add triplets to Forbidden map
%
add_forbidden_triplets(Forbidden,Triplets) =>
foreach(T in Triplets)
Forbidden.put(T,1)
end.
Here's the first solution:
[12345,23145,13245,31245,32145,32415,32451,13425,
1425,34125,34215,34251,31452,34152,12435,21435,
24135,24315,24351,14235,42135,42315,42351,14325,
41325,43125,43215,43251,14352,41352,43152,43512,
43521,12453,21453,24153,24513,24531,14253,41253,
42153,42513,42531,14523,41523,45213,45231,14532,
41532,45132,45312,45321,21354,23154,23514,23541,
13254,31254,32154,32514,32541,13524,31524,35124,
35214,35241,13542,31542,35142,35412,35421,12534,
21534,25134,25314,25341,52134,52314,15324,51324,
53124,53214,53241,15342,51342,53142,53412,53421,
12543,21543,25143,25413,25431,15243,51243,52143,
52413,52431,15423,51423,54213,54231,15432,51432,
54132,54312,54321]
len = 107
Here's my original answer:
Your program generates 106+1 numbers (using initial number to just 12345), not all 120 that my programs below generates. Perhaps I have missed some requirement in the problem? By the way, you don't need solve/1 in your program since there aren't any constraints.
Below are two of my approaches: both generate a sequence of length 120, i.e. all numbers can be "chained". Both use permutations/1 (from util module) to first generate all the 120 permutations (5!=120) and the select non-deterministically some of the permutations that are left (using select/3). The checking of the allowed successor is done using tri/2 to generate all triplets and check/2 to check that there no common triplets.
Since I found out early that all number can be used (unless I've missed something), the control when the program is done is when there are no permutations available. This is probably a shortcoming of my approach.
import util.
% Using foreach loop
go ?=>
N = 5,
Ps = permutations(1..N),
select(P,Ps,Ps2), % pick the first number (i.e. 12345)
L := [P],
while(Ps2 != [])
tri(P,Forbidden),
select(NextP,Ps2,Ps3),
tri(NextP,PTri),
check(Forbidden,PTri),
L := L ++ [NextP],
P := NextP,
Ps2 := Ps3
end,
println([[I.to_string : I in C].join('').to_int : C in L]), % convert to number
nl.
go => true.
% Using genx/2 ("Prolog style")
go3 ?=>
Ps = permutations(1..5),
PLen = Ps.len,
println(plen=PLen),
genx(Ps,L),
println(len=L.len),
nl.
go3 => true.
% Create triplets (Tri) from the permutation P
tri(P,Tri) :- Tri=[P[K..K+2] : K in 1..3].
% Check if Tri contain some forbidden triplet
check(Forbidden,Tri) =>
foreach(PP in Tri)
not membchk(PP,Forbidden)
end.
% This is the same principal logic as used in go/0
% but in "Prolog style"
genx([],[]).
genx([P],[P]).
genx([P|Ps],[P|L]) :-
tri(P,Forbidden),
select(Next,Ps,Ps2), % pick a new available number
tri(Next,Tri),
check(Forbidden,Tri),
genx([Next|Ps2],L).
Here's the output of go/0 (converted to numbers):
[12345,23145,21345,23415,13245,23451,31245,32145,32415,
13425,32451,31425,34125,34215,13452,34251,31452,34152,
34512,12435,34521,21435,24135,24315,14235,24351,41235,
42135,42315,14325,42351,41325,43125,43215,14352,43251,
41352,43152,43512,12453,43521,21453,24153,24513,14253,
24531,41253,42153,42513,14523,42531,41523,45123,45213,
14532,45231,41532,45132,45312,12354,45321,21354,23154,
23514,13254,23541,31254,32154,32514,13524,32541,31524,
35124,35214,13542,35241,31542,35142,35412,12534,35421,
21534,25134,25314,15234,25341,51234,52134,52314,15324,
52341,51324,53124,53214,15342,53241,51342,53142,53412,
12543,53421,21543,25143,25413,15243,25431,51243,52143,
52413,15423,52431,51423,54123,54213,15432,54231,51432,
54312,54132,54321]

Smallest sub-list that contains all numbers

I am trying to write a program in sml that takes in the length of a list, the max number that will appear on the list and the list of course. It then calculates the length of the smallest "sub-list" that contains all numbers.
I have tried to use the sliding window approach , with two indexes , front and tail. The front scans first and when it finds a number it writes into a map how many times it has already seen this number. If the program finds all numbers then it calls the tail. The tail scans the list and if it finds that a number has been seen more times than 1 it takes it off.
The code I have tried so far is the following:
structure Key=
struct
type ord_key=int
val compare=Int.compare
end
fun min x y = if x>y then y else x;
structure mymap = BinaryMapFn ( Key );
fun smallest_sub(n,t,listall,map)=
let
val k=0
val front=0
val tail=0
val minimum= n;
val list1=listall;
val list2=listall;
fun increase(list1,front,k,ourmap)=
let
val number= hd list1
val elem=mymap.find(ourmap,number)
val per=getOpt(elem,0)+1
fun decrease(list2,tail,k,ourmap,minimum)=
let
val number=hd list2
val elem=mymap.find(ourmap,number)
val per=getOpt(elem,0)-1
val per1=getOpt(elem,0)
in
if k>t then
if (per1=1) then decrease(tl list2,tail+1,k-1,mymap.insert(ourmap,number,per),min minimum (front-tail))
else decrease(tl list2,tail+1,k,mymap.insert(ourmap,number,per),min minimum (front-tail))
else increase (list1, front,k,ourmap)
end
in
if t>k then
if (elem<>NONE) then increase (tl list1,front+1,k,mymap.insert(ourmap,number,per))
else increase(tl list1,front+1,k+1,mymap.insert(ourmap,number,per))
else (if (n>front) then decrease(list2,tail,k,ourmap,minimum) else minimum)
end
in
increase(list1,front,k,map)
end
fun solve (n,t,acc)= smallest_sub(n,t,acc,mymap.empty)
But when I call it with this smallest_sub(10,3,[1,3,1,3,1,3,3,2,2,1]); it does not work. What have I done wrong??
Example: if input is 1,3,1,3,1,3,3,2,2,1 the program should recognize that the parto of the list that contains all numbers and is the smallest is 1,3,3,2 and 3,2,2,1 so the output should be 4
This problem of "smallest sub-list that contains all values" seems to recur in
new questions without a successful answer. This is because it's not a minimal,
complete, and verifiable example.
Because you use a "sliding window" approach, indexing the front and the back
of your input, a list taking O(n) time to index elements is not ideal. You
really do want to use arrays here. If your input function must have a list, you
can convert it to an array for the purpose of the algorithm.
I'd like to perform a cleanup of the code before answering, because running
your current code by hand is a bit hard because it's so condensed. Here's an
example of how you could abstract out the book-keeping of whether a given
sub-list contains at least one copy of each element in the original list:
Edit: I changed the code below after originally posting it.
structure CountMap = struct
structure IntMap = BinaryMapFn(struct
type ord_key = int
val compare = Int.compare
end)
fun count (m, x) =
Option.getOpt (IntMap.find (m, x), 0)
fun increment (m, x) =
IntMap.insert (m, x, count (m, x) + 1)
fun decrement (m, x) =
let val c' = count (m, x)
in if c' <= 1
then NONE
else SOME (IntMap.insert (m, x, c' - 1))
end
fun flip f (x, y) = f (y, x)
val fromList = List.foldl (flip increment) IntMap.empty
end
That is, a CountMap is an int IntMap.map where the Int represents the
fixed key type of the map, being int, and the int parameter in front of it
represents the value type of the map, being a count of how many times this
value occurred.
When building the initialCountMap below, you use CountMap.increment, and
when you use the "sliding window" approach, you use CountMap.decrement to
produce a new countMap that you can test on recursively.
If you decrement the occurrence below 1, you're looking at a sub-list that
doesn't contain every element at least once; we rule out any solution by
letting CountMap.decrement return NONE.
With all of this machinery abstracted out, the algorithm itself becomes much
easier to express. First, I'd like to convert the list to an array so that
indexing becomes O(1), because we'll be doing a lot of indexing.
fun smallest_sublist_length [] = 0
| smallest_sublist_length (xs : int list) =
let val arr = Array.fromList xs
val initialCountMap = CountMap.fromList xs
fun go countMap i j =
let val xi = Array.sub (arr, i)
val xj = Array.sub (arr, j)
val decrementLeft = CountMap.decrement (countMap, xi)
val decrementRight = CountMap.decrement (countMap, xj)
in
case (decrementLeft, decrementRight) of
(SOME leftCountMap, SOME rightCountMap) =>
Int.min (
go leftCountMap (i+1) j,
go rightCountMap i (j-1)
)
| (SOME leftCountMap, NONE) => go leftCountMap (i+1) j
| (NONE, SOME rightCountMap) => go rightCountMap i (j-1)
| (NONE, NONE) => j - i + 1
end
in
go initialCountMap 0 (Array.length arr - 1)
end
This appears to work, but...
Doing Int.min (go left..., go right...) incurs a cost of O(n^2) stack
memory (in the case where you cannot rule out either being optimal). This is a
good use-case for dynamic programming because your recursive sub-problems have a
common sub-structure, i.e.
go initialCountMap 0 10
|- go leftCountMap 1 10
| |- ...
| `- go rightCountMap 1 9 <-.
`- go rightCountMap 0 9 | possibly same sub-problem!
|- go leftCountMap 1 9 <-'
`- ...
So maybe there's a way to store the recursive sub-problem in a memory array and not
perform a recursive lookup if you know the result to this sub-problem. How to
do memoization in SML is a good question in and of itself. How to do purely
functional memoization in a non-lazy language is an even better one.
Another optimization you could make is that if you ever find a sub-list the
size of the number of unique elements, you need to look no further. This number
is incidentally the number of elements in initialCountMap, and IntMap
probably has a function for finding it.

How to make nested for loop over two integer ranges?

I want to replicate a function like -
for i in range(10):
for j in range(10):
do something with i, j
Is there a easy way to do this in SML?
You can certainly do this, but you should consider whether you really want to just do something with i, j and discard the result. That is inherently imperative logic, not really idiomatic Standard ML.
But if this is really what you want, then there are a few ways. Here's an imperative one:
let val i = ref 0
in while !i < 10 do
( let val j = ref 0
in while !j < 10 do
( do something with i, j
; j := !j + 1
)
end
; i := !i + 1
)
end
And here's a more-idiomatic one:
let val range0To9 = List.tabulate (10, fn i => i)
in List.app range0To9 (fn i => List.app range0To9 (fn j => do something with i, j))
end
It depends on what it is you want to do.
Do you want to produce a side-effect, like print something, for each (i, j)?
fun appulate (n, f) =
let fun go i = if i < n then (f i; go (i+1)) else ()
in go 0 end
val show = Int.toString
val _ = appulate (10, fn i =>
appulate (10, fn j =>
print ("(" ^ show i ^ ", " ^ show j ^ ")\n")))
Or do you want to produce a result value? Is this value a list, or something else?
As a concrete example, there's a Q&A on the Pythagorean triplets in SML. It asks how one might port a piece of Haskell code that uses list comprehensions to Standard ML which doesn't feature list comprehensions. You could make a very similar case with Python 3; its range() function does not produce a list, but rather an object that can be iterated.
Whereas SML's List.tabulate actually does produce a list, making it less memory efficient if you're going to discard or filter that list immediately afterwards. The Q&A I linked to discusses readability and efficiency for a specific case of doing something on the Cartesian product of three integer ranges (namely finding the ones that are Pythagorean triplets).
Maybe if you gave a concrete example of doing something, you'd get a better response. :-)
Feel free to revise your question with a concrete example.

isPrime function with TLA+

This question is about TLA+ using toolbox (https://github.com/tlaplus/tlaplus/releases)
I haven't been able to find any tag about it. Sorry about that. This is why I only tagged with Primes. If I am missing something please be kind to add better tags or create the missing ones.
Here is the issue
There is a well known function and algorithm for GCD. Here it is.
------------------ MODULE Euclid -------------------------------
EXTENDS Naturals, TLC
CONSTANT K
Divides(i,j) == \E k \in 0..j: j = i * k
IsGCD(i,j,k) ==
Divides(i,j)
/\ Divides(i,k)
/\ \A r \in 0..j \cup 0..k :
(Divides(r,j ) /\ Divides(r,k)) => Divides(r,i)
(* --algorithm EuclidSedgewick
{
variables m \in 1..K, n \in 1..m, u = m, v = n;
{
L1: while (u # 0) {
if (u < v) { u := v || v := u };
L2: u := u - v
};
assert IsGCD(v, m, n)
}
}
*)
This is a well known solution which is working.
I'm now trying to write a isPrime function using this one. But I think what I'm doing is wrong. I would like to know if you have an idea.
isPrime(nb) ==
\E k \in 2..nb: isGCD(nb,k,1) \/ isGCD(nb,k,nb)
Thanks
There are many way to express the notion that an integer is prime, however your attempt says that an integer N is prime if there exists some integer k in 2..N for which the gcd(k,n) = 1 or gcd(k,n) = n. This is easily seen to be incorrect, as 4 is clearly composite but gcd(3,4) = 1. And, of course, for every N prime or not, gcd(N, N) = N.
I'm not sure about the rules for TLA+, but I had a quick read of some documentation and here's my try at IsPrime
isPrime(nb) == \A k in 2..nb-1: ~Divides(k, nb)
or
isPrime(nb) == \A k in 1..nb: Divides(k, nb) => ( (k = 1) \/ (k=nb) )
or, if you really want to work IsGCD in there for some reason
isPrime(nb) == \A k in 1..nb: IsGCD(k, nb, d) => ( (d = 1) \/ (d = nb) )
or
isPrime(nb) == \A k in 2..nb-1: IsGCD(k, nb, d) => (d = 1)

SML - error in finding elements in list

I'm new to SML. I've written a function which takes 2 int and a list of tuples as input.
fun move(x,y,mylist:(int * int)list): NOxNO =
let
val counter = ref y-1
in
if y=1 then (x,y)
else (
while !counter > 0 do (
if List.exists (fn s => s = (x,!counter)) mylist
then counter := !counter - 1
else break
);
if !counter = 0 then (x,y) else (x,y-1)
)
end
I may have syntax error since I'm a beginner. What the function is trying to do is: it will check the list to find all the tuples whose first element is x and second element varies from 1 to y-1 (tuples like this: (x,1) (x,2) ... (x,y-1) ) and if all of them exist in the list it will return (x,y) else (x,y-1). I used a while loop and a counter. counter is set to y-1 at first and in while loop if (x,counter) was found, counter's value will decrease. At last if counter=0 it means we have found all the tuples. After running the program I encountered this error:
Caught Error ../compiler/TopLevel/interact/evalloop.sml:296.17-296.20
../compiler/TopLevel/interact/evalloop.sml:44.55
../compiler/TopLevel/interact/evalloop.sml:66.19-66.27
What's wrong?
Here's some feedback:
(Error) As Andreas Rossberg said, break doesn't exist. But if you use (), the loop won't terminate when y > 1 and the predicate evaluates to false. You probably want to "break" by setting counter := 0 or counter := -1, depending on what you want the subsequent if !counter = 0 ... to do.
(Error) As Andreas Rossberg said, ref y-1 gives the following type error:
! Toplevel input:
! val r = ref y-1;
! ^
! Type clash: expression of type
! int
! cannot have type
! int ref
This is because function application (ref y) binds tighter than infix operators (y-1). What you mean is ref (y-1), since you can't subtract 1 from a reference.
This isn't very comprehensible or robust. I tried to run it in the simplest case I could think of,
val test1 = move (1,1,[])
But that's a weird base case not handled by the loop. If I change the numbers slightly,
val test2 = move (5,6,[])
then it returns either (5,6) or (5,5) depending on what you change break into.
Based on your description below the code, here is a suggested implementation, although I'm still not completely certain I understand the use of this function:
(* verticalPointsExist (x, y, ps) checks that
* (x,1), (x,2), ..., (x,y-1) are all in ps. *)
fun verticalPointsExist (_, 0, _) = true
| verticalPointsExist (x, y, ps) = List.exists (fn p => (x,y) = p) ps
andalso verticalPointsExist (x, y - 1, ps)
fun move (x, y, ps) =
if verticalPointsExist (x, y, ps) then (x,y) else (x,y-1)
Considerations I made:
Use recursion rather than iteration.
Split the checking part into a helper function, so move doesn't do two things.
Give the functions good names so the code reads more easily. Since I don't know the domain and am really guessing as to whether y is some kind of vertical dimension, there are probably even better names out there. (verticalLineExists? verticalPathClear?) Maybe a more general function will have a better name, e.g. one that took two points and saw that the line is clear between them.
There is no break in ML. You probably just want to write () there. Also, you'll need parens around the argument to ref in line 3.