Probleme mit funktionen plotten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Jaga
User
Beiträge: 64
Registriert: Mittwoch 21. August 2013, 13:18

Hallo zusammen,
mein Problem ist eigentlich relativ einfach umschrieben. Bei meinem Funktionsplotter funktionierte die Plot-Funktion einwandfrei, bis ich etwas an der Ableitung verändert habe. Jetzt wird das Koordinatensystem angezeit und auch ein Kästchen mit der Funktion und zugehörigen farbe aber die eigentliche Funktion ist nicht da. Wo ist mein Fehler?

Danke im voraus!

Hier mein Code:

Code: Alles auswählen

import matplotlib.pyplot as plt
import numpy as np
import Tkinter
from sympy.parsing.sympy_parser import parse_expr
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import parser
import re
import sympy as sp

REPLACE_DIC = {'sin' : 'np.sin',
               'arcsin' : 'np.arcsin',
               'sinh' : 'np.sinh',
               'arcsinh' : 'np.arcsinh',
               'cos' : 'np.cos',
               'arccos' : 'np.arccos',
               'cosh' : 'np.cosh',
               'arccosh' : 'np.arccosh',
               'tan' : 'np.tan',
               'arctan' : 'np.arctan',
               'tanh' : 'np.tanh',
               'arctanh' : 'np.arctanh',
               'ln' : 'np.log',
               'log' : 'np.log',
               'log10' : 'np.log10',
               'exp' : 'np.exp',
               '^' : '**',
               'sqrt' : 'np.sqrt',
               'pi' : 'np.pi',
               
               }
class App():

    def __init__(self, master):

        self.master = master
        master.protocol("WM_DELETE_WINDOW", self.ende)
        self.initUI()

    def ende(self):
        self.master.destroy()
        self.master.quit()

    def initUI(self):
        

        
        self.master.title("Funktionsplotter")

           
        
        
        self.label_funktion = Tkinter.Label(self.master, text = "Funktion: f(x)")
        self.label_funktion.grid(row = 0, column = 0)

        self.entry_funktion = Tkinter.Entry(self.master, width = 120)
        self.entry_funktion.grid(row = 0, column = 1)

        self.label_startwert = Tkinter.Label(self.master, text = "Startwert der Funktion:")
        self.label_startwert.grid(row = 2, column = 0)

        self.entry_startwert = Tkinter.Entry(self.master, width = 15)
        self.entry_startwert.grid(row = 2, column = 1)

        self.label_endwert = Tkinter.Label(self.master, text = "Endwert der Funktion:")
        self.label_endwert.grid(row = 3, column = 0)

        self.entry_endwert = Tkinter.Entry(self.master, width = 15)
        self.entry_endwert.grid(row = 3, column = 1)

        self.label_genauigkeit = Tkinter.Label(self.master, text = "Abstand zwischen zu berechnenden Punkten:")
        self.label_genauigkeit.grid(row = 4, column = 0)

        self.entry_genauigkeit = Tkinter.Entry(self.master, width = 15)
        self.entry_genauigkeit.grid(row = 4, column = 1)

        self.plot_button = Tkinter.Button(self.master, text = "Funktion plotten", command = self.plot)
        self.plot_button.grid(row = 0, column = 4)

        self.hinzufueg_button = Tkinter.Button(self.master, text = "Zum Plot hinzufügen", command = self.hinzufuegen)
        self.hinzufueg_button.grid(row = 3, column = 4)

        self.tangenten_button = Tkinter.Button (self.master, text = "Tangente einzeichnen im Punkt:", command = self.tangente)
        self.tangenten_button.grid(row = 5, column = 4)

        self.entry_tangentenpunkt = Tkinter.Entry(self.master, width = 7)
        self.entry_tangentenpunkt.grid(row = 6, column = 4)

        fig = plt.figure()
        self.canvas = FigureCanvasTkAgg(fig, master=self.master)
        self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.master)
        self.canvas.get_tk_widget().grid(row=5, column=1)
        self.toolbar.grid(row=7, column=1)



    def formel_analysieren(self, genauigkeit, startwert, endwert):
            self.x = np.arange(float(startwert),
                               float(endwert),
                               float(genauigkeit))

            x = self.x
            formula_raw = self.entry_funktion.get().replace('e^x', 'exp(x)')
            formula_raw_exp = formula_raw.replace('e^', 'exp')
            formula_list = re.split('(\W)', formula_raw_exp)
            formula_replace = [REPLACE_DIC.get(item,item) for item in formula_list]
            formula_finish = ''.join(formula_replace)
            form = parser.expr(formula_finish).compile()
            try:
                self.y = eval(form)
                self.legend = self.entry_funktion.get()
            except NameError:
                self.y = np.sin(self.x)
                self.legend = 'sin(x)'
            
            return (self.x,self.y,self.legend)
        
    
        

    def plot(self):
            self.formel_analysieren(self.get_startwert(),
                                    self.get_endwert(),
                                    self.get_genauigkeit())
            plt.clf()
            plt.plot(self.x,self.y, label=self.legend)
            plt.grid('on')
            plt.xlabel('x - Achse')
            plt.ylabel('y - Achse')
            legend = plt.legend()
            legend.draggable(state=True)
            plt.gcf().canvas.draw()

    def hinzufuegen(self):
            self.formel_analysieren()
            plt.plot(self.x,self.y, label=self.legend)
            legend = plt.legend()
            legend.draggable(state=True)
            plt.gcf().canvas.draw()

    

        
    def tangente(self):
        self.formel_analysieren(0.0005,
                                float(self.entry_tangentenpunkt.get())-0.0001,
                                float(self.entry_tangentenpunkt.get())+0.0001)
        plt.plot(self.x,self.y,'o')
        np.set_printoptions(precision=3)
        plt.text(self.x, self.y, [float(np.round(self.x, decimals=3)),
                                  float(np.round(self.y, decimals=3))])
        self.ableitung(self.entry_tangentenpunkt.get())
        plt.plot([self.x+1,self.x-1],[self.y+self.slope,self.y-self.slope])
        plt.gcf().canvas.draw()
        
        
    def ableitung(self, val):
        x = sp.Symbol('x')
        formula = self.formula_finish
        form = self.formula_finish.replace('np.', '')
        sympy_exp = parse_expr(form)
        df = sympy_exp.diff(x)
        self.slope = df.evalf(subs={x:val})

    def get_genauigkeit(self):
        if self.entry_genauigkeit.get() is None:
            return 0.01
        else:
            return self.entry_genauigkeit.get()

    def get_startwert(self):
        if self.entry_startwert.get() is None:
            return -5
        else:
            return self.entry_startwert.get()

    def get_endwert(self):
        if self.entry_endwert.get() is None:
            return 5
        else:
            return self.entry_endwert.get()
                               
    
        

    


            
def main():
     
     root = Tkinter.Tk()
     app = App(root)
     root.mainloop()

if __name__ == '__main__':
    main()
FASTER! HARDER! LOUDER!
BlackJack

@Jaga: Da in der Aufgabenstellung etwas von Softwareentwicklung stand ist ein Fehler wohl, dass Du anscheinend kein Versionskontrollsystem wie Git, Subversion, oder Mercurial verwendest und deshalb jetzt nicht ganz einfach die Änderung finden kannst ab der es nicht mehr funktioniert.

Nimm doch manuell die Änderungen heraus, also so dass es nur ein einfacher Funktionenplotter ist, und bau dann nach und nach die Funktionalität wieder ein. Und nach jedem Schritt *testen*. Dann siehst Du welche Veränderung etwas kaputt macht. Am besten mit Versionskontrollsystem.

Ansonsten hast Du im Laufe der Zeit schon eine Menge Hinweise bekommen was man besser machen kann. Von so Kleinigkeiten, die gar nicht so klein sind wie Du vielleicht denkst, wie der Formatierung (konsistente Einrücktiefe, Leerzeilen und -zeichen, maximale Zeilenlänge), bis zu Namensgebungen (`App`), und strukturelle wie das Trennen von GUI und Programmlogik. Ebenfalls angesprochen wurde schon das `eval()` böse ist, weil der Benutzer damit nahezu beliebigen Code in Kontext des Programms ausführen kann. Deshalb sollte man zumindest über die optionalen Argumente von `eval()` mitgeben auf welche Funktionen der Benutzer direkten Zugriff hat und die eingebauten Funktionen explizit ausschliessen. Damit kann man sich dann auch die Ersetzungsaktion der ganzen Numpy-Funktionsnamen in der Formelzeichenkette ersparen, weil man die Numpy-Funktionen unter Namen ohne den ``np.``-Präfix zur Verfügung stellen kann.

Das man das `parser`-Modul nicht braucht weil man das gleiche mit der eingebauten `compile()`-Funktion erreichen kann, habe ich auch schon mal geschrieben.

Was ist der Sinn der `initUI()`-Methode? Das hätte man auch alles in die `__init__()` schreiben können. Das gehört die Initialisierung eines Objekts und die zwei Zeilen von dort reichen IMHO nicht als Begründung die Methode aufzuspalten.

Nachdem eine `__init__()`-Methode abgearbeitet ist sollte auch alle Attribute auf dem Objekt definiert sein. Wenn man in beliebigen Methoden neue Attribute einführt ist das unübersichtlich und Fehleranfällig. Insbesondere in GUI-Klassen wo man die Reihenfolge der Aufrufe dem Benutzer des Programms überlässt, kann es so schnell passieren, dass der mal eine andere Reihenfolge wählt, als man sich das vorgestellt hat. An der Stelle sehe ich auch ein kleines Bedienungsproblem. Man kann eine Formel eingeben, die zeichnen lassen, dann eine andere Formel eingeben, die *nicht* zeichnen lassen, aber eine Tangente für die neue Formel in die Zeichnung der alten Formel einzeichnen lassen. Das ist verwirrend. Die Aufgabenstellung spricht von einem Fenster für die Formeleingabe. Da steckt wahrscheinlich die Idee dahinter, dass man den Benutzer dort eine Formel eingeben lässt, sie auch gleich auf so etwas wie `NameError` bei der Auswertung prüfen lässt, und bei einer neuen Formel mit einer leeren Zeichnung beziehungsweise einer Zeichnung mit nur dieser Formel anfängt. Das bei einer fehlerhaften Formel einfach kommentarlos eine Sinusfunktion angenommen wird, finde ich von der Bedienung her auch recht fragwürdig.

Neben den über verschiedene Methoden verstreuten Einführungen von neuen Attributen wird auch zuviel an das Objekt gebunden. Wenn man auf Attribute nie wieder zugreift, braucht man sie auch nicht einführen. Und es werden Werte an das Objekt gebunden die sicher nicht zum Zustand der Anwendung gehören, wie beispielsweise die Steigung der zuletzt eingezeichneten Tangente. Wenn man solche Werte nicht mehr benötigt, dann sind das keine Attribute sondern Argumente und Rückgabewerte. An der Stelle würde wohl einiges klarer sein wenn man die Programmlogik von der GUI-Klasse trennt. Denn einiges davon sind einfach nur Funktionen und keine Methoden. Andererseits kann es sein, dass die Komplexität der Programmlogik am Ende sogar ausreicht um eigene Klassen zu schreiben, zum Beispiel für eine Formel und die Operationen darauf.

Beim Auswerten der Formel sollte man nicht nur `NameError` behandeln. Mindestens `SyntaxError` ist noch eine offensichtliche Möglichkeit. Und als letztes sollte man *alle* Ausnahmen behandeln, in dem man dem Benutzer eine Fehlermeldung anzeigt. Das erscheint mir deutlich sinnvoller als einfach eine andere Funktion anzunehmen als der Benutzer eingeben wollte.

Wie soll sich das Programm eigentlich verhalten wenn man die Grenzwerte der X-Achse ändert? Eigentlich müssten dann doch alle bis dahin eingezeichneten Funktionen und Tangenten mit den geänderten Werten neu berechnet und gezeichnet werden.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@Jaga: Ergänzend zu BlackJack: Bitte poste hier doch nicht ständig Deinen komplextten Quellcode. Die Code-Tags sind für *kleine*, *überschaubare* Quellcodes gedacht; meine Richtlinie sind maximal 20 Zeilen. (Es mag Ausnahmen geben, aber sicher sind Deine gefühlt über 100 Zeilen zu lang!).

So langen Quellcode lagere doch bitte in ein Pastebin aus; etwa das im Forum eingebaut (in der Menüleiste unter dem Logo dieser Seite) oder auf gist.github.com.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten