// --------------------------------------------------------------------------------
// a system of robots moving in a square without colliding
// --------------------------------------------------------------------------------

val R:ℕ; // number of robots
val P:ℕ; // number of positions

axiom notzero ⇔ R ≥ 1 ∧ P ≥ 1;

type Robot = ℕ[R-1];
type Position = ℕ[P-1];
enumtype Direction = Stop | Left | Right | Up | Down;

type Positions = Array[R,Position];
type Directions = Array[R,Direction];
   
// the desired safety property of the system: 
// no two robots are at the same position
pred noCollision(x:Positions, y:Positions) ⇔
  ∀r1:Robot, r2:Robot with r1 < r2. x[r1] ≠ x[r2] ∨ y[r1] ≠ y[r2];

// the initial state condition of the system: 
// robots are at different positions and do not yet move
pred init(x:Positions, y:Positions, d:Directions) ⇔
  noCollision(x, y) ∧ ∀r:Robot. d[r] = Direction!Stop;

// may robot at position x,y move into direction d?
pred mayMove(x:Position, y:Position, d:Direction) ⇔
  match d with
  {
    Left  -> x > 0; 
    Right -> x < P-1;
    Up    -> y > 0; 
    Down  -> y < P-1;
    Stop  -> ⊤; 
  };
  
// the new x-position if robot moves into direction d
fun moveX(x:Position, d:Direction):Position =
  match d with
  {
    Left  -> x-1; 
    Right -> x+1;
    Stop  -> x; Up -> x; Down  -> x;
  };
  
// the new y-position if robot moves into direction d
fun moveY(y:Position, d:Direction):Position =
  match d with  
  {
    Up   -> y-1; 
    Down -> y+1;
    Stop  -> y; Left -> y; Right  -> y;
  }; 
  
// robot r is at position xr,yr or can move there by its chosen direction
pred moveTo(x:Positions, y:Positions, d:Directions, 
  r:Robot, xr:Position, yr:Position) ⇔
  (xr = x[r] ∧ yr = y[r]) ∨
  (mayMove(x[r],y[r],d[r]) ∧ xr = moveX(x[r],d[r]) ∧ yr = moveY(y[r],d[r]))
;

// any robot different from r can move to position xr, yr
pred anyTo(x:Positions, y:Positions, d:Directions,
  r:Robot, xr:Position, yr:Position) ⇔
  ∃r0: Robot with r0 ≠ r. moveTo(x, y, d, r0, xr, yr)
;
  
// the relation between the current system state and the new direction dr of robot r
pred nextDir(x:Positions, y:Positions, d: Directions, r:Robot, dr:Direction) ⇔ 
  mayMove(x[r],y[r],dr) ∧
  let xr = moveX(x[r],dr), yr = moveY(y[r],dr) in
  ¬anyTo(x,y,d,r,xr,yr) 
;

// the system invariant (definition see below)
pred inv(x:Positions, y:Positions, d:Directions);

// --------------------------------------------------------------------------------
// an operational characterization of the system and
// a verification that the system is safe for a certain number of steps
// --------------------------------------------------------------------------------

// a system of robots moving in a grid starting at x0/y0 with directions d0
val N:ℕ; // the system will execute N+1 steps
proc system(x0: Positions, y0: Positions, d0: Directions): ()
  requires init(x0, y0, d0);
{
  // set initial state of the system
  var x: Positions = x0;
  var y: Positions = y0;
  var d: Directions = d0;
  
  // for printing error traces only
  // var xs: Array[N+1,Positions] ≔ Array[N+1,Positions](Array[R,Position](0));
  // var ys: Array[N+1,Positions] ≔ Array[N+1,Positions](Array[R,Position](0));
  // var ds: Array[N+1,Directions] ≔ Array[N+1,Directions](Array[R,Direction](Direction!Stop));
  // var rs: Array[N+1,Robot] = Array[N+1,Robot](0);
  
  // let the system execute a number of steps
  for var i:ℕ[N] ≔ 0; i < N; i ≔ i+1 do
    // invariant inv(x, y, d);
  {    
    // choose any robot and move it in its selected direction
    choose r: Robot;
    // xs[i] ≔ x; ys[i] ≔ y; ds[i] ≔ d; rs[i] := r;
    x ≔ x with [r] = moveX(x[r], d[r]);
    y ≔ y with [r] = moveY(y[r], d[r]);

    // check the system's safety property
    // if ¬nocollision(x, y) then print i, xs, ys, ds, rs;
    assert noCollision(x, y);
    
    // choose any new direction for the robot
    choose dr: Direction with nextDir(x, y, d, r, dr);
    d[r] ≔ dr;
  }
}

// --------------------------------------------------------------------------------
// a logical characterization of the system by its 
// initial state condition (given above) and its transition relation
// --------------------------------------------------------------------------------
  
// the relationship between the prestate of the system and its poststate
pred next(x:Positions, y:Positions, d:Directions,
  x0:Positions, y0:Positions, d0:Directions) ⇔
  ∃r:Robot.
    x0 = x with [r] = moveX(x[r], d[r]) ∧ 
    y0 = y with [r] = moveY(y[r], d[r]) ∧
    ∃dr: Direction with nextDir(x0, y0, d, r, dr). 
      d0 = d with [r] = dr;

// --------------------------------------------------------------------------------
// verification that system is safe for an infinite number of steps
// --------------------------------------------------------------------------------

// the system invariant
pred inv(x:Positions, y:Positions, d:Directions) ⇔
  noCollision(x, y) ∧
  ∀r:Robot. mayMove(x[r], y[r], d[r]) ∧
    let xr = moveX(x[r], d[r]), yr = moveY(y[r], d[r]) in
    ¬anyTo(x, y, d, r, xr, yr);

// the system invariant implies the desired safety property
theorem invIsStrongEnough(x:Positions, y:Positions, d:Directions) ⇔
  inv(x, y, d) ⇒ noCollision(x, y);
  
// the system invariant is inductive:
// it holds in its initial state and is preserved by the transition relation
theorem invHoldsInitially(x:Positions, y:Positions, d:Directions) ⇔
  init(x, y, d) ⇒ inv(x, y, d);
theorem invIsPreserved(x:Positions, y:Positions, d:Directions) ⇔ 
  inv(x, y, d) ⇒ 
  // ∀x0:Positions, y0:Positions, d0:Directions. 
  // next(x, y, d, x0, y0, d0) ⇒ inv(x0, y0, d0);
  ∀r:Robot. 
    let x0 = x with [r] = moveX(x[r], d[r]), y0 = y with [r] = moveY(y[r], d[r]) in
    ∀dr:Direction with nextDir(x0, y0, d, r, dr).
      let d0 = d with [r] = dr in
      inv(x0, y0, d0);

// --------------------------------------------------------------------------------
// end of file
// --------------------------------------------------------------------------------







