diff --git a/compte_rendu/Makefile b/compte_rendu/Makefile new file mode 100644 index 0000000..3cf140d --- /dev/null +++ b/compte_rendu/Makefile @@ -0,0 +1,21 @@ +.PHONY: +.SUFFIXES: + +COMP = mdbg +OUT = cours +TITLE = "Microéconomie" +PACKAGE = "{{MyPack2}}" +AUTHOR = "Hugo LEVY--FALK" +DOCUMENTCLASS = "article" + +all: + $(COMP) $(OUT).mdbg --title $(TITLE) --packages $(PACKAGE) --date \\today --documentclass $(DOCUMENTCLASS) --author $(AUTHOR) + lualatex --shell-escape $(OUT).tex + lualatex --shell-escape $(OUT).tex + rm $(OUT).aux $(OUT).log $(OUT).out $(OUT).toc + rm $(OUT).tex + +clean: + rm $(OUT).aux $(OUT).log $(OUT).out $(OUT).toc + rm $(OUT).tex + diff --git a/compte_rendu/MyPack2.sty b/compte_rendu/MyPack2.sty new file mode 100644 index 0000000..840c748 --- /dev/null +++ b/compte_rendu/MyPack2.sty @@ -0,0 +1,155 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{MyPack2} + +%%%%%%%% INCLUSIONS GÉNÉRALES %%%%%%%%% +\RequirePackage{babel} +%\RequirePackage{titlesec} +\RequirePackage{color} +\RequirePackage[a4paper]{geometry} +\RequirePackage[font=small,format=plain,labelfont=bf,up, + textfont=normal,up, + justification=justified, + singlelinecheck=false]{caption} +\RequirePackage[justification=centering]{caption} +\RequirePackage{xcolor} % Required for specifying custom colours +\definecolor{grey}{rgb}{0.9,0.9,0.9} % Colour of the box surrounding the title +\RequirePackage[utf8]{inputenc} % Required for inputting international characters + +\RequirePackage[T1]{fontenc} +% \RequirePackage[sfdefault]{ClearSans} % Use the Clear Sans font (sans serif) +\RequirePackage{placeins} +\RequirePackage{listings} +\RequirePackage{graphicx} +\RequirePackage{amsfonts} + + + +\definecolor{bleu}{RGB}{0,100,143} +\definecolor{gris}{RGB}{130,130,140} +\definecolor{grisBleu}{RGB}{65,115,141} + + + +%%%%%%%% MATH %%%%%%%%% +\RequirePackage{stmaryrd} +\newcommand*\intervalleEntier[2]{\intervalle{\llbracket}{#1}{#2}{\rrbracket}} +\newcommand*\intervalle[4]{\left#1 #2 \, ; #3 \right#4} +\newcommand*\intervalleOO[2]{\intervalle{]}{#1}{#2}{[}} +\newcommand*\intervalleFF[2]{\intervalle{[}{#1}{#2}{]}} +\newcommand*\intervalleOF[2]{\intervalle{]}{#1}{#2}{]}} +\newcommand*\intervalleFO[2]{\intervalle{[}{#1}{#2}{[}} + +\let\ensembleNombre\mathbb +\newcommand*\N{\ensembleNombre{N}} +\newcommand*\Z{\ensembleNombre{Z}} +\newcommand*\Q{\ensembleNombre{Q}} +\newcommand*\R{\ensembleNombre{R}} +\newcommand*\C{\ensembleNombre{C}} + +\newcommand*\enstq[2]{\left\{#1,\; #2\right\}} + +%\newcommand*\int[1]{\left\lfloor #1\right\rfloor} +%\newcommand*\norme[1]{\left\lVert#1\right\rVert} +%\newcommand*\abs[1]{\left\lvert#1\right\rvert} + +\let\vecteur\overrightarrow + +\newcommand*\Sys[1]{\left\{ \begin{aligned} #1 \end{aligned} \right. \kern-\nulldelimiterspace} + +\newcommand*\application[5]{ + #1 \colon + \begin{alignedat}{2}  &\to \\ +  &\mapsto  + \end{alignedat} +} +\newcommand*\applicationSigne[3]{ + #1 \colon #2 \to #3 +} + +\newtheorem{defi}{Définition}[section] +\newtheorem{theo}{Théorème}[section] +\newtheorem{prop}{Proposition}[section] +\newtheorem{ex}{Exemple}[section] + + +%%%%%%%% SECTIONNEMENT %%%%%%%%% + +%\renewcommand{\thesection}{\Roman{section}} +%\renewcommand{\thesubsection}{\arabic{subsection}} + +\newcommand*\emptyPage{\newpage\null\thispagestyle{empty}\addtocounter{page}{-1}\newpage} + +%%%%%%%% INTIALISATION DE LA PAGE %%%%%%%%% +\RequirePackage{fancyhdr} +\newcommand\initPage[3]{ + \pagestyle{fancy} + \fancyhead{#1} + \lhead{#2} + \rhead{#3} + \rfoot{} + \lfoot{} + + %\titleformat*{\section}{\Large\bfseries\color{bleu}\Roman{section}} + %\titleformat*{\subsection}{\large\bfseries\color{grisBleu}} + %\titleformat*{\subsubsection}{\bfseries\color{gris}} + %\titlespacing*{\section}{-3em}{*1}{*1} + %\titlespacing*{\subsection}{-2em}{*1}{*1} + %\titlespacing*{\subsubsection}{-1em}{*1}{*1} + +} + +%%%%%%%% TITRE %%%%%%%%M +\renewcommand{\maketitle}{ +\begin{titlepage} % Suppresses displaying the page number on the title page and the subsequent page counts as page 1 + %------------------------------------------------ + % Grey title box + %------------------------------------------------ + \topskip0pt + \vspace*{5cm} + \colorbox{grey}{ + \parbox[t]{0.93\textwidth}{ % Outer full width box + \parbox[t]{0.91\textwidth}{ % Inner box for inner right text margin + \raggedleft % Right align the text + \fontsize{72pt}{80pt}\selectfont % Title font size, the first argument is the font size and the second is the line spacing, adjust depending on title length + \vspace{1cm} % Space between the start of the title and the top of the grey box + + \@title + + \vspace{1cm} % Space between the end of the title and the bottom of the grey box + } + } + } + + \vfill % Space between the title box and author information + + %------------------------------------------------ + % Author name and information + %------------------------------------------------ + + \parbox[t]{0.93\textwidth}{ % Box to inset this section slightly + \raggedleft % Right align the text + \large % Increase the font size + {\Large \@author}\\[4pt] % Extra space after name + + \hfill\rule{0.2\linewidth}{1pt}\\% Horizontal line, first argument width, second thickness + \@date + } + +\end{titlepage} + +\emptyPage +} + + + +%%%%%%%% FIN DU DOCUMENT %%%%%%%%% + +\newcommand{\roadMap}{ + \clearpage + \listoffigures + \clearpage + \listoftables +} + +% Fin du package +\endinput diff --git a/compte_rendu/compte_rendu.tex b/compte_rendu/compte_rendu.tex new file mode 100644 index 0000000..e69de29 diff --git a/compte_rendu/gabriel_vs_delaunay.png b/compte_rendu/gabriel_vs_delaunay.png new file mode 100644 index 0000000..72be2a7 Binary files /dev/null and b/compte_rendu/gabriel_vs_delaunay.png differ diff --git a/compte_rendu/reseau_naif.png b/compte_rendu/reseau_naif.png new file mode 100644 index 0000000..639aa2f Binary files /dev/null and b/compte_rendu/reseau_naif.png differ diff --git a/compte_rendu/reseau_naif_log.png b/compte_rendu/reseau_naif_log.png new file mode 100644 index 0000000..e245df7 Binary files /dev/null and b/compte_rendu/reseau_naif_log.png differ diff --git a/compte_rendu/reseau_vs_reseauRapide.png b/compte_rendu/reseau_vs_reseauRapide.png new file mode 100644 index 0000000..19a95d6 Binary files /dev/null and b/compte_rendu/reseau_vs_reseauRapide.png differ diff --git a/compte_rendu/reseau_vs_reseauRapide_log.png b/compte_rendu/reseau_vs_reseauRapide_log.png new file mode 100644 index 0000000..2069dfb Binary files /dev/null and b/compte_rendu/reseau_vs_reseauRapide_log.png differ diff --git a/graph.py b/graph.py deleted file mode 100644 index 50e7392..0000000 --- a/graph.py +++ /dev/null @@ -1,33 +0,0 @@ -class Graph: - """Implémente un graphe non orienté.""" - - def __init__(self, nom): - """Initialise un graphe vide. - - :param nom: Nom du graphe - """ - self.nom = nom - - -class Sommet: - """Implémente un sommet de graphe.""" - - def __init__(self, pos): - """Initialise un sommet. - - :param pos: couple donnant la position du point. - """ - self.pos = pos - - -class Arete: - """Implémente une arête de graphe.""" - - def __init__(self, longueur, v_moyenne): - """Initialise une arête. - - :param longueur: longueur de l'arête. - :param v_moyenne: vitesse moyenne sur l'arête. - """ - self.longueur = longueur - self.v_moyenne = v_moyenne diff --git a/graphe.py b/graphe.py new file mode 100644 index 0000000..ba20733 --- /dev/null +++ b/graphe.py @@ -0,0 +1,316 @@ +import random +import triangulation + + +class Graphe: + """Implémente un graphe non orienté.""" + + def __init__(self, nom): + """Initialise un graphe vide. + + :param nom: Nom du graphe + """ + self.nom = nom + self.sommets = [] + self.aretes = [] + + def renomme(self, nom): + """Renome le graphe + + :param nom: Nouveau nom du graphe. + """ + self.nom = nom + + def ajouteSommet(self, x, y): + """Ajoute le sommet s au graphe. + + :param x: abcisse du sommet. + :param y: ordonnée du sommet. + + :return: Le sommet créé. + """ + s = Sommet((x, y), len(self.sommets)) + self.sommets.append(s) + return s + + def connecte(self, s1, s2, longueur, v_moyenne): + """Connecte les sommets s1 et s2. + + :param s1: premier sommet. + :param s2: deuxième sommet. + :param longueur: longueur de l'arrête. + :param v_moyenne: vitesse moyenne sur l'arrête. + + :return: L'arête créée. + """ + a = Arete(s1, s2, longueur, v_moyenne) + self.aretes.append(a) + return a + + def n(self): + """Retourne le nombre de sommets du graphe.""" + return len(self.sommets) + + def m(self): + """Retourne le nombre d'arrêtes du graphe.""" + return len(self.aretes) + + def __str__(self): + return "V({nom})=[\n{noeuds}\n]\nE({nom})=[\n{aretes}\n]\n".format( + nom=self.nom, + aretes='\n'.join([str(v) for v in self.aretes]), + noeuds='\n'.join([str(n) for n in self.sommets]) + ) + + def trace(self, dest): + """Affiche le graphe sur la destination. + + :param dest: instance de Afficheur. + """ + for s in self.sommets: + dest.changeCouleur(s.couleur) + dest.tracePoint((s.x(), s.y())) + dest.traceTexte((s.x(), s.y()), 'v'+str(s.num)) + + for a in self.aretes: + dest.changeCouleur(a.couleur) + dest.traceLigne((a.s1.x(), a.s1.y()), (a.s2.x(), a.s2.y())) + + def ajouteRoute(self, v1, v2, vmax): + """Ajoute une route de distance euclidienne entre v1 et v2. + + :param v1: Premier sommet. + :param v2: Deuxième sommet. + :param vmax: vitesse maximale sur la route. + + :return: Route créée. + """ + return self.connecte(v1, v2, v1.distance(v2), vmax) + + def ajouteNationale(self, v1, v2): + """Ajoute une nationale au graphe. (rouge et vmax = 90km/h). + + :param v1: Premier sommet. + :param v2: Deuxième sommet. + + :return: route créée. + """ + a = self.ajouteRoute(v1, v2, 90) + a.couleur = (1., 0., 0.) + return a + + def ajouteDepartementale(self, v1, v2): + """Ajoute une départementale au graphe. (jaune et vmax = 60km/h). + + :param v1: Premier sommet. + :param v2: Deuxième sommet. + + :return: route créée. + """ + a = self.ajouteRoute(v1, v2, 60) + a.couleur = (1., 1., 0.) + return a + + +class Sommet: + """Implémente un sommet de graphe.""" + + def __init__(self, pos, num, couleur=(0., 0., 0.)): + """Initialise un sommet. + + :param pos: couple donnant la position du point. + :param num: numéro du sommet. + :param couleur: couleur du sommet. + """ + self.pos = pos + self.num = num + self.couleur = couleur + + def __str__(self): + return "v{} (x = {} km y = {} km)".format( + str(self.num), + str(self.x()), + str(self.y()) + ) + + def distance(self, v): + """Calcule la distance entre deux points. + + :param v: Point de calcul de la distance. + """ + return ((self.x()-v.x())**2 + (self.y()-v.y())**2)**(1/2) + + def x(self): + """Retourne l'abcisse de x.""" + return self.pos[0] + + def y(self): + """Retourne l'ordonnée de y.""" + return self.pos[1] + + +class Arete: + """Implémente une arête de graphe.""" + + def __init__(self, s1, s2, longueur, v_moyenne, couleur=(0., 0., 0.)): + """Initialise une arête. + + :param s1: sommet 1. + :param s2: sommet 2. + :param longueur: longueur de l'arête. + :param v_moyenne: vitesse moyenne sur l'arête. + :param couleur: couleur de l'arête. + """ + self.s1 = s1 + self.s2 = s2 + self.longueur = longueur + self.v_moyenne = v_moyenne + self.couleur = couleur + + def voisin(self, s): + """Retourne le sommet voisin de s dans l'arête. + + :param s: Un sommet de l'arête. + """ + if s == self.s1: + return self.s2 + else: + return self.s1 + + def __str__(self): + return " v{v1}, v{v2} (long. = {lon} km vlim. = {v} km/h)".format( + v1=str(self.s1.num), + v2=str(self.s2.num), + lon=str(self.longueur), + v=str(self.v_moyenne) + ) + + +def pointsAleatoires(n, L): + """Crée un graphe de n points aléatoires non reliés dans le carré centré en + 0 de largeure L. + + :param n: nombre de points. + :param L: Côté du carré. + """ + g = Graphe("Graphe aléatoire") + for _ in range(n): + x, y = random.uniform(-L/2, L/2), random.uniform(-L/2, L/2) + g.ajouteSommet(x, y) + return g + + +def test_gabriel(g, v1, v2): + """Teste si on peut connecter deux sommets selon le critère de Gabriel. + + :param g: Le graphe. + :param v1: Premier sommet. + :param v2: Deuxième sommet. + """ + milieu = Sommet(((v1.x()+v2.x())/2, (v1.y()+v2.y())/2), -1) + rayon = v1.distance(v2)/2 + ajoute = True + for v3 in g.sommets: + if v3 in [v1, v2]: + continue + elif v3.distance(milieu) < rayon: + ajoute = False + break + return ajoute + + +def gabriel(g, ignore_gvr=False): + """Crée le graphe de Gabriel associé à g avec des routes démartementales. + + :param g: Un graphe sans arrêtes. + :param ignore_gvr: booléen indiquant si on ne doit pas ajouter une arrête + quand gvr en ajoute une. + """ + for v1 in g.sommets: + for v2 in g.sommets: + if v2 == v1: + continue + if test_gabriel(g, v1, v2): + if (ignore_gvr and not test_gvr(g, v1, v2)) or not ignore_gvr: + g.ajouteDepartementale(v1, v2) + + +def test_gvr(g, v1, v2): + """Teste si on peut connecter deux sommets selon le critère GVR. + + :param g: Le graphe. + :param v1: Premier sommet. + :param v2: Deuxième sommet. + """ + rayon = v1.distance(v2) + ajoute = True + for v3 in g.sommets: + if v3 in [v1, v2]: + continue + elif v3.distance(v1) < rayon and v3.distance(v2) < rayon: + ajoute = False + break + return ajoute + + +def gvr(g): + """Crée le graphe GVR associé à g avec des routes nationales. + + :param g: Un graphe sans arrêtes. + """ + for v1 in g.sommets: + for v2 in g.sommets: + if v2 == v1: + continue + if test_gvr(g, v1, v2): + g.ajouteNationale(v1, v2) + + +def reseau(g): + """Construit une carte routière avec comme nationales les routes obtenues + par la méthode GVR et départementales celles de Gabriel. + + :param g: Un raphe sans arêtes. + """ + gabriel(g, ignore_gvr=True) + gvr(g) + + +def delaunay(g): + """Crée une triangulation de Delaunay à partir d ' un nuage de point""" + t = triangulation.Triangulation(g) + # Définit une fonction destinée à être appelée + # pour toute paire (s1,s2) de sommets + # qui forme une arête dans la triangulation de Delaunay. + # v3 et v4 sont les troisièmes sommets des deux triangles + # ayant (s1,s2) comme côté. + + def selectionneAretes(graphe, v1, v2, v3, v4): + # Fait de chaque arête de la triangulation + # une nationale + graphe.ajouteNationale(v1, v2) + # Construit le graphe de retour, égal à la triangulation de Delaunay + g = t.construitGrapheDeSortie(selectionneAretes) + g.renomme("Delaunay(" + str(g.n()) + ")") + return g + + +def reseauRapide(g): + """Crée une triangulation de Delaunay à partir d ' un nuage de point""" + t = triangulation.Triangulation(g) + # Définit une fonction destinée à être appelée + # pour toute paire (s1,s2) de sommets + # qui forme une arête dans la triangulation de Delaunay. + # v3 et v4 sont les troisièmes sommets des deux triangles + # ayant (s1,s2) comme côté. + + def selectionneAretes(graphe, v1, v2, v3, v4): + if test_gabriel(graphe, v1, v2): + if test_gvr(graphe, v1, v2): + graphe.ajouteNationale(v1, v2) + else: + graphe.ajouteDepartementale(v1, v2) + # Construit le graphe de retour, égal à la triangulation de Delaunay + g = t.construitGrapheDeSortie(selectionneAretes) + g.renomme("Delaunay(" + str(g.n()) + ")") + return g diff --git a/graphique.py b/graphique.py new file mode 100644 index 0000000..30273ca --- /dev/null +++ b/graphique.py @@ -0,0 +1,195 @@ +# coding: utf-8 + +import sys +import numpy as np +import math +import copy +import PySide.QtCore as qtcore +import PySide.QtGui as qtgui +import traceback + +class Afficheur(qtgui.QWidget): + afficheurs = [] + size = (800, 600) + coord = np.array((50, 50)) + decalage = np.array((40,40)) + + def __init__(self, sujet, centre, ech): + super(Afficheur, self).__init__() + if(not "trace" in dir(sujet)): + raise Exception('Méthode "trace" non définie dans la classe ' + str(sujet.__class__)) + + app = qtgui.QApplication.instance() + if not app: + app = qtgui.QApplication(sys.argv) + desktop = app.desktop() + screen = desktop.screenGeometry(desktop.screenNumber(self)) + + self.rayon = 4 + self.ech = ech + self.centre = np.array(centre) + self.crayon = None + self.sujet = sujet + self.setStyleSheet("background-color: gray") + self.center = np.array(Afficheur.size) / 2. + self.setGeometry(Afficheur.coord[0] + screen.x(), Afficheur.coord[1] + screen.y(), Afficheur.size[0], Afficheur.size[1]) + Afficheur.coord += Afficheur.decalage + self.setWindowTitle("Afficheur") + self.show() + + def __pointVersPixel(self, p): + p = np.array(p) + pixel = (p - self.centre) * self.ech + pixel[1] = -pixel[1] + pixel += self.center + return qtcore.QPoint(pixel[0],pixel[1]) + + def __pixelVersPoint(self, pixel): + p = np.array((pixel.x(), pixel.y())) - self.center + p[1] = -p[1] + p = p / self.ech + self.centre + return p + + def __pixelVersVecteur(self, pixel1, pixel2): + assert isinstance(pixel1, qtcore.QPoint) + assert isinstance(pixel2, qtcore.QPoint) + v = np.array((pixel2.x(), pixel2.y())) - np.array((pixel1.x(), pixel1.y())) + v[1] = -v[1] + v = v / self.ech + return v + + def tracePoint(self, p): + assert self.crayon != None + assert isinstance(p, tuple) or isinstance(p, np.ndarray) + assert isinstance(p[0], float) + assert isinstance(p[1], float) + pixel = self.__pointVersPixel(p) + self.crayon.drawEllipse(pixel, self.rayon, self.rayon) + + def traceLigne(self, p1, p2): + assert self.crayon != None + assert isinstance(p1, tuple) or isinstance(p1, np.ndarray) + assert isinstance(p1[0], float) + assert isinstance(p1[1], float) + assert isinstance(p2, tuple) or isinstance(p2, np.ndarray) + assert isinstance(p2[0], float) + assert isinstance(p2[1], float) + pixel1 = self.__pointVersPixel(p1) + pixel2 = self.__pointVersPixel(p2) + self.crayon.drawLine(pixel1, pixel2) + + def changeCouleur(self, couleur): + assert self.crayon != None + assert isinstance(couleur, tuple) + assert isinstance(couleur[0], float) + assert isinstance(couleur[1], float) + assert isinstance(couleur[2], float) + c = qtgui.QColor(couleur[0]*255, couleur[1]*255, couleur[2]*255) + self.crayon.setPen(c) + self.crayon.setBrush(c) + + def traceTexte(self, p, texte): + assert isinstance(p, tuple) or isinstance(p, np.ndarray) + assert isinstance(p[0], float) + assert isinstance(p[1], float) + assert isinstance(texte, str) + pixel = self.__pointVersPixel(p) + pixel += qtcore.QPoint(10,-10) + self.crayon.drawText(pixel, texte) + + def renomme(self, titre): +# assert isinstance(titre, str) +# titre = unicode(titre, 'utf-8') + self.setWindowTitle(titre) + + def mousePressEvent(self, QMouseEvent): + pos = QMouseEvent.pos() + self.clic = pos + pos = self.__pixelVersPoint(pos) + self.centre = self.centre + + def mouseMoveEvent(self, QMouseEvent): + pos = QMouseEvent.pos() + self.centre -= self.__pixelVersVecteur(self.clic, pos) + self.clic = pos + self.update() + + def mouseReleaseEvent(self, QMouseEvent): + pos = QMouseEvent.pos() + self.centre -= self.__pixelVersVecteur(self.clic, pos) + self.clic = pos + self.update() + + def wheelEvent(self, event): + pos = event.pos() + C = self.__pixelVersPoint(pos) + k1 = self.ech + k2 = k1 * math.exp(0.001 * event.delta()) + self.centre = (self.centre * k1 + C * (k2 -k1)) / k2 + self.ech = k2 + self.update() + + def paintEvent(self, event): + if(self.crayon != None): + return + self.crayon = qtgui.QPainter(self) + self.trace() + self.crayon = None + + def trace(self): + self.crayon.setFont(qtgui.QFont('Decorative', 10)) + if(self.sujet != None): + try: + self.sujet.trace(self) + except AttributeError: + self.close() + qtgui.QApplication.quit() + raise Exception('Méthode "trace" non définie dans la classe ' + str(self.sujet.__class__)) + except BaseException: + traceback.print_exc() + self.close() + qtgui.QApplication.quit() + L = 100.; + x = 20; y = 20; + self.crayon.setPen('white') + msg = '{0:.2f} km'.format(L / self.ech) + l = self.crayon.fontMetrics().boundingRect(msg).width() + self.crayon.drawText(x + (L-l)/2, y-2, msg); + self.crayon.drawLine(x, y, x + L, y); + self.crayon.drawLine(x, y - L/20, x, y + L/10); + self.crayon.drawLine(x + L, y - L/20, x + L, y + L/10); + + + def sauvegarde(self): + filename = qtgui.QFileDialog.getSaveFileName(self, 'Fichier PDF') + if filename: + printer = qtgui.QPrinter(qtgui.QPrinter.HighResolution) + printer.setPageSize(qtgui.QPrinter.A4) + printer.setColorMode(qtgui.QPrinter.Color) + printer.setOutputFormat(qtgui.QPrinter.PdfFormat) + printer.setOutputFileName(filename) + + self.crayon = qtgui.QPainter(printer) + self.trace() + self.crayon = None + +def affiche(sujet, centre, ech, blocage = True): + '''Affiche dans une fenêtre l'objet sujet qui doit avoir une méthode trace''' + assert isinstance(centre, tuple) + assert isinstance(ech, float) + sujet = copy.deepcopy(sujet) + app = qtgui.QApplication.instance() + if not app: + app = qtgui.QApplication(sys.argv) + a = Afficheur(sujet, centre, ech) + Afficheur.afficheurs.append(a) + if(blocage): + bloque() + return a + +def bloque(): + app = qtgui.QApplication.instance() + if not app: + app = qtgui.QApplication(sys.argv) + app.exec_() + diff --git a/polygone.py b/polygone.py new file mode 100644 index 0000000..50cd257 --- /dev/null +++ b/polygone.py @@ -0,0 +1,109 @@ +# coding: utf-8 +# La ligne précédente est indispensable pour accpeter les accents dans le fichier + +# Importe les modules qui seront nécessaires +import math # Fonctions mathématiques : math.cos, math.sin, math.pi, etc +import random # Générateurs de nombres aléatoires +import numpy # Calcul numérique sur des vecteurs, matrices, etc + +# Importe le fichier graphique.py qui +import graphique + +class Sommet: + '''Classe représentant un sommet d'un polygone''' + + def __init__(self, theta, r): + '''Constructeur de la classe Sommet : (theta,r) sont les coordonnées polaires du sommet self''' + self.deplace(theta, r) + + def deplace(self, theta, r): + '''Déplace le sommet self: (theta,r) sont les nouvelles coordonnées polaires''' + + # Sauvegarde les coordonnées polaires dans des attributs de l'objet self + self.theta = theta + self.r = r + + # Calcule les coordonnées cartésiennes (pour l'affichage) + # On utilise la bibliothèque de calcul numérique numpy. + self.pos = numpy.array([math.cos(theta), math.sin(theta)]) * r + + def __str__(self): + '''Fonction spéciale de conversion en chaîne de caractères''' + + chaine = '({:0.2f},{:0.2f})'.format(self.pos[0], self.pos[1]) + return chaine + +class Polygone: + ''' Classe représentant un polygone du plan''' + + def __init__(self, n, R): + '''Constructeur d'un objet Polygone : crée un polygone régulier + à n sommets placés sur le cercle de rayon R centré sur l'origine''' + + # Vérifie que les arguments ont le type attendu + assert isinstance(n, int) + assert isinstance(R, float) + + # Crée comme attribut de self une liste pour accueillir les sommets + self.sommets = [] + + # Crée un attribut pour stocker le nom du polygone + self.nom = str(n) + "-polygone régulier" + + # Remplie la liste des n sommets + alpha = 2. * math.pi / n + angle = math.pi / 2. + for _ in range(n): + self.sommets.append(Sommet(angle, R)) + angle += alpha + + def __str__(self): + '''Fonction spéciale de conversion en chaîne de caractères''' + chaine = self.nom + ' = (' + if(len(self.sommets) > 0): + chaine += str(self.sommets[0]) + for s in self.sommets[1:]: + chaine += ', ' + str(s) + chaine += ')' + return chaine + + def secoue(self, amplitudeAngulaire, amplitudeRadiale): + '''Fonction qui déplace aléatoirement chaque sommet selon une amplitude angulaire et radiale réglable''' + + # Modifie les coordonnées de chaque sommet à l'aide de la méthode deplace + for s in self.sommets: + s.deplace( + s.theta + (random.random() * 2. - 1.) * amplitudeAngulaire, + s.r * (1. + (random.random() * 2. - 1.) * amplitudeRadiale) + ) + + # Change le nom du polygone + self.nom = str(len(self.sommets)) + "-polygone secoué" + + + def trace(self, afficheur): + '''Fonction de dessin''' + + assert isinstance(afficheur, graphique.Afficheur) + + afficheur.renomme(self.nom) + precedent = self.sommets[-1] + afficheur.changeCouleur((0.,0.,0.)) + + for suivant in self.sommets: + afficheur.traceLigne(precedent.pos, suivant.pos) + precedent = suivant + + afficheur.changeCouleur((1.,0.,0.)) + for sommet in self.sommets: + afficheur.tracePoint(sommet.pos) + + +if __name__ == "__main__": + triangle = Polygone(3, 10.) + print(triangle) + graphique.affiche(triangle, (0., 0.), 10., blocage = False) + + heptagone = Polygone(7, 10.) + heptagone.secoue(math.pi / 5, 0.1) + graphique.affiche(heptagone, (0., 0.), 10.) diff --git a/tas.py b/tas.py new file mode 100644 index 0000000..47f63df --- /dev/null +++ b/tas.py @@ -0,0 +1,136 @@ +# coding: utf-8 + +class Tas: + class Element: + def __init__(self, valeur, priorite, index): + self.valeur = valeur + self.priorite = priorite + self.index = index + + def __str__(self): + return str(self.valeur) + + def __init__(self, fonctionPriorite): + self.L = [] + self.fonctionPriorite = fonctionPriorite + + def __str__(self): + s = "[" + for elt in self.L: + s += " " + str(elt) + s += "]" + return s + + def __parent(self, elt): + if(elt.index == 0): + return None + else: + return self.L[(elt.index-1) // 2] + + def __fils_gauche(self, elt): + i = 2 * elt.index + 1 + if(i < len(self.L)): + return self.L[i] + else: + return None + + def __fils_droit(self, elt): + i = 2 * elt.index + 2 + if(i < len(self.L)): + return self.L[i] + else: + return None + + def __deplace(self, elt, index): + assert isinstance(elt, Tas.Element) + ancien = elt.index + self.L[index] = elt + elt.index = index + return ancien + + def __promeut(self, elt): + while(True): + parent = self.__parent(elt) + if(parent == None): + break + if(parent.priorite >= elt.priorite): + break + elt.index = self.__deplace(parent, elt.index) + self.__deplace(elt, elt.index) + + def actualise(self, elt): + assert isinstance(elt, Tas.Element) + elt.priorite = self.fonctionPriorite(elt.valeur) + self.__promeut(elt) + + def ajoute(self, valeur): + elt = Tas.Element(valeur, self.fonctionPriorite(valeur), len(self.L)) + self.L.append(elt) + self.__promeut(elt) + return elt + + def empty(self): + return len(self.L) == 0 + + def pop(self): + n = len(self.L) + if(n == 0): + return None + + tete = self.L[0] + elt = self.L[n - 1] + elt.index = 0 + + while True: + filsGauche = self.__fils_gauche(elt) + filsDroit = self.__fils_droit(elt) + plusPrioritaire = elt + if(filsGauche != None and filsGauche.priorite > plusPrioritaire.priorite): + plusPrioritaire = filsGauche + if(filsDroit != None and filsDroit.priorite > plusPrioritaire.priorite): + plusPrioritaire = filsDroit + elt.index = self.__deplace(plusPrioritaire, elt.index) + if(plusPrioritaire is elt): + break + self.L.pop() + return tete.valeur + +######################################### +# Exemple d'utilisatio de la classe Tas # +# Tri de tâches par ordre chronologique # +######################################### + +class Tache: + '''Tache devant être effectuée avant une date limite''' + def __init__(self, jour, nom): + self.jour = jour + self.nom = nom + # Cet attribut servira à accueillir la clé du tas + self.cle = None + + def __str__(self): + return self.nom + " ({})".format(self.jour) + +if __name__ == "__main__": + + # Le niveau de priorité d'un événement x est -x.jour : on trie donc les événements par ordre chronologique + def calculePriorite(tache): + return -tache.jour + + # On crée un tas dont les éléments sont des événements triés par ordre chronologique + T = Tas(calculePriorite) + + # Ajoute des événements au tas + evts = [ Tache(5, "T1"), Tache(10, "T2"), Tache(5, "T3"), Tache(3, "T4")] + for evt in evts: + evt.cle = T.ajoute(evt) + + # Supposons que la tache 2 devienne soudain assez urgente + evts[1].jour = 4 + T.actualise(evts[1].cle) + + # Retirons dans l'ordre chronologie les éléments du tas + while(not T.empty()): + print(T.pop()) + + diff --git a/test.py b/test.py new file mode 100644 index 0000000..7a3e89b --- /dev/null +++ b/test.py @@ -0,0 +1,172 @@ +import graphe +import graphique +import copy +import numpy as np +import matplotlib.pyplot as pl +import time + + +def creerGrapheFigure1(): + """ Crée le graphe de la figure 1 """ + g = graphe.Graphe("Graphe de la figure 1") + s1 = g.ajouteSommet(1.0, 1.0) + s2 = g.ajouteSommet(2.0, 3.5) + s3 = g.ajouteSommet(2.5, 2.5) + s4 = g.ajouteSommet(5.0, 2.0) + s1.couleur = (1., 1., 0.) + s2.couleur = (0., 0., 1.) + s3.couleur = (1., 0., 0.) + s4.couleur = (1., 1., 0.) + a = g.connecte(s1, s2, 4.0, 90.) + a.couleur = (1., 1., 0.) + g.connecte(s1, s4, 5.2, 124.) + g.connecte(s2, s3, 2.0, 54.) + g.connecte(s2, s4, 5.0, 90.) + return g + + +def testQuestion1_2(): + """' Teste que la création d ' un graphe ne plante pas + print ”Question 1.2 :""" + creerGrapheFigure1() + print("Ok. Pas de plantage") + + +def testQuestion1_3(): + """ Teste l ' affichage d ' un graphe dans la console""" + print("Question 1.3 :") + g = creerGrapheFigure1() + print(g) + + +def testQuestion1_4(): + """ Teste du dessin d'un graphe.""" + print("Question 1.4:") + graphique.affiche(creerGrapheFigure1(), (3., 2.), 100.) + + +def testQuestion2_2(): + """Teste de génération aléatoire de graphe.""" + print("Question 2.2:") + graphique.affiche(graphe.pointsAleatoires(5, 30), (0, 0), 10.) + + +def testQuestion2_4(): + """Teste de gabriel et gvr.""" + print("Question 2.4") + g = graphe.pointsAleatoires(30, 30) + g.renomme("Gabriel") + g1 = copy.deepcopy(g) + g1.renomme("GVR") + graphe.gabriel(g) + graphe.gvr(g1) + graphique.affiche(g, (0, 0), 10.) + graphique.affiche(g1, (0, 0), 10.) + + +def testQuestion2_5(): + """Teste de la création de réseau.""" + g = graphe.pointsAleatoires(30, 30) + graphe.reseau(g) + graphique.affiche(g, (0, 0), 10.) + + +def chronometre(fonctionTest, fonctionPreparation, parametres): + ''' Mesure le temps d ' exécution fonctionTest pour différentes valeurs d ' + un paramètres ''' + temps = [] + # Pour chaque valeur de paramètre + for p in parametres: + # Génère les entrées du test pour la valeur p + entrees = fonctionPreparation(p) + # Lance le test pour ces entrées + print("t({}) = ".format(p), end="", flush=True) + debut = time.time() + fonctionTest(entrees) + fin = time.time() + # Mesure le temps d ' exécution + t = (fin - debut) + print("{:.2f} s".format(t)) + temps.append(t) + return temps + +def testQuestion2_6(): + """Mesure la performance de graphe.reseau""" + prepare = lambda p : graphe.pointsAleatoires(p, 10) + valeurs_n = np.arange(1, 200) + temps = chronometre(graphe.reseau, prepare, valeurs_n) + pl.close('all') + pl.title("Mesure du temps d'exécution de `reseau`.") + pl.plot(valeurs_n, temps) + pl.xlabel("n") + pl.ylabel("temps") + pl.show() + pl.title("Mesure du temps d'exécution de `reseau`.") + pl.loglog(valeurs_n, temps, label='temps de calcul') + pl.loglog(valeurs_n, (lambda x : x**3)(valeurs_n), label='$x\mapsto x^3$') + pl.legend(loc='best') + pl.xlabel("n") + pl.ylabel("temps") + pl.show() + +def testQuestion2_7(): + """Compare la création de graphe de gabriel et ed delaunay.""" + prepare = lambda p : graphe.pointsAleatoires(p, 10) + valeurs_n = np.arange(1, 200) + temps1 = chronometre(graphe.gabriel, prepare, valeurs_n) + temps2 = chronometre(graphe.delaunay, prepare, valeurs_n) + pl.close('all') + pl.title("Comparaison du temps pour `delaunay` et `gabriel`") + pl.plot(valeurs_n, temps1, label='gabriel') + pl.plot(valeurs_n, temps2, label='delaunay') + pl.legend(loc='best') + pl.xlabel('n') + pl.ylabel('temps') + pl.show() + +def testQuestion2_8(): + """Compare le reseau naif et non naif.""" + g = graphe.pointsAleatoires(30, 30) + g1 = copy.deepcopy(g) + g.renomme("Naïf") + g1.renomme("Non naïf") + + graphe.reseau(g) + graphe.reseauRapide(g1) + graphique.affiche(g, (0, 0), 10.) + graphique.affiche(g1, (0, 0), 10.) + +def testQuestion2_9(): + """Compare le temps de création des deux méthodes de création de réseau""" + prepare = lambda p : graphe.pointsAleatoires(p, 10) + valeurs_n = list(map(lambda x: int(x), np.logspace(1, 3, 30))) + temps1 = chronometre(graphe.reseau, prepare, valeurs_n) + temps2 = chronometre(graphe.reseauRapide, prepare, valeurs_n) + pl.close('all') + pl.title("Comparaison du temps d'exécution de `reseau` et `reseauRapide`.") + pl.plot(valeurs_n, temps1, label='reseau') + pl.plot(valeurs_n, temps2, label='reseauRapide') + pl.legend(loc='best') + pl.xlabel("n") + pl.ylabel("temps") + pl.show() + pl.title("Comparaison du temps d'exécution de `reseau` et `reseauRapide`.") + pl.loglog(valeurs_n, temps1, label='reseau') + pl.loglog(valeurs_n, temps2, label='reseauRapide') + pl.legend(loc='best') + pl.xlabel("n") + pl.ylabel("temps") + pl.show() + + + +# testQuestion1_2() +# testQuestion1_3() +# testQuestion1_4() +# testQuestion2_2() +# testQuestion2_4() +# testQuestion2_5() +# testQuestion2_6() +# testQuestion2_7() +# testQuestion2_8() +testQuestion2_9() diff --git a/triangulation.py b/triangulation.py new file mode 100644 index 0000000..4115fa4 --- /dev/null +++ b/triangulation.py @@ -0,0 +1,299 @@ +# coding: utf-8 + +import sys +import math + + +def distanceAuCarre(v1, v2): + dx = (v1.x() - v2.x()) + dy = (v1.y() - v2.y()) + return dx**2 + dy**2 + + +def conditionDeDelaunay(A, B, C, D): + dab2 = distanceAuCarre(A, B) + dad2 = distanceAuCarre(A, D) + dbc2 = distanceAuCarre(B, C) + dbd2 = distanceAuCarre(B, D) + dcd2 = distanceAuCarre(C, D) + cosa = (dab2 + dad2 - dbd2) / (2 * math.sqrt(dab2) * math.sqrt(dad2)) + cosc = (dbc2 + dcd2 - dbd2) / (2 * math.sqrt(dbc2) * math.sqrt(dcd2)) + sina2 = 1. - cosa * cosa + if(sina2 > 0.): + sina = math.sqrt(sina2) + else: + sina = 0. + sinc2 = 1. - cosc * cosc + if(sinc2 > 0.): + sinc = math.sqrt(sinc2) + else: + sinc = 0. + return sina * cosc + cosa * sinc >= 0 + + +class Triangulation: + + class QuadEdge: + '''Classe définissant un quad-edge : une arête connectée à deux faces et deux sommets''' + + def __init__(self, f1, f2, v1, v2): + self.f1 = f1 + self.f2 = f2 + self.v1 = v1 + self.v2 = v2 + + def __eq__(self, e): + '''Méthode qui teste si deux quad-edges ont même extrémités''' + if((self.v1 == e.v1) and (self.v2 == e.v2)): + return True + elif((self.v1 == e.v2) and (self.v2 == e.v1)): + return True + else: + return False + + def __str__(self): + return str(self.v1) + " - " + str(self.v2) + + def remplaceFace(self, ancienneFace, nouvelleFace): + '''Méthode qui remplace une face adjacente au quad-edge par une autre''' + if(self.f1 == ancienneFace): + self.f1 = nouvelleFace + else: + self.f2 = nouvelleFace + + def lanceAlgorithme(self): + # Construit la triangulation initiale. Complexité en O(n log(n)) + self.construitTriangulationInitiale() + + # Bascule les arêtes de la triangulation jusqu'à obtenir la triangulation de Delaunay + self.basculeAretes() + + # Efface les sommets à l'infini + for _ in range(3): + self.graphe.sommets.pop() + + def __init__(self, g): + '''Constructeur de la classe triangulation qui prend en entrée un nuage de points + et crée une triangulation de Delaunay''' + self.racine = None + self.quadEdges = [] + self.graphe = g + self.lanceAlgorithme() + + class Triangle: + def __init__(self, v1, v2, v3): + self.v1 = v1 + self.v2 = v2 + self.v3 = v3 + self.t12 = None + self.t13 = None + self.t23 = None + self.e12 = None + self.e13 = None + self.e23 = None + + def contient(self, v): + ''' Méthode qui teste si un sommet est à l'intérieur d'un triangle''' + v1 = self.v1 + v2 = self.v2 + v3 = self.v3 + det = (v1.x() - v3.x()) * (v2.y() - v3.y()) - \ + (v1.y() - v3.y()) * (v2.x() - v3.x()) + c1 = ((v2.y() - v3.y()) * (v.x() - v3.x()) + + (v3.x() - v2.x()) * (v.y() - v3.y())) * det + c2 = ((v3.y() - v1.y()) * (v.x() - v3.x()) + + (v1.x() - v3.x()) * (v.y() - v3.y())) * det + return (c1 > 0) and (c2 > 0) and (c1 + c2 < det * det) + + def trouve(self, v): + '''Méthode récursive qui renvoie le triangle d'une triangulation qui contient un sommet. + Renvoie le triangle contenant v, None si v n'est pas dans la triangulation''' + + t12 = self.t12 + t13 = self.t13 + t23 = self.t23 + if(t12 == None): + return self + else: + if(t12.contient(v)): + return t12.trouve(v) + elif(t13.contient(v)): + return t13.trouve(v) + elif(t23.contient(v)): + return t23.trouve(v) + else: + return None + + def aPourSommet(self, v): + '''Méthode qui teste si un sommet est un sommet d'un triangle + Renvoie vrai si v est un sommet du triangle''' + return self.v1 == v or self.v2 == v or self.v3 == v + + def troisiemeSommet(self, a, b): + ''' Renvoie le troisième sommet d'un triangle à partir des deux autres''' + + if(self.v1 != a and self.v1 != b): + return self.v1 + elif(self.v2 != a and self.v2 != b): + return self.v2 + else: + return self.v3 + + def remplaceSommet(self, ancienSommet, nouveauSommet): + ''' Méthode qui substitue un sommet par un autre dans un triangle ''' + if(self.v1 == ancienSommet): + self.v1 = nouveauSommet + elif(self.v2 == ancienSommet): + self.v2 = nouveauSommet + elif(self.v3 == ancienSommet): + self.v3 = nouveauSommet + else: + raise Exception("Bad vertex" + ancienSommet) + + def retrouveArete(self, v1, v2): + '''Méthode qui renvoie le quad-edge d'un triangle ayant deux sommets pour extrêmités''' + e = Triangulation.QuadEdge(None, None, v1, v2) + if(self.e12 == e): + return self.e12 + elif(self.e13 == e): + return self.e13 + elif(self.e23 == e): + return self.e23 + else: + raise Exception("getEdge") + + def remplaceArete(self, ancienneArete, nouvelleArete): + '''Méthode qui substitue le quad-edge d'un triangle par un autre''' + if(self.e12 == ancienneArete): + self.e12 = nouvelleArete + elif(self.e13 == ancienneArete): + self.e13 = nouvelleArete + elif(self.e23 == ancienneArete): + self.e23 = nouvelleArete + else: + raise Exception("changeEdge") + + def construitTriangleInitial(self): + '''Méthode qui construit une triangulation initiale qui contient tous les sommets''' + xmin = sys.float_info.max + xmax = sys.float_info.min + ymin = sys.float_info.max + ymax = sys.float_info.min + + for v in self.graphe.sommets: + if(v.x() > xmax): + xmax = v.x() + if(v.x() < xmin): + xmin = v.x() + if(v.y() > ymax): + ymax = v.y() + if(v.y() < ymin): + ymin = v.y() + + largeur = xmax - xmin + hauteur = ymax - ymin + margex = 0.1 * largeur + margey = 0.1 * hauteur + + v1 = self.graphe.ajouteSommet( + xmin - largeur / 2 - margex, ymin - margey) + v2 = self.graphe.ajouteSommet( + xmax + largeur / 2 + margex, ymin - margey) + v3 = self.graphe.ajouteSommet( + (xmin + xmax) / 2, ymax + hauteur + margey) + + self.racine = Triangulation.Triangle(v1, v2, v3) + + e12 = Triangulation.QuadEdge(None, self.racine, v1, v2) + e13 = Triangulation.QuadEdge(None, self.racine, v1, v3) + e23 = Triangulation.QuadEdge(None, self.racine, v2, v3) + self.racine.e12 = e12 + self.racine.e13 = e13 + self.racine.e23 = e23 + + def supprimeAretes(self): + for s in self.graphe.sommets: + s.aretes.clear() + + def construitTriangulationInitiale(self): + self.quadEdges = [] + self.construitTriangleInitial() + + for v in self.graphe.sommets: + t = self.racine.trouve(v) + if(t != None): + t.t12 = Triangulation.Triangle(t.v1, t.v2, v) + t.t13 = Triangulation.Triangle(t.v1, t.v3, v) + t.t23 = Triangulation.Triangle(t.v2, t.v3, v) + + e1v = Triangulation.QuadEdge(t.t12, t.t13, t.v1, v) + e2v = Triangulation.QuadEdge(t.t12, t.t23, t.v2, v) + e3v = Triangulation.QuadEdge(t.t13, t.t23, t.v3, v) + self.quadEdges.append(e1v) + self.quadEdges.append(e2v) + self.quadEdges.append(e3v) + + t.t12.e12 = t.e12 + t.e12.remplaceFace(t, t.t12) + t.t12.e13 = e1v + t.t12.e23 = e2v + + t.t13.e12 = t.e13 + t.e13.remplaceFace(t, t.t13) + t.t13.e13 = e1v + t.t13.e23 = e3v + + t.t23.e12 = t.e23 + t.e23.remplaceFace(t, t.t23) + t.t23.e13 = e2v + t.t23.e23 = e3v + + def basculeAretes(self): + pile = [] + for qe in self.quadEdges: + pile.append(qe) + + while(len(pile) > 0): + e = pile.pop() + + f1 = e.f1 + f2 = e.f2 + if(f1 != None and f2 != None): + v1 = e.v1 + v2 = e.v2 + + v3 = f1.troisiemeSommet(v1, v2) + v4 = f2.troisiemeSommet(v1, v2) + + if(not conditionDeDelaunay(v3, v1, v4, v2)): + v1v3 = f1.retrouveArete(v1, v3) + v2v3 = f1.retrouveArete(v2, v3) + v1v4 = f2.retrouveArete(v1, v4) + v2v4 = f2.retrouveArete(v2, v4) + f1.remplaceArete(v2v3, v1v4) + f2.remplaceArete(v1v4, v2v3) + v2v3.remplaceFace(f1, f2) + v1v4.remplaceFace(f2, f1) + f1.remplaceSommet(v2, v4) + f2.remplaceSommet(v1, v3) + e.v1 = v3 + e.v2 = v4 + pile.append(v1v3) + pile.append(v2v3) + pile.append(v1v4) + pile.append(v2v4) + + def construitGrapheDeSortie(self, selectionneArete): + '''Méthode qui construit la triangulation de Delaunay à partir du nuage de points passé au constructeur''' + + # Sélectionne les arêtes + graphe = self.graphe + racine = self.racine + for qe in self.quadEdges: + v1 = qe.v1 + v2 = qe.v2 + if(not (racine.aPourSommet(v1) or racine.aPourSommet(v2))): + v3 = qe.f1.troisiemeSommet(v1, v2) + v4 = qe.f2.troisiemeSommet(v1, v2) + selectionneArete(graphe, v1, v2, v3, v4) + return self.graphe