Cours de Florent.

# Calculer en théorie et en pratique II/III

Dans cette séquence de cours, nous tentons de répondre à la question suivante.

C'est quoi un ordinateur?

Cette question s'apparente à celle que ce sont posés philosophes et mathématiciens de Leibnitz à Turing en passant par Hilbert.

Qu'est-ce qu'une question à laquelle on peut répondre de manière mécanique par un calcul?

Nous avons vu une approximation de calcul théorique basé sur le calcul booléen et des portes logiques qui sont implémentable avec des composants electroniques, permettant de réaliser divers opérations comme l'addition bit à bit (schéma de Vernam en cryptographie), la majorité ou encore l'addition avec retenue.

Aujourd'hui nous poursuivons avec un modèle théorique, qui est celui de l'**automate fini**, un modèle assez restreint de machine. L'adjectif fini s'applique à la mémoire de la machine qui est finie.

Nous verrons plus tard un modèle plus général avec une mémoire arbitrairement grande : la machine deTuring. 

## Le modèle d'automate fini.

* La machine va lire en entrée un mot sur un alphabet A de taille finie (on peut par exemple imaginer qu'il s'agit d'un mot en binaire et l'alphabet A serait par exemple composé des symboles 0 et 1).
* La machine possède plusieurs états. Intuitivement, chaque état code pour ce qu'on stocke dans la mémoire finie de la machine.
* Certains états sont dit **acceptant**.
* Un état spécial est celui dans lequel la machine débute son calcul. C'est l'**état initial**.
* La machine va "manger" le mot d'entrée lettre par lettre et passe d'un état à l'autre selon ce que son **programme** lui dicte.
* Quand toutes les lettres sont lues, si l'état dans lequel la machine se trouve est acceptant, la machine répond OUI (elle **accepte** le mot) sinon elle répond NON (elle **rejette** le mot).
* L'ensemble des mots sur l'alphabet A qui sont acceptés par la machine forme le *langage reconnu par l'automate*.

### Le programme de l'automate.
On peut l'écrire dans un tableur, sous forme d'une succession fini de lignes avec trois colonnes. 
La première colonne est l'état actuel, la seconde colonne est la lettre lue, la troisième colonne est le nouvel état.

Pour un état donné q et une lettre lue x, l'automate va se placer dans un état donné q' (il est possible que q et q' soit le même état).

Si le programme est incomplet et que le tableur n'explique pas comment faire, on arrête le calcul et on rejette le mot.

Si le tableur n'est pas ambigu et que on n'a jamais deux lignes indiquant pour la même paire de lettre lue et d'état un nouvel état différent, on dit que l'automate est **déterministe**.

Sinon l'automate n'est pas déterministe -- on dit aussi **non-déterministe** -- et on considère que la machine peut choisir une action du tableur parmi celles qui sont listées.

Pour un automate non déterministe, on considère qu'un mot est accepté si il existe un calcul qui accepte.

Analogie : vous cherchez à sortir d'un labyrinthe, il y a de nombreux chemins possibles. Si il en existe un vers la sortie alors vous pouvez gagner au jeu du labyrinthe. 

## Exemples avec JFLAP.

JFLAP est un logiciel de simulation de machines théoriques qui permet de définir, manipuler, exécuter et transformer des machines. En pratique c'est une archive jar.

Une archive jar est un programme codé dans le langage java que vous pouvez exécuter.
L'archive se compose de divers fichiers, dont un fichier principal, des librairies etc.

Pour utiliser le logiciel, vous devez commencer par installer java.
Suivez les recommandations de votre système d'exploitation.

Ensuite, il vous suffit souvent de double-cliquer sur l'archive jar.

Vous pouvez aussi en ligne de commande sous linux écrire.

```bash
java -jar <nom de l'archive jar>
```
Sur les machines virtuelles, 

### Prise en main de JFlap.

1. Lancez JFLAP et ouvrez [l'automate sauvegardé ici](https://www.lacl.fr/fmadelaine/Download/M1Droit/Automates/TP2NDA.jff)
2. Allez dans le menu *Input* et choisissez *Step by State*. Entrez le mot **babb** et observez la simulation des exécutions parallèles étape par étape en appuyant sur *step*. 
3. Testez ensuite avec le mot **baaa**.
4. Relancez les tests ci-dessus. Cette fois essayez les autres
  options.
  * *Reset* permet de revenir au début du calcul
  *  *Trace* après sélection en bas d'une exécution permet
    de montrer cette exécution depuis le début.
  *  *Remove* élimine l'exécution sélectionnée
  *  *Freeze*/*Thaw* permet de mettre en pause ou de
    reprendre une exécution (on perd la synchronisation)
5. Vous pouvez aussi tester sans les étapes avec *Fast Run*
  voir plusieurs entrées en même temps avec
  *Multiple Run*   (tous les deux dans le menu *Input*).
  Faites le par exemple pour les deux mots précédents.
6. Pour information, *Step with closure* fait la même chose
  que *step by State* sauf pour les automates ayant des
  transitions avec le mot-vide (mais on ne les considère pas dans
  ce cours).

### Hello World
Votre but est de créer l'automate dessiné ci-dessous.

    ![images/HelloWorldAutomate.jpg]

1. Cliquez sur *File* puis *New*.
2. JFLAP permet de gérer différents modèles de calcul. Nous allons
  nous en tenir aux automates finis. Cliquez sur *Finite Automaton*.
  Vous avez accès à une nouvelle fenêtre en mode éditeur.
  * Cliquez sur le petit cercle avec un q à l'intérieur, puis sur
    le canevas pour ajouter des états.
  *  Les deux derniers boutons permettent de faire du undo/redo.
  *  La flèche fine permet d'ajouter une transition entre 1 ou 2
    états (ne pas oublier de mettre une lettre).
  *  La flèche plus épaisse à gauche permet de sélectionner,
    déplacer, éditer les propriétés des différents éléments (par
    exemple de faire un état initial).
  *  La tête de mort permet de détruire des états ou des transitions.

### Exercices
Dessinez les automates qui reconnaissent :

(pour l'alphabet $\{a,b\}$) 

1. les mots de 2 lettres;
2. les mots contenant exactement 2 a;
3. les mots contenant 2 a consécutifs;
4. les mots commençant par b;
5. les mots se terminant par bab;
6. le langage réduit au mot vide;

(pour l'alphabet $\{a,b,c\}$)

7. les mots qui contiennent $aabcc$;
8. les mots de taille multiple de 3;
9. les mots de la forme abcabcabc... (une ou plusieurs successions des 3 lettres abc);
10. les mots dont la première lettre est égale à la dernière. 

<details><summary>Calcul modulaire en base 2</summary>
<p>

### Compter avec les automates
On ne le verra pas dans ce cours, mais il est possible d'établir que le modèle des automates ne permet pas de compter. Par exemple, on ne peut pas lire une expression avec des parenthèses et vérifier qu'il y a autant de parenthèses ouvrantes que de parenthèses fermantes. On ne peut pas non plus vérifier que les parenthèses correspondent bien. Ainsi ce modèle n'est pas suffisant pour vérifier par exemple qu'une chaîne de caractères est un programme suivant un syntaxe fixée du C.

Par contre, on peut compter facilement modulo N, puisque celà revient à se souvenir d'un nombre fini de cas.

NB. modulo N veut dire qu'on manipule des nombres en faisant des sommes, des produits, etc mais en s'intéressant uniquement au reste de la division par N du résultat. Ça semble abstrait au premier abord, mais c'est ce que vous faites quand vous calculez le numéro d'un mois avec N=12.
Par exemple 3 mois après novembre c'est février : soit 11+3 = 2 modulo 12.

Imaginons que notre automate lise un mot sur l'alphabet 0,1 représentant en binaire un nombre et qu'on souhaite tester si ce nombre est un multiple de 3.

#### Retour sur la notation binaire

En décimal, si j'ajoute 0 à droite d'un mot décimal w qui représente le nombre n alors le mot décimal w0 (w suivi d'un 0) représente n fois 10.

En binaire, c'est similaire mais en remplaçant 10 par 2 dans la phrase ci-dessus.
Donc w0 représente 2n.

Si j'écris w1 c'est le nombre représenté par w0 plus 1, soit 2n+1.

On peut utiliser cette relation pour déduire des propriétés de divisibilité par trois des nombres représentés par w0 ou w1 en fonction de celle du nombre n représenté par w.

Si n est divisible par trois, il s'écrit sous la forme 3k pour un certain k.
    * 2n vaut dans ce cas 2*3k qui est encore divisible par 3 (reste nul, soit 0 modulo 3).
    * 2n+1 vaut dans ce cas 2(3k)+1 (reste 1, soit 1 modulo 3).
    
On peut procéder de même pour les 2 autres cas.

Si le reste de la division de n par trois vaut 1, il s'écrit sous la forme 3k+1 pour un certain k.
    * 2n vaut dans ce cas 2(3k+1)=2.3k+2 (reste 2, soit 2 modulo 3).
    * 2n+1 vaut dans ce cas 2(3k+1)+1=2.3k+3=3(2k+1) (reste 0, soit 0 modulo 3).

##### Exercice
Procédez de même lorsque le reste de la division de n par trois vaut 2.

En déduire un automate qui lit un mot en binaire et accepte celui-ci exactement lorsque le nombre représenté est divisible par trois.
(indication : il faut un état par reste possible).


</p>
</details>

## Le modèle de la machine de Turing
Nous allons maintenant nous pencher sur un modèle de machine qui
semble au premier abord n'être que légèrement différent de celui des
automates. D'une part, on va pouvoir revenir en arrière sur le mot, et
d'autre part on va pouvoir écrire ce qui permet d'avoir une mémoire
arbitrairement grande. 

Ces deux changements permettent
d'obtenir *in fine* un modèle très riche puisque *n'importe quelle
procédure de calcul connue à l'heure actuelle* peut être traduite
théoriquement par une machine de Turing.

Il est important de noter que le programme reste fini.


    ![images/turingMachine.png]

Il y a beaucoup de ressources sur Alan Turing disponibles en ligne, en
particulier depuis son centenaire en 2012. Vous pouvez trouver (ses
articles originaux et ses brouillons)[http://www.turingarchive.org/].

C'est un mathématicien qui a eu une influente forte en informatique,
malgré une vie trop courte (1912-1954). En droit britannique, (une loi porte son nom)[https://en.wikipedia.org/wiki/Alan_Turing_law}] 

### Turing avec JFLAP

Dans le menu choisissez **Machine de Turing**. L'éditeur graphique vous permet de créer rapidement le programme d'une machine de Turing. Les cercles sont les états de la machine (comme pour les automates). Un clic droit permet de rendre initial un état, ou final des états. Un état final correspond ici en fait à un état acceptant. La machine s'arrête si il n'y a pas de transition appropriée (en
rejetant si l'état dans laquelle elle se trouve n'est pas final).

Dans la suite on utilise l'alphabet 0,1 en plus du blanc (ressemblant à un carré pour JFLAP).
Notez que le ruban est infini à gauche et à droite pour JFLAP. Il y a
de nombreuses variations possibles pour la machine de Turing qui ne
font pas de différence notable, par exemple un ruban fini à gauche, un ruban à 2 dimensions etc.

Un point technique avec JFLAP. Si vous voulez fabriquer une transition
qui écrit un blanc (un carré) pour une transition, il suffit de ne rien
saisir dans cette cellule pour la transition et d'appuyer sur entrée. 

#### Exercice
On suppose que l'alphabet est 0,1
  1.  Écrire un programme qui lit les caractères du mot d'entrée de
    la gauche vers la droite et s'arrête en acceptant.
  1.  Même question pour un programme qui accepte si et seulement si
    la première et la dernière lettre sont les mêmes.
  1.  Même question pour les mots qui sont des \textsl{palindromes}
    (exemple de palindrome en français KAYAK). On peut dans un premier
    temps ne gérer correctement que les mots qui sont de longueur
    paire.

#### Indications + correction indicative
Pour les palindromes pairs, de la forme w suivi de w renversé, *on ne peut pas se souvenir dans nos états* du début du mot w qui est arbitrairement grand et donc pour un mot
assez grand forcément plus grand que le nombre (fini) d'états de notre
machine.

Si on disposez d'une machine de Turing à **2 rubans**, on pourrait :
recopier le premier ruban sur le second; ramener la tête de lecture du
premier ruban tout à gauche; puis décaler la tête du premier ruban
vers la droite et celle du second ruban vers la gauche en vérifiant
une par une que les lettres sont identiques.

Comme nous ne disposons que d'un ruban, la seule mémoire non bornée à
notre disposition est l'unique ruban. L'idée consiste donc à vérifier
l'un après l'autre les couples de lettres suivantes : (première
lettre, dernière lettre) puis (seconde lettre, avant-dernière lettre)
etc.

Si nous avions **deux têtes** il suffirait de placer notre seconde tête à
la fin du mot et de procéder comme dans la version à 2 rubans.

Puisqu'on a une seule tête, cette dernière va devoir faire des
aller-retours. Il va falloir marquer d'une manière ou d'une autre les
lettres qu'on a vu pour éviter de repasser au même endroit.
L'exemple ci-dessous enlève tout simplement les lettres en cours de
vérification.

    ![images/TuringPalindromePair.jpg]

## La thèse de Church Turing.
**Pour tout modèle raisonnable de calcul, on obtient la même notion de ce qui est calculable.**


Arguments en faveur de cette thèse
* Théorie générales des fonctions récursives (Gödel, Herbrand
  1933).
*  lambda-calcul (Church, 1936).
*  Machines de Turing (Turing, 1936).
*  Trois modèles équivalents (Church 1936, Turing 1937).
*  Machines de Post (1936).
*  Machines de Turing avec plusieurs rubans (Minsky).
*  Machines à compteurs.
*  Machines à registre.
*  etc.

