Library theories.re_dec
Vivam! Este pdf serve como um documento que vos explique qual a minha ideia para implementar o algoritmo de decisão para as res. Na realidade, a ideia na sua essência não é minha. É sim a adaptação de um dos 4 métodos standard para a implementação de programas recursivos gerais na framework dos programas dotados de recursão estrutural, como é o caso do Coq. O método é o da construção de um iterador e está descrito na secção 15.3 do Coq'Art.
A ideia desta técnica é seguir os seguintes três passos:
O truque desta técnica está em usar indução bem fundada para descrever de forma estruturalmente decrescente as várias iterações do algoritmo. Esta descrição é, na realidade, codificada como termos do predicado Acc, que representa a noção de acessibilidade numa cadeia finita decrescente de valores.
Agora vou passar a descrever os vários componentes que necessitei de codificar para que a implementação do algoritmo siga, o mais à risca possível, os passos da descrição feita no Coq'Art.
O algoritmo para a decisão da equivalência de res. (trate-mo-lo a partir de agora com AREdec) resume-se ao seguinte comportamento (simplificado): primeiro, e dadas duas res, decorre uma pequena fase de inicialização de variáveis :
De seguida vou apresentar as funções que vão permitir construir os vários pontos acima.
Por partilha de linguagem constante entre dois conjuntos de res. entende-se que ou ambos os conjuntos aceitam a linguagem vazia , ou nenhum dos conjuntos a aceita. Esta função é fundamental para encontrar contra-exemplos no cálculo da equivalência de duas res. Do resultado da função f_c_of_re_pair, definida abaixo, é possível construír uma proposição que exibe o facto de as res. dadas como argumento não serem equivalentes.
Ideia do método
A ideia desta técnica é seguir os seguintes três passos:
- determinar o funcional associado à função recursiva que se pretende implementar;
- demonstrar que a função recursiva termina, ou é uma função total. Para tal usa-se o apenas funcional definido no ponto anterior;
- construir a função alvo, usando os dois itens anteriores.
O truque desta técnica está em usar indução bem fundada para descrever de forma estruturalmente decrescente as várias iterações do algoritmo. Esta descrição é, na realidade, codificada como termos do predicado Acc, que representa a noção de acessibilidade numa cadeia finita decrescente de valores.
Agora vou passar a descrever os vários componentes que necessitei de codificar para que a implementação do algoritmo siga, o mais à risca possível, os passos da descrição feita no Coq'Art.
Funções úteis
O algoritmo para a decisão da equivalência de res. (trate-mo-lo a partir de agora com AREdec) resume-se ao seguinte comportamento (simplificado): primeiro, e dadas duas res, decorre uma pequena fase de inicialização de variáveis :
- inicializa um conjunto S com um par contendo as duas res. dadas como argumentos. Este conjunto irá conter ao longo da execução todos os pares que ainda não foram tratados.
- inicialização de um conjunto vazio H que ao longo das interções de AREdec irá acumular todos os pares já "tratatos". Uma vez que um determinado par é adicionado a H, caso volte a ser considerado em futuras iterações do algoritmo, é imediatamente ignorado e é considerado um novo valor de S. Esta restrição evita que AREdec entre em ciclo, pois não existe repetição de pares e também porque H tem um número finito de elementos.
- o conjunto S fica vazio e portanto não existem mais pares a serem processados;
- é encontrado um par onde não existe partilha de linguagem constante.
De seguida vou apresentar as funções que vão permitir construir os vários pontos acima.
Partilha da linguagem constante
Por partilha de linguagem constante entre dois conjuntos de res. entende-se que ou ambos os conjuntos aceitam a linguagem vazia , ou nenhum dos conjuntos a aceita. Esta função é fundamental para encontrar contra-exemplos no cálculo da equivalência de duas res. Do resultado da função f_c_of_re_pair, definida abaixo, é possível construír uma proposição que exibe o facto de as res. dadas como argumento não serem equivalentes.
Definition f_c_of_re_pair (x:repset.elt) :=
match c_of_re_set (fst x) , c_of_re_set (snd x) with
| true , true ⇒ true
| false , false ⇒ true
| _ , _ ⇒ false
end.
Adição condicional de novos pares
Já foi acima referido que novos pares que já tenham sido processador em iterações anteriores do algoritmo, i.e., elementos de H, não considerados. Esta restrição aplica-se quando se constroem os novos pares de derivadas com base no alfabeto . A geração das derivadas é implementada através de um fold da lista que representa , e cuja função a aplicar é a adição de novos elementos que não estejam já presentes no histórico.Definition add_to_H (x:prod Sre Sre)(H:Srep) :=
match repsetp.In_dec x H with
| left _ ⇒ fun x ⇒ x
| right _ ⇒ repset.add x
end.
A função anterior devolve a função de adição de um novo elemento a um qualquer conjunto, considerando que não se encontra no histórico H. Essa função é depois utilizada na função genDrvs que produz novos pares de conjuntos de derivadas de res.
Definition genDrvs (x:prod Sre Sre)(s:list A)(H:Srep) :=
fold_right (fun a ⇒ add_to_H (pair ((fst x) %dS a) ((snd x) %dS a)) H) [{}$] s.
Lemma In_genDrvs_not_in_H : ∀ x e H,
x ?$ genDrvs e sigma H → x !?$ H.
Pares possíveis
A prova da terminação do AREdec assenta no facto de o número de possíveis combinações de derivadas de um qualquer par de res é sempre finito e limitado pelo número de símbolos existentes em e . A totalidade dos pares possíveis de conjuntos de derivadas de um par e dados pelas seguintes duas funções:
Definition powPI (r1 r2:re) :=
((powerset (PI r1))[X$](powerset (PI r2))).
Definition powPD (r1 r2:re) :=
([!r1],[!r2])[@$]powPI r1 r2.
Definition powPIset (x y:Sre) :=
powerset (PIset x)[X$](powerset (PIset y)).
Definition powPDset (x y:Sre) :=
(x,y)[@$]powPIset x y.
Agora são apresentadas algumas das propriedades lógicas importantes que estão relacionadas com as produção de novos pares de derivadas. Essas propriedades são as seguintes:
- as derivadas de um par de conjuntos com um só elemento é sub-conjunto de todos os pares de derivadas por um símbolo
Lemma genDrvsInPIprod : ∀ x y H,
genDrvs ([!x],[!y]) sigma H [<=$] powPI x y.
genDrvs ([!x],[!y]) sigma H [<=$] powPI x y.
- para cada novo par de conjuntos de res. produzido por genDrvs é possível saber qual o símbolo que originou a derivada que corresponde a
Lemma genDrvsSet : ∀ x y z t H,
(x,y) ?$ genDrvs (z,t) sigma H → ∃ a,
x[=](z %dS a) ∧ y[=](t %dS a).
(x,y) ?$ genDrvs (z,t) sigma H → ∃ a,
x[=](z %dS a) ∧ y[=](t %dS a).
- todos os novos pares produzidos a partir de um par da totalidade dos pares de derivadas são elementos deste último. Esta propriedade garante a finitude dos elementos que podem ser considerados ao longo de toda a execução de AREdec
Lemma genDrvsInPIprod_in_PI : ∀ x y z t H,
(z,t) ?$ powPI x y →
genDrvs (z,t) sigma H [<=$] powPI x y.
Lemma genDrvsInPDprod_in_PD : ∀ x y z t H,
(z,t) ?$ powPD x y →
genDrvs (z,t) sigma H [<=$] powPD x y.
(z,t) ?$ powPI x y →
genDrvs (z,t) sigma H [<=$] powPI x y.
Lemma genDrvsInPDprod_in_PD : ∀ x y z t H,
(z,t) ?$ powPD x y →
genDrvs (z,t) sigma H [<=$] powPD x y.
Geração de novos pares a serem considerados
Usando a definição genDrvs introduzida na secção anterior, podemos agora finalizar a função que produz novos pares de derivadas que poderão a ser processados no futuro pelo algoritmo. Assim, a função genDrvsPairs devolve um valor parcial, dependendo do resultado da partilha da linguagem constante dos conjuntos de res. dados como argumento. Caso não partilhem a mesma linguagem constante, é devolvido o termo None que determina que um contra-exemplo foi encontrado. Caso contrário, é devolvida um termo que é o resultado da execução de genDrvs, isto é, um conjunto de pares de derivadas não presentes no histórico H.
Definition genDrvPairs (x:repset.elt)(H:Srep):option Srep :=
if f_c_of_re_pair x then
Some (genDrvs x sigma H)
else
None.
O seguinte lema dá-nos a análise de casos possíveis para o reultado da execução de genDrvPairs, que no caso afirmativo também fornece uma prova de que os termos que constituem o resultado são um subconjunto da totalidade das derivadas. O termo que define o caso negativo não está ainda completo. Na versão final erá ser substituido por um tipo- que contém um certificado de que o facto do par que foi processado não partilhar a linguagem constante implica a não equivalência das respectivas res.
Lemma genDrvPairs_dec : ∀ x y H,
{e | genDrvPairs (x,y) H = Some e ∧ e[<=$]powPIset x y} + {genDrvPairs (x,y) H = None}.
{e | genDrvPairs (x,y) H = Some e ∧ e[<=$]powPIset x y} + {genDrvPairs (x,y) H = None}.
Agora implementamos uma função que descreve uma iteração do algoritmo. O resultado vai ser um triplo, cujos componentes são:
- um valor booleano parcial, estabelecendo se deve ou não existir uma nova iteração e, no caso de terminação, devolver o valor correspondente à avaliação da equivalência das res. dadas como argumentos;
- o conjunto actualiazado de para serem processados na próxima iteração do algoritmo ;
- o histórico dos pares já processados
Definition alg_step (s:Srep)(H:Srep) :=
match [<-$] s with
| None ⇒
(true,[{}$],H)
| Some e ⇒
if f_c_of_re_pair e then
(true,(genDrvs e sigma (e[@$]H))[+$](repset.remove e s),e[@$]H)
else
(false,repset.remove e s,H)
end .
Notation ">>[ S , H ]" := (alg_step S H) (at level 45,right associativity).
Lemma alg_step_S_empty_val : ∀ S H,
[<-$]S = None → fst (fst (alg_step S H)) = true.
match [<-$] s with
| None ⇒
(true,[{}$],H)
| Some e ⇒
if f_c_of_re_pair e then
(true,(genDrvs e sigma (e[@$]H))[+$](repset.remove e s),e[@$]H)
else
(false,repset.remove e s,H)
end .
Notation ">>[ S , H ]" := (alg_step S H) (at level 45,right associativity).
Lemma alg_step_S_empty_val : ∀ S H,
[<-$]S = None → fst (fst (alg_step S H)) = true.
Lemma alg_step_S_empty_S : ∀ S H,
[<-$]S = None → snd (fst (alg_step S H)) [=$] [{}$].
Lemma alg_step_S_empty_H : ∀ S H,
[<-$]S = None → snd (alg_step S H) [=$] H.
Lemma alg_step_S_Some_fcpair_false : ∀ S H e,
[<-$]S = Some e →
f_c_of_re_pair e = false → fst (fst (>>[S,H])) = false.
Lemma alg_step_S_Some_fcpair_true_1 : ∀ S H e,
[<-$]S = Some e →
f_c_of_re_pair e = true →
snd (fst (>>[S,H])) [=$] ((genDrvs e sigma (e[@$]H)[+$]repset.remove e S)).
Lemma alg_step_S_Some_fcpair_true_2 : ∀ S H e,
[<-$]S = Some e →
f_c_of_re_pair e = true →
(∀ a, (fst e %dS a,snd e %dS a) ?$ (e[@$]H)) → snd (fst (>>[S,H])) [=$] repset.remove e S.
Lemma alg_step_always_in_powPD_H : ∀ x y S H,
S[<=$]powPD x y → H[<=$]powPD x y → snd (alg_step S H)[<=$]powPD x y.
Lemma alg_step_always_in_powPD_S : ∀ x y S H,
S[<=$]powPD x y → H[<=$]powPD x y → snd (fst (alg_step S H))[<=$]powPD x y.
Lemma alg_step_always_in_powPD_H_true : ∀ S H,
(∀ x, x ?$ H → f_c_of_re_pair x = true) → ∀ z, z ?$ snd (alg_step S H) → f_c_of_re_pair z = true.
Lemma alg_step_stack_history_disj : ∀ S H,
S[&$]H [=$] [{}$] → snd (fst (alg_step S H)) [&$] snd (alg_step S H) [=$] [{}$].
Pares de res. certificados
Agora define-se a estrutura de dados a usar no algoritmo. Basicamente corresponde a um triplo , onde as variáveis , e definem, respectivamente, o indicador de uma nova iteração do algoritmo, ou a terminação do mesmo; o conjunto de pares que ainda podem vir a ser considerados pelo algoritmo antes de este terminar; o histórico de pares já processados pelo algoritmo. Juntamente com estas variáveis, o tiplo algT incluí provas da finitude e correcção dos pares considerados em cada iteração do algoritmo.
Record algT (r1 r2:re) : Type := mk_algT {
v : option bool ;
Stack : Srep ;
History : Srep ;
Stk_in_PIpow : Stack[<=$](powPD r1 r2);
Hist_in_PIpow : History[<=$](powPD r1 r2) ;
Hist_share_eps : ∀ x, x ?$ History → f_c_of_re_pair x = true ;
Hist_Stk_disj : Stack [&$] History [=$] [{}$]
}.
Como vimos anteriormente, existe uma fase de inicialização do algoritmo AREdec. Essa inicialização prende-se com a criação dos conjuntos Stack e History, o primeiro contendo o par formado pelas res. dadas como argumento ao algoritmo de decisão e o segundo sendo vazio.
Definition alg_start (r1 r2:re) : algT r1 r2.
Agora é a definição da execução de uma iteração do algoritmo. Esta função implica a construção de demonstrações que garantem a boa formação dos elementos que irão dar origem à veracidade ou não da equivalência das res. r1 e r2
Definition algT_step (r1 r2:re)(x:algT r1 r2) : algT r1 r2.
Lemma History_monotone : ∀ r1 r2 x (L:algT r1 r2),
x ?$ History r1 r2 L →
x ?$ History r1 r2 (algT_step r1 r2 L).
Lemma Stack_increases_pdrvs_in_Hist : ∀ r1 r2 H e,
Stack r1 r2 H [<=$] Stack r1 r2 (algT_step r1 r2 H) →
[<-$](Stack r1 r2 H) = Some e → f_c_of_re_pair e = true →
~(forall a, (fst e %dS a,snd e %dS a) ?$ (e[@$]History r1 r2 H)).
Lemma Stack_decreases_pdrvs_in_Hist : ∀ r1 r2 H e,
Stack r1 r2 (algT_step r1 r2 H) [<=$] Stack r1 r2 H →
[<-$](Stack r1 r2 H) = Some e → f_c_of_re_pair e = true →
∀ x, x ?$ genDrvs e sigma (e[@$]History r1 r2 H) → x !?$ (e[@$]History r1 r2 H).
Definição do funcional
Agora definimos o funcional que define a iteração do algoritmo um determinado número de vezes. O funcional termina pois está definido por indução estrutural no número natural que determina o número de iterações a executar.
Fixpoint n_iter_algT (r1 r2:re)(F:algT r1 r2 → algT r1 r2)(inp:algT r1 r2)(n:nat) :=
match n with
| O ⇒ inp
| S m ⇒ n_iter_algT r1 r2 F (F inp) m
end.
Agora seguem-se alguns lemas que definem a especificação lógica da função alg_step.
Lemma n_iter_algTHist_in_powPD : ∀ r1 r2 n,
History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n) [<=$] powPD r1 r2.
Lemma n_iter_algTStack_in_powPD : ∀ r1 r2 n,
Stack r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n) [<=$] powPD r1 r2.
Lemma n_iter_algTHist_valid : ∀ r1 r2 x n,
x ?$ (History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n)) → f_c_of_re_pair x = true.
Lemma n_iter_algTHist_Stack_disj : ∀ r1 r2 n,
(Stack r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n))[&$]
(History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n))
[=$][{}$].
Open Scope nat_scope.
Lemma rel_algT : ∀ r1 r2 H,
(#$(History r1 r2 H)) = (#$(History r1 r2 (algT_step r1 r2 H)))
∨
(
(#$(History r1 r2 H)) < (#$(History r1 r2 (algT_step r1 r2 H)))
∧
(#$(Stack r1 r2 H) > #$(Stack r1 r2 (algT_step r1 r2 H)))
).
Close Scope nat_scope.
History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n) [<=$] powPD r1 r2.
Lemma n_iter_algTStack_in_powPD : ∀ r1 r2 n,
Stack r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n) [<=$] powPD r1 r2.
Lemma n_iter_algTHist_valid : ∀ r1 r2 x n,
x ?$ (History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n)) → f_c_of_re_pair x = true.
Lemma n_iter_algTHist_Stack_disj : ∀ r1 r2 n,
(Stack r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n))[&$]
(History r1 r2 (n_iter_algT r1 r2 (algT_step r1 r2) (alg_start r1 r2) n))
[=$][{}$].
Open Scope nat_scope.
Lemma rel_algT : ∀ r1 r2 H,
(#$(History r1 r2 H)) = (#$(History r1 r2 (algT_step r1 r2 H)))
∨
(
(#$(History r1 r2 H)) < (#$(History r1 r2 (algT_step r1 r2 H)))
∧
(#$(Stack r1 r2 H) > #$(Stack r1 r2 (algT_step r1 r2 H)))
).
Close Scope nat_scope.
Invariants of the constant language for pairs of re sets
End ReDec.