Réseaux et flots dans les réseaux#
Réseaux#
Définition
On appelle réseau (network) un graphe orienté, sans boucles (arêtes d’un noeud vers lui-même) ni arêtes multiples, dont les arêtes sont étiquetées par des nombres positifs, appelés capacités (capacity).
Par exemple, le graphe suivant est un réseau :
from network import examples
examples.example1().show()
Un réseau servira typiquement à modéliser un réseau de transport (de fluide, d’électricité, d’informations, …), la capacité d’une arête modélisant le quantité maximale pouvant être transportée le long de cette arête.
Implantation#
En terme d’implantation, un réseau peut être représenté par un graphe
simple étiqueté sur les arêtes. Nous avons rajouté une petite
surcouche à la classe Graph
de networkx pour une utilisation plus
commode. La fonction Network
est implantée dans le fichier
network.py
. Elle est basée sur les graphes de networkx.graph
et ne
dépend donc pas de l’implantation que vous avez réalisée dans les
premières sections du cours. Quelques exemples d’utilisation :
from network import Network
Ici, on construit le réseau à partir d’une liste arêtes et de noeuds. Voici par exemple un réseau avec une seule arête :
N = Network(edges = [(1,2,1)], nodes = [1,2])
N.show()
On peut modifier la capacité de l’arête :
N.set_edge_capacity(1,2,2)
N.show()
N.capacity(1,2)
list(N.predecessors(2))
list(N.successors(1))
Exercice
Créez un réseau \(N\) contentant 3 noeuds \(0,1,2\) avec une arête \((0,1)\) de capacité 1 et une arête \((2,1)\) de capacité 2 :
### BEGIN SOLUTION
N = Network(edges = [(0,1,1), (2,1,2)], nodes = [0,1,2])
### END SOLUTION
assert set(N.nodes()) == {0,1,2}
assert N.capacity(0,1) == 1
assert N.capacity(2,1) == 2
assert N.capacity(0,2) == 0
assert N.capacity(1,0) == 0
Flots#
Définition
Un flot (flow) sur un réseau \(r\) entre deux sommets \(s\) (appelé source) et \(t\) (appelé cible (target); target en anglais) est une fonction \(f\) qui associe un nombre réel positif à chaque arête de \(r\) – appelé valeur du flot sur l’arête – de sorte que :
\(f(v1,v2)\) est toujours inférieur ou égal à la capacité de l’arête \((v1,v2)\) dans \(r\);
pour tout sommet \(a\) différent de \(s\) et \(t\), le flot entrant en \(a\) est égal au flot sortant de \(a\) : $\( \sum_{v_1\rightarrow a} f(v_1,a) \qquad = \qquad \sum_{a\rightarrow v_2} f(a,v_2)\,,\)\( où les sommes ci-dessus portent respectivement sur les voisins entrants \)v_1\( et les voisins sortants \)v_2\( de \)a$.
Par exemple, dans le cas d’un réseau de transport d’eau, le flot modélise l’une des façons de transporter de l’eau dans le réseau entre \(s\) et \(t\) en respectant les contraintes de capacités. En chaque sommet (distinct de \(s\) et \(t\)), la quantité de d’eau qui arrive doit être égale à la quantité de fluide qui en sort.
Voici un flot sur le réseau donné précédemment en exemple (avec \(s = 0\) et \(t = 6\)). Sur chaque arête, le premier nombre est la capacité tandis que le second nombre est le flot à travers l’arête :
from flow_examples import flow_examples
flow_examples.example1().show()
Exercice
Vérifiez que le flot est valide : pour chaque arête, le second nombre est toujours inférieur ou égal à la capacité de l’arête et pour chaque sommet en dehors de 0 et 6, le flot entrant est égal au flot sortant; vous aurez reconnu la loi de conservation de Kirchhoff des réseaux électriques.
Par exemple, sur le sommet \(2\), on a en flot entrant \(1+1\) et en flot sortant \(1+1\), sur le sommet 5, on a en flot entrant \(2\) et en flot sortant \(1+1\).
Définition
On appelle valeur globale du flot la différence entre le flux sortant et le flux rentrant pour la source \(s\).
Dans notre exemple, la valeur globale vaut \(2=1+1+0\). Cela modélise le volume injecté dans le réseau au niveau de la source. Comme, d’après la loi de conservation, il n’y a ni perte ni création en aucun sommet intérieur, tout le volume injecté dans le réseau au niveau de la source doit ressortir au niveau de la cible. Du coup on peut aussi calculer la valeur globale comme la différence entre le flux rentrant et le flux sortant pour la cible (\(t=6\)). Ici: \(2 = 3+1-2\).
Implantation#
Pour l’implantation, nous allons considérer un flot comme un double réseau : le réseau de départ \(r\) et un second réseau sur le même ensemble de sommets \(f\) qui vérifie des propriétés particulières.
Dans flow.py vous trouverez une classe partiellemment implantée.
%load_ext autoreload
%autoreload 2
from flow import Flow
from network import Network
from network import examples
from flow_examples import flow_examples
Vous pouvez créer un flot vide à partir d’un réseau donné. Par exemple :
# 0 est la source, 6 la cible
# le paramètre check permet de vérifier les propriétés du flots (pas encore implanté)
F = Flow(examples.example1(), 0, 6, check = False)
F.show()
Sur chaque arête est affiché la capacité de l’arête et la valeur du flot sur l’arête qui, ici, est toujours de \(0\).
Le flot contient le réseau de départ :
F.network().show()
F.source()
F.target()
On peut directement récupérer la capacité d’une arête :
F.capacity(5,6)
F.network().capacity(5,6)
L’objet flot contient aussi un second réseau comme attribut donnant les valeurs du flot sur chaque arête :
F._flow.show()
On ne fait pas de différence entre l’absence d’arête entre deux sommets \(i\) et \(j\) et une arête de flot nulle. C’est pour cela qu’ici on ne voit aucune arête : le flot est vide.
Voici un exemple de flot non trival; on y voit les deux réseaux superposés ainsi que la source et la cible :
F = flow_examples.example1()
F.show()
Le réseau sous-jacent :
F.network().show()
Le réseau du flot :
F._flow.show()
On peut accéder directement aux valeurs du flot depuis l’objet Flow
. Voici par exemple la valeur du flot sur l’arête 0,1 :
F.flow(0,1)
Voici les valeurs du flot sur toutes les arêtes du réseau :
F.flows()
Exercice
En utilisant les méthodes predescessors
et successors
comme
ci-dessous, calculez les flots entrants et sortants sur le noeud \(2\)
que vous affecterez respectivement aux variables f_in
et f_out
.
list(F.network().predecessors(2))
list(F.network().successors(2))
### BEGIN SOLUTION
f_in = sum(F.flow(i,2) for i in F.network().predecessors(2))
f_out = sum(F.flow(2,i) for i in F.network().successors(2))
### END SOLUTION
assert f_in == 2
assert f_out == 2
Exercice
Implantez les méthodes
flow_in
etflow_out
de la classeFlow
du fichierflow.py
.
Exercice (suite)
Vérifiez votre implantation en exécutant les cellules suivantes.
Astuce
Nous avons activé l’extension
autoreload
. La classeFlow
devrait donc automatiquement se remettre à jour à chaque modification deflow.py
. Dans le doute, n’hésitez pas à redémarrer votre noyau pour être sûr d’utiliser la toute dernière version de votre code
%load_ext autoreload
%autoreload 2
from flow_examples import flow_examples
F = flow_examples.example1()
F.flow_in(1)
F.flow_out(1)
assert F.flow_in(1) == 2
assert F.flow_out(1) == 2
assert F.flow_out(0) == 2
assert F.flow_in(6) == 4
assert F.flow_out(6) == 2
assert F.flow_in(4) == 3
assert F.flow_out(4) == 3
Affichage du code de la méthode flow_in
pour correction :
from utils import show_source
show_source(Flow.flow_in)
show_source(Flow.flow_out)
Exercice (suite)
Implantez la méthode
global_value
et vérifiez ci-dessous.
%load_ext autoreload
%autoreload 2
from flow_examples import flow_examples
F = flow_examples.example1()
F.global_value()
assert F.global_value() == 2
from utils import show_source
show_source(Flow.global_value)
Exercice (suite)
Implantez les méthodes
check_capacity
etcheck_in_out
et vérifiez ci-dessous.
%load_ext autoreload
%autoreload 2
from flow_examples import flow_examples
F = flow_examples.example1()
F.check_capacity()
F.check_in_out()
assert flow_examples.example1().check_capacity()
assert flow_examples.example1().check_in_out()
assert not flow_examples.wrong_capacity().check_capacity()
assert not flow_examples.wrong_in_out().check_in_out()
# affichage de la source pour correction
show_source(Flow.check_capacity)
show_source(Flow.check_in_out)
Vérification de syntaxe et de style :
from utils import code_checker
code_checker("ruff check --ignore E741 --exclude nbgrader_config.py")
Vérification statique de types :
code_checker("mypy .")
Conclusion#
Vous voici arrivés à la fin de cette feuille d’introduction aux réseaux et aux flots. Dans la feuille suivante, vous découvrirez un des algorithmes fondamentaux pour calculer des flots.