Les équations de ce document sont affichées avec MathJax.
Poser les bonnes questions a toujours été d'une importance capitale en sciences.
L'informatique, entre autres, est née – pourrait-on dire – d'une question particulièrement intéressante sur la calculabilité, à savoir quels sont les nombres qui sont calculables?
Historiquement, on considère que ce problème fut posé par David Hilbert lors d'une allocation publique à Paris portant sur les 23 problèmes les plus importants des mathématiques de son époque, en 1900.
La réponse à ce problème particulier a été donnée en 1936 par Alan Turing dans son On Computable Numbers, with an application to the Entscheidungsproblem.
Turing est considéré comme le fondateur, par ricochet, de l'informatique au sens où on la connaît aujourd'hui. L'une de ses thèses, nommée la Church-Turing Thesis parce que rédigée conjointement avec Alonzo Church, a donné le ton aux recherches en intelligence artificielle.
Deux autres des grandes questions posées par David Hilbert, à savoir comment trouver si un système d'axiomatique formel est complet (s'il permet de prouver tout énoncé vrai) et si un système d'axiomatique formel est cohérent (s'il est impossible, avec ce système, de démontrer à la fois une proposition et sa négation), ont tous deux été résolus par Kurt Gödel, logicien de génie et contemporain et ami d'Albert Einstein (voir Les limites de ce qui est faisable, ci-dessous).
Il faut comprendre ici que David Hilbert croyait en une vision particulièrement stricte et formelle des mathématiques (et d'un truc qu'on nomme la métamathématique, et qui sert à exprimer les mathématiques), comme étant un jeu de symbole dénué de sens. Sa stratégie, à succès, sous-tend encore aujourd'hui le cursus mathématique universitaire.
Hilbert pensait, en 1900, que l'on avait presque résolu tous les problèmes importants des sciences, et que ce qui restait à résoudre le serait inévitablement. Pour lui, tout n'était qu'une question de temps et d'effort par quelques cerveaux importants.
Hilbert était confiant, entre autres, qu'on pourrait démontrer la complétude et la cohérence de l'arithmétique (opérations de base sur les entiers), du fait que Giuseppe Peano avait réussi, à cette époque, à exprimer formellement cette arithmétique bien particulière. La devise écrite sur la tombe de Hilbert, d'ailleurs, est « Nous devons savoir, et nous saurons ». Sachant exprimer clairement l'arithmétique des entiers, on pensait tenir la clé qui nous permettrait de démontrer ce qui, intuitivement, apparaissait comme évident.
On le sait aujourd'hui, grâce à Gödel, nous avions tort. L'arithmétique, comme tout système formel suffisamment fort, ne peut à la fois être complète et cohérente.
La preuve soumise par Perelman a été acceptée par un jury. Pour plus de détails, lire http://www.claymath.org/poincare/ et, pour mieux comprendre la conjecture, voir http://en.wikipedia.org/wiki/Solution_of_the_Poincar%C3%A9_conjecture.
Le site http://www.claymath.org/prizeproblems/ répertorie quelques uns des problèmes les plus importants des mathématiques et de l'informatique aujourd'hui. Un prix de 1 million de dollars est offert à la résolution de chacun d'entre eux.
L'un d'entre eux, la conjecture de Poincaré, a été résolu par Grigori Perelman, un personnage très particulier. Un autre, associé à la quête d'une solution pour les équations de Navier-Stokes, a été résolu par Mukhtarbai Otelbayev.
Un autre site, http://www.eff.org/coop-awards/prime-release1.htm, offre des montants d'argent intéressants pour des efforts collaboratifs de résolution de problèmes de calcul difficiles. Un petit texte amusant mettant en relief l'intérêt de ces problèmes se trouve sur http://www.studyworksonline.com/cda/content/article/0,,NAV4-44_SAR193,00.shtml.
Entre autres, le problème y apparaît, lui qui a été présenté (à juste titre, sans doute) par Donald Knuth comme étant, sous sa forme du problème du commis voyageur (en anglais: TSP, pour Traveling Salesman Problem), le problème le plus important de toute la discipline informatique.
Pour en savoir plus, voir PvsNP.html
Il existe des limites à ce qui peut être algorithmiquement réalisé. Ceci est une affirmation qui fait réagir les scientifiques de manière souvent adverse, à plus forte partie les informaticiens qui ont l'impression (pas si folle) de pouvoir, à l'aide d'une stratégie assez astucieuse, résoudre tous les problèmes.
Or, répétons-le, il y a des limites à ce qui peut être fait. Tel que mentionné plus haut, Kurt Gödel a, avec son théorème d'incomplétude (Über formal unentscheidbare Sätze der Principia Mathematica und verwandter Systeme), montré que tout système axiomatique au moins aussi fort que celui de l'arithmétique des nombres entiers ne peut à la fois être complet et cohérent – ce qui signifie essentiellement, présumant la cohérence de la logique mathématique[1], qu'il existe une infinité d'énoncé qui sont vrais mais ne peuvent être démontrés à partir d'un ensemble d'axiomes, peu importe l'ensemble d'axiomes choisi.
Ce résultat a envoyé une onde de choc à travers le monde scientifique tout entier, et fait encore énormément parler aujourd'hui, chacun ayant sa propre vision et sa propre compréhension des conséquences réelles de ce résultat. Certains, dont Roger Penrose, pensent qu'il s'agit d'une preuve que l'esprit humain échappe à l'algorithmique, et dépasse les limites possibles de toute mécanique – Penrose pense donc que ce résultat stipule que l'intelligence artificielle est utopique. D'autres (dont moi-même) font une lecture différente du résultat, ou du moins y voient là une détermination qui affecte aussi les humains.
Certains problèmes sont très complexes. On peut évaluer la complexité relative de différentes problèmes, grâce en grande partie à Alan Turing (voir Introduction à la complexité algorithmique). Il se trouve que certains problèmes sont algorithmiquement indécidables – donc qu'il ne peut y avoir, pour ces problèmes, de solution à l'aide d'une méthode formelle de solution, quelle qu'elle soit. On peut introduire une gradation entre plusieurs problèmes indécidables: on peut donc affirmer, en souriant, que «certains problèmes insolubles sont plus insolubles que d'autres»!.
Avec remerciements à mon chic professeur d'algorithmique à l'Université de Sherbrooke, il y a déjà longtemps, monsieur François Bédard, qui nous faisait à la fois suer fort et apprendre beaucoup, et qui a décidé en fin de session d'aller « hors manuel » et de nous faire réagir avec ceci. Des collègues ont claqué la porte, refusant d'assimiler cette idée que des problèmes existent qui sont algorithmiquement insolubles. Mais la démonstration est impitoyable...
Pourtant, depuis Gödel et (ce qui est particulièrement intéressant pour les informaticiennes et les informaticiens) depuis Turing, nous savons qu'il existe une infinité de tels problèmes. L'un des résultats les plus à propos de Turing à cet effet est ce qu'on nomme le problème de l'arrêt.
Pour une explication concise du théorème de Gödel, voir http://blog.plover.com/math/Gdl-Smullyan.html
Pour comprendre qu'il existe au moins un problème qui soit algorithmiquement indécidable – il en existe en fait une infinité, mais commençons avec un seul pour digérer le concept – il nous faut d'abord quelques définitions.
On s'en doutera, s'ensuit la définition suivante :
Comment démontrera-t-on qu'un problème donné est indécidable? Voici l'idée :
La démonstration par contradiction est la plus commune en informatique, mais il en existe d'autres – celle par induction, populaire dans certains milieux, sert beaucoup dans les démonstrations liées à des langages et, par conséquent, à des compilateurs.
Imaginons le problème COMPILE.
Son intrant sera le texte (le « listing ») d'une fonction F(X:chaîne):chaîne en langage PASCAL.
Le choix du langage est arbitraire, mais que les démonstrations données dans un langage de programmation sont souvent en PASCAL, par simple habitude.
On utilisera, de toute manière, un dérivé simplificateur de PASCAL, près du pseudocode, pour que les solutions et démonstrations proposées soient de taille raisonnable.
Son extrant sera booléen, soit VRAI si la fonction est légale selon les règles du langage PASCAL (donc si elle compile), et FAUX sinon.
On dira que COMPILE est un problème décidable. Non seulement COMPILE est soluble (on écrit, en informatique, des programmes validés par un compilateur sur une base quotidienne), mais on dispose d'un tas de solutions bien documentées à ce problème. Puisqu'il existe des tas de compilateurs commerciaux, on présumera ici que vosu acceptez le caractère soluble de COMPILE.
Imaginons le problème ARRÊT.
Ses intrants seront (a) le texte (le « listing ») d'une fonction F(X:chaîne):chaîne en langage PASCAL, et (b) une chaîne de caractères W.
Son extrant sera booléen, soit VRAI si l'exécution de F(W) arrête normalement (ou plante), et FAUX si l'exécution de F(W) boucle à l'infini.
Bien sûr, ARRÊT n'exécutera pas F(W) (ceci pourrait le faire planter ou le faire boucler à l'infini, ce qui ne résoudrait pas le problème). Il lui faudra analyser le texte résultant par F(W) et déduire algorithmiquement quel serait son comportement dans l'éventualité où on l'exécuterait.
On prétend que ARRÊT est un problème indécidable (d'ailleurs, avez-vous déjà vu un compilateur capable de vous annoncer ce genre de chose?). Voici la démonstration.
Supposons que ARRÊT soit décidable (pour démarrer la démonstration par contradiction).
Puisque ARRÊT est possible (c'est notre hypothèse), on est en droit d'écrire la fonction suivante:
Function CANTOR(Z:chaîne):booléen;
{ définition de la fonction ARRÊT, qu'on présume exister
pour les besoins de la cause }
BEGIN
while ARRÊT (Z,Z) do ;
CANTOR := true;
END
On peut voir que, tel qu'écrit ici, CANTOR(Z) arrêtera si et seulement si ARRÊT(Z,Z) est FAUX, sinon CANTOR(Z) bouclera à l'infini (à cause de la répétitive tant que à l'intérieur).
On se doute que, puisque ARRÊT prend deux morceaux de texte dont le premier doit être le texte d'une fonction et le second un paramètre textuel à cette fonction, le Z passé à la fonction CANTOR sera le texte d'une fonction.
Selon la définition de notre problème, d'ailleurs, pour que ARRÊT(Z,Z) soit FAUX, il faut que l'hypothétique exécution de Z(Z) boucle à l'infini (car ARRÊT(F,W) doit retourner FAUX si et seulement si F(W) boucle à l'infini).
On se souviendra que le Z ici nous appartient, puisque ARRÊT ne doit pas être dépendant d'un Z ou de l'autre – toute fonction bien écrite devrait faire l'affaire.
Posons maintenant ce Z comme étant le texte de CANTOR. On en a le droit, bien sûr – CANTOR est une fonction valide, après tout, et elle vaut bien n'importe quelle autre fonction!
Nous obtiendrons alors que CANTOR(texte de CANTOR) arrêtera si et seulement si ARRÊT(texte de CANTOR, texte de CANTOR) est FAUX.
Mais le sens de cet énoncé, on s'en souviendra, est que CANTOR(texte de CANTOR) boucle à l'infini!
Donc, si on présume la décidabilité de ARRÊT, on arrive ici à la situation où CANTOR(texte de CANTOR) arrête si et seulement si CANTOR(texte de CANTOR) boucle à l'infini.
Nous avons donc une sérieuse contradiction, ce qui invalide notre hypothèse de départ. ARRÊT est, clairement, un problème indécidable – il ne peut exister d'algorithme pour le résoudre.
L'emploi du nom CANTOR ici n'est pas accidentel, et réfère à notre utilisation de la technique de diagonalisation que Georg Cantor fut le premier à employer pour démontrer qu'il y a autant de nombres rationnels que de nombres entiers, mais plus de nombres irrationnels que de rationnels.
Toutes celles et tous ceux qui ont utilisé des ensembles à l'école lui doivent beaucoup. David Hilbert a déjà déclaré (c'est une de ces citations célèbres) que « Personne ne nous délogera du paradis que Cantor a créé pour nous ».
Cantor est un des géants de l'histoire des mathématiques. Comme beaucoup de ces géants, il est mort fou et abandonné, après des problèmes maritaux importants et beaucoup de haine. Ce n'était pas une personne agréable, et il était en forte opposition avec plusieurs des idées mathématiques admises de son époque.
Idée : comparer la complexité des problèmes signifie comparer la difficulté de la meilleure solution qu'on a pour les résoudre. Idéalement, la meilleure solution qu'on a pour résoudre un problème sera aussi la meilleure solution possible.
Quand on a deux problèmes décidables, on peut comparer les meilleurs algorithmes connus pour les résoudre et ainsi déterminer leur complexité relative.
Quand deux problèmes A et B sont algorithmiquement indécidables, on dira que B est plus difficile que A s'il est nécessaire d'avoir un algorithme résolvant A pour obtenir un algorithme résolvant B.
La notation utilisée ci-dessus signifie « A se réduit au sens de Turing à B », ou encore « A est un problème plus facile ou égal à B ».
Cette notation s'applique si et seulement si on ne peut résoudre A que dans la mesure où on a à notre disposition une fonction « prédéfinie » qui résout B. On verra plus bas comment on l'applique dans le cas des problèmes indécidables.
Il n'y a aucune restriction de temps ou d'espace dans la complexité des soutions à l'un ou l'autre des problèmes, mais il est interdit qu'un des algorithmes boucle à l'infini.
La notation suivante :
signifie « A équivaut au sens de Turing à B », ou encore « A est un problème aussi facile (ou aussi difficile) que B », donc :
On comprend intuitivement que la réduction et l'équivalence au sens de Turing respectent tous deux les propriétés habituelles des opérateurs relationnels, incluant celles présentées à ci-dessous.
Théorème : si et si est décidable, alors est décidable.
Démonstration :
Corollaire au théorème : si est indécidable et , alors est indécidable.
Démonstration | |
---|---|
est décidable est décidable. |
Directement du théorème. |
est décidable est décidable . |
Simple réécriture. |
est indécidable est indécidable |
Car . |
est indécidable est indécidable |
Par une loi de De Morgan. |
est indécidable est indécidable |
Par commutativité. |
est indécidable est indécidable |
Par une loi de De Morgan. |
est indécidable est indécidable |
Car . |
On sait que ARRET est indécidable (voir la démonstration, plus haut).
Imaginons maintenant le problème BOUCLE: écrire une fonction qui prend en paramètre le texte d'une fonction à un paramètre, et le texte décrivant ce paramètre, et qui retourne VRAI si cette combinaison fonction,paramètre mène à une boucle infinie, FAUX sinon.
Ses intrants seront (a) le texte (le listing) d'une fonction F(X:chaîne):chaîne en langage PASCAL, et (b) une chaîne de caractères W.
Son extrant sera booléen, soit VRAI si l'exécution de F(W) boucle à l'infini, et FAUX si l'exécution de F(W) arrête normalement (ou plante).
On prétend que BOUCLE est un problème indécidable. La démonstration, qui repose sur le principe de réductibilité au sens de Turing exposé ci-dessus, suit (plus bas).
Imaginons le problème PLANTE: écrire une fonction qui prend en paramètre le texte d'une fonction à un paramètre, et le texte décrivant ce paramètre, et qui retourne VRAI si cette combinaison fonction,paramètre plante à l'exécution, FAUX sinon.
Ses intrants seront (a) le texte (le listing) d'une fonction F(X:chaîne):chaîne en langage PASCAL, et (b) une chaîne de caractères W.
Son extrant sera booléen, soit VRAI si l'exécution de F(W) plante, et FAUX sinon.
On prétend que PLANTE est un problème indécidable. La démonstration, qui repose sur le principe de réductibilité au sens de Turing exposé ci-dessus, suit (plus bas).
Imaginons le problème PEUTPLANTER: écrire une fonction qui prend en paramètre le texte d'une fonction à un paramètre, et le texte décrivant ce paramètre, et qui retourne VRAI si cette combinaison fonction,paramètre peut planter à l'exécution, FAUX sinon.
Ses intrants seront (a) le texte (le listing) d'une fonction F(X:chaîne):chaîne en langage PASCAL, et (b) une chaîne de caractères W.
Son extrant sera booléen, soit VRAI s'il existe une chaîne w telle que l'exécution de F(w) plante, et FAUX sinon (donc si F(W) ne plante jamais, peu importe la chaîne W passée en paramètre).
On prétend que PEUTPLANTER est un problème indécidable. La démonstration, qui repose sur le principe de réductibilité au sens de Turing exposé ci-dessus, suit (plus bas).
Imaginons le problème PEUTBOUCLER: écrire une fonction qui prend en paramètre le texte d'une fonction à un paramètre, et le texte décrivant ce paramètre, et qui retourne VRAI si cette combinaison fonction,paramètre peut mener à une boucle infinie, FAUX sinon.
Ses intrants seront (a) le texte (le listing) d'une fonction F(X:chaîne):chaîne en langage PASCAL, et (b) une chaîne de caractères W.
Son extrant sera booléen, soit VRAI s'il existe une chaîne w telle que l'exécution de F(w) boucle à l'infini, et FAUX sinon (donc si F(W) ne boucle jamais à l'infini, peu importe la chaîne W passée en paramètre).
On prétend que PEUTBOUCLER est un problème indécidable. La démonstration, qui repose sur le principe de réductibilité au sens de Turing exposé ci-dessus, suit (plus bas).
On a démontré que ARRÊT est indécidable (plus haut). On veut prouver que ARRÊT BOUCLE, pour impliquer que BOUCLE est indécidable. La démonstration va de soi :
Function ARRET(F,W:chaîne):booléen;
BEGIN
SI BOUCLE(F,W)
Alors ARRET := false;
Sinon ARRET := true;
END
En C++, à titre d'exemple, on écrirait simplement :
bool ARRET(const string &F, const string &W)
{ return !BOUCLE(F,W); }
Ceci correspond au sens intuitif de la définition: on sait que ARRÊT est indécidable, or on vient de démontrer que si BOUCLE était décidable, cela entraînerait que ARRÊT le soit aussi, clairement une contradiction.
D'ailleurs, il se trouve que BOUCLE ARRÊT, car :
Function BOUCLE(F,W:chaîne):booléen;
BEGIN
SI ARRET(F,W)
Alors BOUCLE := false;
Sinon BOUCLE := true;
END
On vient donc de démontrer que ARRÊT BOUCLE, donc que ces deux problèmes sont de difficulté équivalente.
On a démontré que BOUCLE est indécidable (plus haut). On va démontrer que BOUCLE PLANTE,pour impliquer que PLANTE est indécidable.
Notre démonstration utilisera COMPILE, qui est décidable (on s'en souviendra) :
Function BOUCLE(F,W:chaîne):booléen;
BEGIN
SI NON (COMPILE (F)) Alors BOUCLE := false;
Sinon
BEGIN
F2 := listing de la fonction suivante:
F2 (X:chaîne):chaîne;
{ insérer le listing de F }
BEGIN
Y := F(X);
Z := 1/0; { eh oui! }
END
Si PLANTE(F2,W)
Alors BOUCLE := false;
Sinon BOUCLE := true;
END
END
On comprendra la logique comme suit: on construit à l'interne une fonction qui plantera si elle se rend jusqu'à une instruction précise (la division par zéro). Si elle devait s'exécuter jusqu'à ce point, alors elle plante (et ne boucle donc pas à l'infini). Si elle ne plante pas, alors on sait que F(X), utilisée à l'intérieur, bouclera à l'infini.
Les cas possibles sont d'ailleurs exposés ici :
F(W) | F2(W) | PLANTE(F2,W) | BOUCLE(F,W) |
---|---|---|---|
Ne compile pas
|
FAUX
|
||
Plante
|
Plante
|
VRAI
|
FAUX
|
OK
|
Plante
|
VRAI
|
FAUX
|
Boucle à l'infini
|
Boucle à l'infini
|
FAUX
|
VRAI
|
Cette fonction est une solution valide à BOUCLE utilisant PLANTE... mais nous savons que BOUCLE est indécidable; PLANTE est donc indécidable.
D'ailleurs, il est aussi vrai que PLANTE BOUCLE, donc qu'ils sont essentiellement de même complexité. On le voit ici :
Function PLANTE(F,W:chaîne):booléen;
BEGIN
SI NON (COMPILE (F)) Alors PLANTE := true;
Sinon
BEGIN
F2 := listing de la fonction suivante:
F2 (X:chaîne):chaîne;
{ insérer le listing de F }
BEGIN
Y := F(X);
while TRUE do ; { eh oui! }
END
Si BOUCLE(F2,W)
Alors PLANTE := false;
Sinon PLANTE := true;
END
END
Le même principe s'applique que dans le cas précédent, à ceci près qu'on provoque ici volontairement une boucle infinie plutôt qu'un échec à l'exécution.
Les cas possibles sont d'ailleurs exposés ici :
F(W) | F2(W) | BOUCLE(F2,W) | PLANTE(F,W) |
---|---|---|---|
Ne compile pas
|
VRAI
|
||
Boucle à l'infini
|
Boucle à l'infini
|
VRAI
|
FAUX
|
OK
|
Boucle à l'infini
|
VRAI
|
FAUX
|
Plante
|
Plante
|
FAUX
|
VRAI
|
On a démontré que PLANTE est indécidable (plus haut). On va démontrer que PEUTPLANTER PLANTE, donc que PEUTPLANTER est indécidable.
On remarquera l'emploi d'une technique similaire à celles déjà utilisées ci-dessus :
Function PLANTE(F,W:chaîne):booléen;
BEGIN
SI NON (COMPILE (F)) Alors PLANTE := true;
Sinon
BEGIN
F2 := listing de la fonction suivante:
F2 (X:chaîne):chaîne;
{ insérer le listing de F }
BEGIN
F2 := F(W)
END
Si PEUTPLANTER(F2)
Alors PLANTE := true;
Sinon PLANTE := false;
END
END
Les cas possibles sont d'ailleurs exposés ici :
F(W) | F2(X) | PEUTPLANTER(F) | PLANTE(F,W) |
---|---|---|---|
Ne compile pas
|
VRAI
|
||
Plante
|
Plante pour tout
X
|
VRAI
|
VRAI
|
OK
|
OK pour tout
X
|
FAUX
|
FAUX
|
Boucle à l'infini
|
Boucle à l'infini pour tout
X
|
FAUX
|
FAUX
|
Démontrer que PLANTE se réduit au sens de Turing à PEUTPLANTER est plus délicat. Il y a quelques stratégies qu'on ne peut se permettre d'employer.
Cette stratégie n'est pas envisageable car il y a une infinité de chaînes possibles, et si PLANTE(F,W) est faux pour tout W, cet algorithme va boucler à l'infini. |
|
De la même manière...
Cette stratégie risque de tomber « par accident » sur un W tel que F(W) bouclerait à l'infini, et risquerait de manquer une chaîne subséquente pour laquelle F(W) planterait. |
|
Une démonstration qui, elle, est valide serait :
Function PEUTPLANTER(F:chaîne):booléen;
BEGIN
SI NON (COMPILE (F)) Alors PLANTE := true;
Sinon
BEGIN
F2 := listing de la fonction suivante:
F2 (W:chaîne; T:entier):booléen;
{ insérer le listing de F }
BEGIN
Exécuter F(W), mais pas plus de T instructions
F2 := true;
END
H := listing de la fonction suivante:
H(X:chaîne):chaîne;
{ insérer le listing de F2 }
Pour T allant de 0 à «l'infini positif»
Pour toutes les chaînes W de longueurs <= T faire
C := F2(W,T);
Si PLANTE(H,"n'importe quoi")
Alors PEUTPLANTER := true;
Sinon PEUTPLANTER := false;
END
END
Les cas possibles sont d'ailleurs exposés ici :
F(W) | F2(W,T) | H(...) | PLANTE(H,...) | PEUTPLANTER(F) |
---|---|---|---|---|
Ne compile pas
|
VRAI
|
|||
Plante
|
VRAI
|
VRAI
|
||
Boucle à l'infini
|
FAUX
|
FAUX
|
Quelques liens pour enrichir le propos.
[1] Qui, elle, a été démontrée.