Chapitre 3 : Récursion et théorème de Rice

TD : Théorème de Rice

On rappelle que décider a la même signification que calculer, lorsqu'on traite un problème avec seulement deux réponses possibles : oui ou non, vrai ou faux, etc. (un tel problème est souvent appelé problème de décision). Un ensemble d'entiers E est décidable si, par définition, il existe un algorithme (une procédure) pour décider si un entier x appartient ou non à E ; voir le début du chapitre 4 pour des détails.

Exercice 1 (corrigé ci-dessous). Voici une liste d'applications directes du théorème de Rice :

Ecrire explicitement (à l'aide de quantificateurs), dans chaque cas, le prédicat (indécidable) correspondant. Ecrire une procédure contradictoire, qui prouve par l'absurde le résultat d'indécidabilité, sur le modèle de la preuve du théorème de Rice.

Exercice 2 (corrigé ci-dessous). Peut-on décider si un ensemble décidable est fini ? On commencera par formuler rigoureusement le problème.

Exercice 3 (corrigé ci-dessous). Montrer en appliquant le théorème de Rice que l'ensemble des procédures qui calculent une même fonction f est infini.

Exercice 4 (corrigé ci-dessous). Prouver qu'on ne peut pas décider si deux procédures p et q calculent la même fonction.

Exercice 10 (corrigé ci-dessous). On rappelle qu'on peut assimiler une procédure p et son numéro, comme en langage C, où p désigne l'adresse de la procédure. Le test de comparaison p < q est donc valide, même s'il est inhabituel en programmation.

On s'intéresse aux procédures sans argument, ce qui ne les empêche pas de calculer un résultat ; par exemple :

int p (void) { return 33; }

int q (void) {
  int i, e = 1;
  for (i = 0; i < 10; i++)
    e = 2 * e;
  return e;
}

sont des procédures telles que p ( ) = 33, et q ( ) = 1024. Soit f la fonction définie par :

f (x) = min { p | p ( ) = x }

  1. Montrer que f est totale — autrement dit f (x) est défini pour tout x.
  2. Montrer que f n'est pas calculable. Indication : construire sinon une procédure γ (sans argument) contradictoire, c'est-à-dire telle que t = γ ( ), et pourtant γ < f (t).
  3. Il semble exister un algorithme simple pour calculer f (x) : parcourir toutes les procédures (sans argument) jusqu'à en trouver une qui calcule x ! Où est le bug ?

Exercice 11 (corrigé ci-dessous). Les conventions sont les mêmes que pour l'exercice précédent, et on considère la fonction g définie comme suit :

g (p) = max { q ( ) | qp }

  1. Montrer que g ( p ) est défini dès que p est suffisamment grand. La fonction g est croissante : expliquer pourquoi.
  2. Montrer que g n'est pas calculable. Indication : construire sinon une procédure γ (sans argument) contradictoire, c'est-à-dire telle que t = γ ( ), et pourtant g ( γ ) < t.
  3. Il semble exister un algorithme simple pour calculer g ( p ) : parcourir toutes les procédures q (sans argument) de numéro inférieur à p, et ajuster une variable max chaque fois que max < q ( ). Où est le bug ?

Exercice 20 (corrigé ci-dessous). Soit T une fonction calculable totale ; comme les procédures peuvent être numérotées, T peut être vue comme une transformation sur les procédures. Montrer qu'il existe une procédure p telle que p et T ( p ) calculent la même fonction. Note : ce résultat est appelé, dans ce contexte, théorème de point fixe.

Montrer que T possède en fait une infinité de points fixes (définis comme ci-dessus).

Solutions

Si votre navigateur n'affiche pas les quantificateurs et autres symboles logiques ci-dessous :

Une annexe technique donne quelques détails, et des références, sur le codage des symboles mathématiques et logiques en HTML.

Exercice 1 (énoncé ci-dessus).

Ces prédicats vérifient l'hypothèse fondamentale du théorème de Rice : leur valeur ne dépend que de la fonction calculée par la procédure p ; d'autre part aucun n'est trivial (c'est-à-dire toujours vrai ou toujours faux), donc le théorème de Rice permet de conclure qu'aucun de ces prédicats n'est calculable (autrement dit décidable).

On peut à chaque fois construire très simplement, en utilisant le principe de récursion, une procédure contradictoire γ, en supposant par l'absurde que le prédicat est calculable :

Note : toutes ces constructions sont des cas particuliers de la preuve du théorème de Rice.

Exercice 2 (énoncé ci-dessus). Un ensemble décidable est donné par une procédure qui calcule sa fonction caractéristique χ. On s'intéresse donc au prédicat Fini défini par :

Fini ( p ) := { x | p(x) = 1 } est un ensemble fini

Le théorème de Rice s'applique, ce prédicat n'est pas calculable, et on ne peut donc pas décider si un ensemble décidable est fini.

Exercice 3 (énoncé ci-dessus). L'ensemble des procédures qui calculent une même fonction f n'est pas décidable, d'après le théorème de Rice, donc en particulier cet ensemble est infini (tout ensemble fini est décidable).

Note : on peut préférer l'argument selon lequel on peut ajouter autant d'instructions inutiles que l'on veut à une procédure donnée, sans changer son résultat …

Exercice 4 (énoncé ci-dessus).

S'il existait un algorithme pour décider si deux procédures p et q calculent la même fonction, on pourrait décider par exemple si p calcule l'identité, en appliquant l'algorithme au cas où q est la procédure "return x". En détail :

int pmf (int p (int), int q (int) ) { … }
int id (int x) {return x; }
int pid (int p (int) ) {
  return pmf (p, id);
}

Si, par l'absurde, il existe une procédure pmf (mf est l'abréviation de même fonction) qui décide si p et q calculent la même fonction, alors on peut construire la procédure pid, comme ci-dessus, pour décider si p calcule l'identité.

Note : on peut résoudre aussi cet exercice en construisant directement un couple de procédures contradictoires gamma1 et gamma2 , qui calculent la même fonction si et seulement si pmf (gamma1, gamma2) dit le contraire (c'est-à-dire vaut 0). La méthode employée ci-dessus est plus rapide et intéressante : elle consiste en une réduction d'un problème A (la procédure p calcule-t-elle l'identité), à un problème B (les procédures p et q calculent-elles la même fonction) ; si B est décidable, la réduction fournit un algorithme pour A, qui est à son tour décidable ; donc inversement (c'est le cas ici), si A est indécidable, B l'est aussi !

Attention à la direction de la réduction (réduire A à B n'est pas du tout la même chose que réduire B à A), à la direction de l'implication pour la décidabilité éventuelle, et à la direction de l'implication pour l'indécidabilité, inverse de la précédente : PQ équivaut à ¬Q ⇒ ¬P (principe logique de contraposition).

Exercice 10 (énoncé ci-dessus).

  1. Tout entier x peut être calculé par une procédure sans argument, sur le modèle de la procédure qui calcule 33 dans l'énoncé de l'exercice.
  2. Supposons, par l'absurde, qu'il existe une procédure pf qui calcule f ; on peut alors construire la procédure contradictoire suivante :

    int gamma (void) {
      int x;
      for (x = 0; ; x++)
        if (gamma < pf (x))
          return x;
    }
    

    Note. Si vous n'aimez pas le test if ci-dessus, vous pouvez supposer que vous disposez d'une fonction de numérotation des procédures, et écrire :

    f (x) = min { numeroProcedure ( p ) | p ( ) = x }.

    Dans la définition de γ, on peut alors remplacer le test par :

    if ( numeroProcedure (gamma) < pf (x))

  3. L'algorithme serait correct si l'on savait éliminer, parmi les procédures examinées, celles qui bouclent. Or il n'existe pas d'algorithme pour cela (indécidabilité du problème de l'arrêt), et donc l'algorithme proposé va boucler en testant si q calcule x, lorsque l'exécution de q ne termine pas.

Exercice 11 (énoncé ci-dessus).

  1. g ( p ) est défini dès que le numéro de p est supérieur à celui de la première procédure (sans argument) qui termine. Lorsque (le numéro de) p augmente, l'ensemble dont on calcule le maximum augmente aussi (la relation d'ordre est l'inclusion) ; donc g est une fonction croissante.
  2. Supposons, par l'absurde, qu'il existe une procédure pg qui calcule g ; on peut alors construire la procédure contradictoire suivante :

    int gamma (void) {
      return 1 + pg (gamma);
    }
    
  3. Même argument que pour l'exercice précédent. L'algorithme proposé va boucler en testant si q calcule un entier supérieur à max, lorsque l'exécution de q ne termine pas.

Exercice 20 (énoncé ci-dessus). Il suffit de définir p comme suit :

int p (int x) {
  q = T (p);
  return q (x);
}

Si cette version ne vous plaît pas (pas de type pour q, et en général q ne sera pas une adresse de procédure valide), vous pouvez utiliser explicitement une fonction de numérotation des procédures, et son inverse :

int p (int x) {
  int n = numeroProcedure (p);
  q = procedureNumero (T (n));
  return q (x);
}

Pour prouver que la transformation T possède une infinité de points fixes, on peut simplement ajouter autant de calculs inutiles que l'on veut dans la procédure ci-dessus. On peut aussi, comme dans l'exercice 3, appliquer le théorème de Rice : dire que p est un point fixe de T, c'est dire, par définition, que p et T ( p ) calculent la même fonction. Donc le théorème de Rice s'applique, on ne peut pas décider si p est un point fixe de T (construisez une procédure contradictoire si vous n'êtes pas convaincu), donc l'ensemble des points fixes est infini.