Métodos Formais em Engenharia de Software |
---|
Cálculo de Sistemas de Informação |
Programação Ciber-física |
Verificação Formal |
Calendário | Sumários |
Anos anteriores |
Alumni |
Um algoritmo que calcula o valor absoluto de um inteiro n
, colocando o resultado em r
:
var r : integer = n;
if r < 0 then r := -r
Um programa em C que usa o algoritmo acima para imprimir o valor absoluto de um número introduzido pelo utilizador:
#include <stdio.h>
int main() {
int n,r;
scanf("%d",&n);
r = n;
if (r < 0) r = -r;
printf("%d\n",r);
return 0;
}
Um algoritmo que multiplica um inteiro não negativo a
por b
, colocando o resultado em r
:
var
r : integer = 0;
n : integer = 0;
while n < a do
begin
n := n + 1;
r := r + b
end
Um programa em C que usa o algoritmo acima para imprimir o produto de dois números introduzidos pelo utilizador:
#include <stdio.h>
int prod(int a, int b) {
int r = 0;
for (int n = 0; n < a; n++) r += b;
return r;
}
int main() {
int a,b;
scanf("%d",&a);
scanf("%d",&b);
printf("%d\n",prod(a,b));
return 0;
}
Um algoritmo concorrente com 2 processos que incrementam uma variável partilhada x
:
var x : integer = 0
procedure increment;
begin
x := x + 1
end;
increment || increment
O um programa em C que implementa este algoritmo:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
int main() {
int *x = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
*x = 0;
if (fork() == 0) {
(*x)++;
return 0;
}
if (fork() == 0) {
(*x)++;
return 0;
}
while (wait(NULL) > 0);
printf("%d\n",*x);
return 0;
}
Discussão:
Para mais discussão sobre algoritmos vs programas veja os minutos iniciais deste vídeo.
Tipicamente algoritmos sequenciais são especificados e verificados de forma dedutiva com lógica de Hoare.
Por exemplo, o algoritmo para calcular o valor absoluto pode ser especificado com as seguintes pré- e pós-condições.
{ true }
var r : integer = n;
if r < 0 then r := -r
{ r = abs(n) }
Para o caso da multiplicação temos:
{ a >= 0 }
var
r : integer = 0;
n : integer = 0;
while n < a do
begin
n := n + 1;
r := r + b
end
{ r = a * b }
Para verificar a correção parcial de algoritmos com ciclos precisamos de invariantes de ciclo. Para verificação a sua terminação precisamos de variantes de ciclo. No caso da multiplicação um possível invariante seria r = n * b /\ n <= a
e um possível variante seria a - n
.
Os exemplos acima verificados com o Why3:
Exercício: verifique os seguintes algoritmos com o Why3:
Como verificar algoritmos reativos, que reagem continuamente a estímulos do ambiente ou utilizador?
Considere o seguinte puzzle da cadeira pesada: num mapa quadriculado temos uma cadeira pesada que ocupa uma quadrícula; o utilizador pode em cada momento escolher rodar a cadeira 90º para a esquerda ou para a direita sobre qualquer uma das 4 pernas. Queremos verificar que não é possível colocar a cadeira na quadrícula imediatamente ao lado direito com a mesma orientação que estava inicialmente.
Este puzzle pode ser descrito pelo seguinte algoritmo, onde usamos não determinismo para abstrair o comportamento do utilizador.
var
x : integer = 0;
y : integer = 0;
o : integer = 0;
while true do
begin
if true then
o := mod (o+1) 4
or else
o := mod (o-1) 4;
case random(0..3) of
0: y := y - 1;
1: x := x + 1;
2: x := x - 1;
3: y := y + 1;
end
end
Neste tipo de algoritmos não faz sentido falar em pós-condições nem em terminação. No entanto a propriedade que queremos verificar é de facto um invariante do ciclo, nomeadamente not (x = 1 and y = 0 and o = 0)
, e podemos usar lógica de Hoare para a verificar. O problema é que este invariante não é indutivo e será necessário descobrir um invariante mais forte (i.e. que o implique) que seja indutivo.
Exercício: tente descobrir esse invariante mais forte e verificar este algoritmo usando o Why3.
Exercício: haverá alguma versão mais abstrata do algoritmo deste puzzle onde a propriedade desejada seja trivial de verificar?
O algoritmo concorrente apresentado acima pode ser especificado da seguinte forma.
{ true }
var
x : integer = 0;
procedure increment;
begin
x := x + 1
end;
increment || increment
{ x = 2 }
Como verificar este algoritmo com lógica de Hoare? De facto podemos modelar este algoritmo concorrente por um algoritmo sequencial que em cada momento escolhe de forma não determinística qual o processo a executar. Para tal temos que saber em que ponto de execução se encontra cada um dos processos.
{ true }
var
x : integer = 0;
pc0 : 0..1 = 0;
pc1 : 0..1 = 0;
while not (pc0 = 1 and pc1 = 1) do
begin
if pc0 = 0 and pc1 = 0 then
begin
if true then
begin
x := x + 1;
pc0 = 1
end
or else
begin
x := x + 1;
pc1 = 1
end
end
else if pc1 = 1 then
begin
x := x + 1;
pc0 = 1
end
else
begin
x := x + 1;
pc1 = 1
end
end;
{ x = 2 }
Exercício: tente descobrir o invariante e o variante que permitem provar a correção deste algoritmo usando o Why3.
Exercício: tente modelar com um algoritmo sequencial não-determinístico a seguinte variante do algoritmo acima, onde cada processo começa por fazer uma cópia local da variável partilhada x
.
{ true }
var
x : integer = 0;
procedure increment;
var
y : integer = 0;
begin
y := x;
x := y + 1
end;
increment || increment
{ x = 2 }
Exercício: consegue verificar a pós-condição usando o Why3? será que a pós-condição não é verdade? nesse caso, especifique a pós-condição mais forte que este algoritmo garante e verifique-a com o Why3.
Considere o seguinte algoritmo de exclusão mútua de Peterson.
var
turn : 0..1;
want0 : boolean = false;
want1 : boolean = false;
procedure process0;
begin
while true do
begin
(* idle *)
want0 := true;
turn := 1;
repeat until (not want1 or turn = 0);
(* critical *)
want0 := false
end
procedure process1;
begin
while true do
begin
(* idle *)
want1 := true;
turn := 0;
repeat until (not want0 or turn = 1);
(* critical *)
want1 := false
end
process0 || process1
Discussão: como especificar e verificar as propriedades fundamentais deste algoritmo?