Funciones S-computables

Anuncio
Funciones S-computables
Herman Schinca
Viernes 9 de Septiembre de 2011
Ejercicio 1. Sea etiquetasUND : N → N la función que toma la codificación de un programa #p
y devuelve la codificación de una secuencia (sin repetidos) con las etiquetas que son usadas en p
(en algún GOTO) pero que no están definidas en el mismo. Demostrar que es p.r.
Resolución. Primero que nada pensemos qué herramientas tenemos para probar esto. ¿Podemos
escribir un programa en S para probar que es p.r? No! Eso sólo probarı́a que es S-computable.
No tenemos manera de, a partir de un programa en S, asegurar que una función sea p.r.
Otra manera posible serı́a usar la definición de función primitiva recursiva: construirla a partir
de las funciones iniciales, composición y recursión primitiva. Esta opción parece demasiado trabajosa. Recordemos que podemos (y está fuertemente recomendado) usar nuestras herramientas
más poderosas: minimización acotada, cuantificadores acotados, etc.
Sin importar que tan “poderosas” sean nuestras herramientas parece muy difı́cil calcular la
función pedida de manera constructiva. Intentaremos una técnica conocida como Generate and
Test (generar y probar o ensayo y error ) que consiste en lo siguiente. Si podemos
Generar posibles soluciones (en este caso, secuencias de etiquetas).
Describir la solución que buscamos y verificar si una posible solución dada es correcta.
Entonces
Generamos todas las posibles soluciones y verificamos hasta encontrar la que queremos.
Intentemos, por lo tanto, describir la solución que buscamos de manera primitiva recursiva.
Vayamos de a poco completando la especificación de nuestra función
etiquetasUND(#p)
=
secuencia s tal que: sinRepetidos(s) y
para toda etiqueta e: e ∈ s si y solo si
esUsada(e, #p) ∧ ¬estaDefinida(e,#p)
Recordemos que las secuencias son números naturales entonces si recorremos los naturales y
los interpretamos como secuencias estarı́amos recorriendo las posibles soluciones al problema. Lo
mismo ocurre con las etiquetas. Entonces, escribamos la función de una manera más precisa.
etiquetasUND(#p) = mins≤k1 sinRepetidos(s) ∧
∀e≤k2 (esEtiqueta(e) ⇒ (e ∈ s ⇔ (esUsada(e, #p) ∧ ¬estaDefinida(e, #p))))
Para que la función sea p.r. sólo podemos usar minimización y operadores acotados. La lista de
etiquetas no puede ser más grande que el programa ya que el programa es una lista de instrucciones
que incluyen a las etiquetas. Por lo tanto, k1 = #p es una buena cota. Por la misma razón, solo nos
interesa considerar las etiquetas que podrı́an aparecer en el programa. De esta manera, k2 = #p
deberı́a ser una buena cota.
Sólo nos queda definir las funciones auxiliares de manera primitiva recursiva. Recordemos que
la codificación de un programa P como un número natural es
#(P ) = [#(I1 ), . . . , #(Ik )] − 1
y codificamos a la instrucción I con #(I) = ha, hb, cii donde
1
1. si I tiene etiqueta L, entonces a = #(L); si no a = 0 (las etiquetas tienen # > 0)
2. si la variable mencionada en I es V entonces c = #(V ) − 1
3. si la instrucción I es
a)
b)
c)
d)
V ← V entonces b = 0
V ← V + 1 entonces b = 1
V ← V − 1 entonces b = 2
IF V 6= 0 GOTO L0 entonces b = #(L0 ) + 2
Las funciones auxiliares que dependen de la codificación de programas quedarı́an definidas
como
estaDefinida(e, #p)
esUsada(e, #p)
= ∃n≤|#p+1| l((#p + 1)[n]) = e
= ∃n≤|#p+1| l(r((#p + 1)[n])) = e + 2
El resto de los auxiliares pueden definirse como
sinRepetidos(s)
esEtiqueta(e)
= ∀i≤|s| ∀j≤|s| (i 6= j ⇒ si 6= sj )
= e>0
Esto prueba que la función que definimos es primitiva recursiva. Aún faltarı́a probar que se
comporta exactamente como queremos. En este caso, como el objetivo del ejercicio era aprender
la técnica de Generate and Test, dejaremos esa demostración como tarea para el lector.
Ejercicio 2. Demostrar que la


1
f (#p, z) =


↑
función f : N2 → N
si para alguna entrada x y algún momento t de
la ejecución de p pasa que la variable Y > z
si no
es parcial computable.
Resolución. Siempre es recomendable evitar “programar en S” ya que es engorroso, fácil de equivocarse y difı́cil de probar su correctitud. En cambio, trataremos de atacar el problema como el
punto anterior.
h(#p, z) = existe x, existe t tal que en el paso t de ejecutar p vale Y > z
En este caso no parece haber una cota para x y t. Ya que el tamaño de la entrada y el tiempo
que tome el programa en alcanzar Y > z puede ser arbitrariamente grande. Incluso podrı́a suceder
que para toda entrada y tiempo pase Y ≤ z, en tal caso el programa deberı́a colgarse. Reescribamos
esta última ecuación usando minimizaciones no acotadas.
h(#p, z) = ∃x∃t en el paso t de ejecutar p vale Y > z
Para saber el valor de alguna variable en un momento dado podemos usar la función
SNAP(x, #p, t) = hi, σi donde σ[#V ] = ‘el valor de la variable V ’
que devuelve un par con el número de instrucción y el estado de las variables luego de ejecutar t
pasos del programa p con entrada x. Esta función fue probada p.r. en la teórica. Reescribiendo lo
anterior quedarı́a
h(#p, z) = ∃x∃t(r(SNAP(x, #p, t))[1] > z)
{z
}
|
hi,σi
¿Listo?
2
Observemos lo siguiente: los existenciales no están acotados porque queremos que “busquen”
todas las combinaciones posibles de valores x, t. Tenemos un problema con el comportamiento
de los existenciales. No debemos olvidar que, en el fondo hay un ‘programa en S’ que simula los
existenciales. Según la definición de la teórica de la minimización no acotada, el comportamiento
de nuestro programa serı́a el siguiente:
1.
2.
3.
4.
Inicia x = 0, intenta validar el predicado que esta luego de ∃x.
Inicia t = 0. Valida la condición SNAP(x, #p, t))[1] > z. Si es verdadera devuelve 1.
Si era falsa prueba con t = 1.
Si era falsa prueba con t = 2.
..
.
¡No prueba todas las combinaciones! Se queda probando los distintos valores de t pero no
cambia nunca el x. Esto puede ser un gran problema ya que si justo la función devolvı́a 1 en
(x = 1, t = 1) en nuestro caso quedarı́a indefinida. ¿Cómo podemos, entonces, recorrer todas las
combinaciones?
Podemos aprovechar que la codificación de pares está en biyección con los naturales. Por lo
tanto, si n recorre los naturales y luego lo interpretamos como n = ha, bi estarı́amos recorriendo
todas las combinaciones de posibles a, b. Esta técnica se llama dovetailing. Podemos reescribir
nuestra función como
h(#p, z) = ∃ |{z}
n (r(SNAP(l(n) , #p, r(n)))[1] > z)
|{z}
|{z}
hx,ti
x
t
También admitiremos el siguiente abuso de notación para simplificar la notación
h(#p, z) = ∃hx,ti (r(SNAP(x, #p, t))[1] > z)
Ya que formamos h a partir de funciones total computables (en particular, p.r.) más un operador no acotado con esto probamos que h es parcial computable. Sólo nos queda demostrar la
correctitud, es decir, que f = h. Para la ida veamos que si f (#p, z) = 1 entonces h(#p, z) = 1.
Si f (#p, z) = 1, entonces existe una entrada x1 y un tiempo t1 en el que Y > z. Por lo tanto
‘r(SNAP(x1 , #p, t1 ))[1] > z’ es verdadero. Como el existencial no acotado recorre todos los
valores de x y t, cuando llegue a (x = x1 , t = t1 ) será verdadero el predicado interno y el
programa devolverá 1. Entonces h(#p, z) = 1.
Para la vuelta probemos que si h(#p, z) = 1 entonces f (#p, z) = 1.
Si h(#p, z) = 1, entonces existen x1 , t1 para los cuales ‘r(SNAP(x1 , #p, t1 ))[1] > z’ es
verdadero. Ya vimos que esa expresión es verdadera justamente cuando a los t1 pasos de la
ejecución de p con entrada x1 la variable Y > z. Entonces f (#p, z) = 1.
3
Descargar