stu:python_gui:pyqt_projet_charges

Exemple de projet : Lignes équipotentielles

- Présentation

Nous nous proposons ici d'évaluer le champ et le potentiel électrostatique dans un plan sur lequel sont distribuées des charges. Pour une distribution discrète de charges $q_i$ aux points $P_i$ du plan, le potentiel et le champ électrostatiques au point $M$ sont donnés par : $$V(M)=\sum_i{q_i\over4\pi\epsilon_0}{1\over{\|{\overrightarrow{P_iM}}\|}}$$ $$\overrightarrow{E}(M)=\sum_i{q_i\over4\pi\epsilon_0}{\overrightarrow{P_iM}\over\|\overrightarrow{P_iM}\|^3}$$

Selon les données du problème, il existe de nombreuses façons de le résoudre. Nous allons ici tirer parti de l'utilisation de l'ordinateur pour tracer des lignes équipotentielles alors que la solution explicite n'est pas simplement calculable.

L'utilisateur devra entrer les positions des charges. L'écran est constitué de pixels carrés. Nous pouvons évaluer le potentiel en chacun de ces points, les sources étant connues, par les formules données plus haut.

Les valeurs calculées seront utilisée pour localiser les équipotentielles pour lesquelles l'utilisateur donnera une valeur min, une valeur max et un pas.

- Organisation de l'interface

Nous créons, pour tester notre interface une fenêtre principale contenant à gauche une partie commande et à droite une partie dessin. La partie commande contient de haut en bas, un tableau permettant d'éditer les positions des charges, un bouton, et des zones d'édition pour préciser les équipotentielles à tracer.

from PySide import QtCore, QtGui
import sys
 
class Dessin(QtGui.QWidget) :
    def __init__(self,parent=None) :
        super().__init__(parent)
        self.image=None
 
    def paintEvent(self,e) :
        p=QtGui.QPainter(self)
        p.fillRect(self.rect(),QtCore.Qt.blue)
        if self.image != None :
            p.drawPixmap(0,0,self.image)
 
 
class MainWindow(QtGui.QMainWindow) :
    def calcule(self)  : pass
 
    def __init__(self,parent=None) :
        super().__init__(parent)
        # Création des widgets principaux :
        self.table=QtGui.QTableWidget(10,3,self)
        self.table.setColumnWidth(0,70)
        self.table.setColumnWidth(1,70)
        self.table.setColumnWidth(2,70)
        self.table.setHorizontalHeaderLabels(['x','y','q'])
        self.dessin=Dessin(self)
        self.dessin.setMinimumSize(300,300)
 
        # Création des layouts 
        hbox=QtGui.QHBoxLayout()
        hbox.setSpacing(30)
        vbox=QtGui.QVBoxLayout()
        vbox.setSpacing(2)
        layeq=QtGui.QGridLayout()
 
        # Imbrication des layouts et ajout des widgets
        hbox.addLayout(vbox)
        hbox.addWidget(self.dessin,stretch=1)
        vbox.addWidget(self.table,stretch=-1) # ne sera pas redimensionné
        button=QtGui.QPushButton('Calcul')
        vbox.addWidget(button)
        button.clicked.connect(self.calcule)
        vbox.addLayout(layeq)
        self.eqmin=QtGui.QLineEdit('-10')
        self.eqpas=QtGui.QLineEdit('0.5')
        self.eqmax=QtGui.QLineEdit('10')
        layeq.addWidget(QtGui.QLabel("Eq mini"),0,0)
        layeq.addWidget(QtGui.QLabel("Eq pas"),1,0)
        layeq.addWidget(QtGui.QLabel("Eq maxi"),2,0)
        layeq.addWidget(self.eqmin,0,1)
        layeq.addWidget(self.eqpas,1,1)
        layeq.addWidget(self.eqmax,2,1)
 
        # Création du Widget central et de la barre de status
        frame=QtGui.QWidget()
        frame.setLayout(hbox)
        self.setCentralWidget(frame)
        self.statusBar().showMessage("Calcul d'équipotentielles")
 
 
app=QtGui.QApplication(sys.argv)
frame=MainWindow()
frame.show()
sys.exit(app.exec_())

La méthode ''self.dessin.setStyleSheet(“* { background-color : blue;}”); ne permet pas de changer la couleur de fond d'un widget de type QWidget. Pour changer la couleur de fond d'un tel objet, il faut redéfinir la méthode paintEvent et dessiner un rectangle bleu. La méthode basée sur setStyleSheet fonctionne néanmoins pour les objets plus évolués (QLabel, QPushButton…) </WRAP>

Il est nécessaire de donner une taille minimum à la zone de dessin : self.dessin.setMinimumSize(300,300). Sans quoi, sa taille minimum sera 0 et elle n'apparaîtra pas à l'écran.

Dès cette étape de construction, le programme est testable et doit donner quelque chose comme : ===== - Méthode de Calcul ===== Le calcul est relativement simple à faire. Il est géré par la méthode calcule. La méthode commence par créer la liste des charges, en relevant les valeurs données dans la table. Nous décidons que la fenêtre graphique représentera cette partie du plan $-1<x<1$, $-1<y<1$. ==== - Calcul du potentiel ==== Le calcul du potentiel est réalisé dans la méthode calculepot. Pour chaque point de la fenêtre $(i,j)$, nous lui associons ses coordonnées réelles $(x,y)$ et calculons le potentiel : $$V(x,y)=\frac{1}{4\pi\varepsilon_0}\sum_{i} \frac{q_i}{\|{\overrightarrow{P_iM_{x,y}}}\|}}$$ Ces valeurs sont stockées dans un tableau de type numpy.array. ==== - Tracé ==== Le tracé est réalisé dans une image, dans la fonction draw. L'image est ensuite affichée dans la fenêtre. Chaque point de l'image est colorié selon le signe du potentiel en ce point. Puis, on recherche si une équipotentielle passe en ce point (pour cela on regarde si le point et un de ses voisins sont situés de part et d'autres d'une des valeurs d'équipotentielles à tracer). Enfin, on trace les charges <file python equipotentielles.py> from PySide import QtCore, QtGui import sys import math import numpy class Dessin(QtGui.QWidget) : def init(self,parent=None) : super().init(parent) self.image=None def paintEvent(self,e) : p=QtGui.QPainter(self) p.fillRect(self.rect(),QtCore.Qt.blue) if self.image != None : p.drawPixmap(0,0,self.image) class MainWindow(QtGui.QMainWindow) : def calcule(self) : “”“ Relève les valeurs des charges ainsi que les caractéristiques des équipotentielles.Calcule le potentiel en chaque point, trace le potentiel et les équipotentielles ”“” self.statusBar().showMessage(“Calculs en cours…”) self.statusBar().showMessage(“Calculs en cours”) # Lecture des valeurs et positions des charges listecharges=[] for i in range(self.table.rowCount()) : if all(type(self.table.item(i,j))==QtGui.QTableWidgetItem for j in range(3)) : print([self.table.item(i,j).text() for j in range(3)]) try : x,y,v=(float(self.table.item(i,j).text()) for j in range(3)) listecharges.append1) except ValueError : pass # Lancement du calcul du potentiel en chaque point. # Ce potentiel est stocké dans un numpy.array (v) v=self.calculpot(listecharges) #print(“MINMAX”,v.min(),v.max()) # Calcul des valeurs des équipotentielles à partir de leurs caractéristiques # entrées dans la fenêtre eq_min=float(self.eqmin.displayText()) eq_max=float(self.eqmax.displayText()) eq_pas=float(self.eqpas.displayText()) liste_eq=[eq_min] while (liste_eq[-1]<eq_max) : liste_eq.append(liste_eq[-1]+eq_pas) # Trace le potentiel (v), les équipotentielles (liste_eq) et dessine les charges (listecharges) self.draw(v,listecharges,liste_eq) self.statusBar().showMessage(“Calculs terminés…”,5000) def calculpot(self,lc) : “”“ Calcule le potentiel en chaque point de la fenêtres. Les charges sont données dans lc (liste) et le potentiel est stocké dans un numpy.array ”“” def tr_coord(x,y) : “ Transforme les coord fenêtre en coord dans (-1,1) ” x=x/self.dessin.width()*2-1 y=y/self.dessin.height()*2-1 return (x,y) def distance(p1,p2) : “ Renvoie la distance entre deux points ” return math.sqrt2) </file>

Notez qu'on évite les divisions par zéro avec une astuce assez honteuse : d=max(distance(l[0:2],c),0.0000000001).

Voici le type de tracé que nous obtenons (en étant patient…) : ===== - Utilisation de Matplotlib ===== Le module Pyplot de Matplotlib permet de réaliser des graphiques assez évolués. Plutôt que de dessiner nous même les équipotentielles, il est possible d'utiliser des fonctions comme contour ou contourf pour avoir des tracés similaires. Voici un exemple de slot, qui pourrait être associé à un autre bouton et réaliserai le tracé avec matplotlib : <code python> import matplotlib.pyplot as plt def calcule2(self) : “”“ Relève les valeurs des charges ainsi que les caractéristiques des équipotentielles.Calcule le potentiel en chaque point, trace le potentiel et les équipotentielles ”“” self.statusBar().showMessage(“Calculs en cours…”) self.statusBar().showMessage(“Calculs en cours”) # Lecture des valeurs et positions des charges listecharges=[] for i in range(self.table.rowCount()) : if all(type(self.table.item(i,j))==QtGui.QTableWidgetItem for j in range(3)) : print([self.table.item(i,j).text() for j in range(3)]) try : x,y,v=(float(self.table.item(i,j).text()) for j in range(3)) listecharges.append3) except ValueError : pass # Lancement du calcul du potentiel en chaque point. # Ce potentiel est stocké dans un numpy.array (v) v=self.calculpot(listecharges) # Calcul des valeurs des équipotentielles à partir de leurs caractéristiques # entrées dans la fenêtre eq_min=float(self.eqmin.displayText()) eq_max=float(self.eqmax.displayText()) eq_pas=float(self.eqpas.displayText()) liste_eq=[eq_min] while (liste_eq[-1]<eq_max) : liste_eq.append(liste_eq[-1]+eq_pas) # Tracé avec matplotlib fig = plt.figure() X=numpy.linspace(-1,1,v.shape[0]) Y=numpy.linspace(-1,1,v.shape[1]) print(X.shape,Y.shape,v.shape) plt.contour(X,Y,numpy.transpose(v),liste_eq) plt.show() # =En mode non interactif, cette commande est blocante. self.statusBar().showMessage(“Calculs terminés…”,5000) </code> On obtient ainsi un tracé de ce genre : Ou avec la fonction contourf à la place de contour'' :

1) , 3)
x,y,v
2)
p2[1]-p1[1])2+(p2[0]-p1[0])2)
      w,h=self.dessin.width(),self.dessin.height()
      v=numpy.zeros((w,h),dtype=numpy.float)
      # On prend la permitivité à 1 pour manipuler des nombres plus agréables
      coef=1/(4*math.pi*1)#8.85e-12)
      for i in range(v.shape[0]) :
          for j in range(v.shape[1]) :
              v[i][j]=0
              c=tr_coord(i,j)
              for l in lc :
                  d=max(distance(l[0:2],c),0.0000000001)
                  v[i][j]+=l[2]/d
              v[i][j]=v[i][j]*coef
      return v
  
  def draw(self,v,lc,equ) :
      """ Trace le potentiel v, les charges lc et les équipotentielles equ """
      def tr_coord(x,y) :
          " Transforme des coordonnées dans (-1,1) en coordonnées fenêtre "
          x=int((x+1)*self.dessin.width()/2)
          y=int((y+1)*self.dessin.height()/2)
          return QtCore.QPoint(x,y)
      
      im=QtGui.QPixmap(self.dessin.rect().size())
      im.fill(QtCore.Qt.white)
      p=QtGui.QPainter(im)
      # Pour chaque point du tableau v
      for i in range(1,v.shape[0]) :
          for j in range(1,v.shape[1]) :
              # Tracé du potentiel (couleur dif si nég. ou pos.)
              if v[i][j]<0 : p.setPen(QtGui.QColor(240,240,255))
              else : p.setPen(QtGui.QColor(255,240,240))
              p.drawPoint(i,j)
              # Une équipotentielle passe par ce point ?
              for e in equ :
                  if (v[i][j]-e)*(v[i-1][j]-e)<0 or (v[i][j]-e)*(v[i][j-1]-e)<0 :
                      if e==0 :  p.setPen(QtCore.Qt.black)
                      elif e<0 :  p.setPen(QtCore.Qt.blue)
                      else  :  p.setPen(QtCore.Qt.red)
                      p.drawPoint(i,j)
                      break
              
      # Affichage des charges
      for l in lc :
          print(l[0:2])
          print(tr_coord(*l[0:2]))
          if l[2]>0 : p.setBrush(QtCore.Qt.red)
          else : p.setBrush(QtCore.Qt.blue)
          p.drawEllipse(tr_coord(*l[0:2]),2,2)
      self.dessin.image=im
      self.dessin.repaint()
      
      
  def __init__(self,parent=None) :
      super().__init__(parent)
      # Création des widgets principaux :
      self.table=QtGui.QTableWidget(10,3,self)
      self.table.setColumnWidth(0,70)
      self.table.setColumnWidth(1,70)
      self.table.setColumnWidth(2,70)
      self.table.setHorizontalHeaderLabels(['x','y','q'])
      self.dessin=Dessin(self)
      self.dessin.setMinimumSize(300,300)
      
      # Création des layouts 
      hbox=QtGui.QHBoxLayout()
      hbox.setSpacing(30)
      vbox=QtGui.QVBoxLayout()
      vbox.setSpacing(2)
      layeq=QtGui.QGridLayout()
      # Imbrication des layouts et ajout des widgets
      hbox.addLayout(vbox)
      hbox.addWidget(self.dessin,stretch=1)
      vbox.addWidget(self.table,stretch=-1) # ne sera pas redimensionné
      button=QtGui.QPushButton('Calcul')
      vbox.addWidget(button)
      button.clicked.connect(self.calcule)
      vbox.addLayout(layeq)
      self.eqmin=QtGui.QLineEdit('-10')
      self.eqpas=QtGui.QLineEdit('0.5')
      self.eqmax=QtGui.QLineEdit('10')
      layeq.addWidget(QtGui.QLabel("Eq mini"),0,0)
      layeq.addWidget(QtGui.QLabel("Eq pas"),1,0)
      layeq.addWidget(QtGui.QLabel("Eq maxi"),2,0)
      layeq.addWidget(self.eqmin,0,1)
      layeq.addWidget(self.eqpas,1,1)
      layeq.addWidget(self.eqmax,2,1)
      
      # Création du Widget central et de la barre de status
      frame=QtGui.QWidget()
      frame.setLayout(hbox)
      self.setCentralWidget(frame)
      self.statusBar().showMessage("Calcul d'équipotentielles")
      
      
app=QtGui.QApplication(sys.argv) frame=MainWindow() frame.show() sys.exit(app.exec_(
stu/python_gui/pyqt_projet_charges.txt · Dernière modification: 2015/02/23 14:13 (modification externe)