Probleme mit Modulen

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
Buddy12345
User
Beiträge: 2
Registriert: Freitag 24. Januar 2014, 10:26

Liebe Forum Mitglieder,

ich arbeite derzeit mit Enthought Canopy zur Animierung einfachster numerischer Lösungen von Differentialgleichungen.

Dabei nimmt im Programmiertext der ganze Kram zur Animation viel mehr Raum ein als die eigentliche Lösungsalgorithmus. Aus diesem Grunde will ich dies in ein Modul schreiben.

Es ist mir bereits gelungen sämtliche die Animation betreffenden Teile in eine Funktion zu schreiben. Quelltext folgend:

Code: Alles auswählen

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math as mat

def ANI2D(X,Y,P1):   #P1 ist d
    def simData():
        i=0
        while i < t1/P1:
            xa = X[i] 
            ya = Y[i]
            i = i+100
            yield xa, ya
 
    def simPoints(simData):
        x = simData[0] 
        y = simData[1]
        line1.set_data(x, y)
        return line1
 
    fig = plt.figure()
    ax = fig.add_subplot(111)
    line1, = ax.plot([], [], 'ro')
    ax.set_ylim(min(x), max(x))
    ax.set_xlim(min(T), max(T))

    ani = animation.FuncAnimation(fig, simPoints, simData, blit=False,
        interval=1, repeat=True)
    #ani.save('HarmonischerOszillator.mp4', fps=15)
    return (plt.show())
    


x = [1]
u = 1
v = 0
D = 1
g = 0
m = 1
a = - D/m *u - g/m *v
t0 = 0
t1 = 5
d = 0.001
T = [0]
t = 0

while t <= t1:
    u = u + v*d
    v = v + a*d
    a = - D/m *u - g/m *v
    x = x + [u]
    t = t + d
    T = T + [t]

ANI2D(T,x,d) 

Bitte keine Verbesserungsvorschläge für das Verfahren als solches!

Wie gesagt, es funktioniert einwandfrei. Wenn ich nun aber die Definition der Funktion in ein extra Modul schreibe Animation2d.py.

Code: Alles auswählen

############### Animation2d.py
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math as mat

def ANI2D(X,Y,P1):   #P1 ist d
    def simData():
        i=0
        while i < t1/P1:
            xa = X[i] 
            ya = Y[i]
            i = i+100
            yield xa, ya
 
    def simPoints(simData):
        x = simData[0] 
        y = simData[1]
        line1.set_data(x, y)
        return line1
 
    fig = plt.figure()
    ax = fig.add_subplot(111)
    line1, = ax.plot([], [], 'ro')
    ax.set_ylim(min(x), max(x))
    ax.set_xlim(min(T), max(T))

    ani = animation.FuncAnimation(fig, simPoints, simData, blit=False,
        interval=1, repeat=True)
    #ani.save('HarmonischerOszillator.mp4', fps=15)
    return (plt.show())
Und dann dieses Modul versuche im Original einzubinden:

Code: Alles auswählen

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math as mat
import Animation2d as A2d

x = [1]
u = 1
v = 0
D = 1
g = 0
m = 1
a = - D/m *u - g/m *v
t0 = 0
t1 = 5
d = 0.001
T = [0]
t = 0

while t <= t1:
    u = u + v*d
    v = v + a*d
    a = - D/m *u - g/m *v
    x = x + [u]
    t = t + d
    T = T + [t]

A2d.ANI2D(T,x,d) 
dann öffnet sich bei Canopy immer das Fehlerfenster und es wird mir ein "Unexpected error UnicodeEncodeError:" mitgeteilt.

Ich habe bereits "einfach" Module erfolgreich anwenden können, aber immer wenn ich in den Modulen selbst Bibliotheken lade bzw. Funktionen aus anderen Bibliotheken verwende kommt dieser Fehler.

Kann mir vielleicht jemand weiterhelfen?

Vielen Dank!

Buddy
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Zu deinem eigentlichen Problem kann ich spontan nichts sagen, aber du hast noch eine Unschönheit im Programm der aus einer unguten Benennung von Bezeichnern herrührt. In ANI2D verwendest du min(x) und max(x). Damit übernimmst du die Werte von x aus dem übergeordneten Namensraum, da als Funktionsparameter in ANI2D nicht x (als Kleinbuchstabe) sondern X (also als Großbuchstabe) verwendet wird.
Buddy12345
User
Beiträge: 2
Registriert: Freitag 24. Januar 2014, 10:26

Schon Mal vielen Dank!

Leider behebt das, wie ja erwartet, das eigentliche Problem nicht...

Hat vielleicht jemand eine Idee woran es liegen kann?
BlackJack

@Buddy12345: `t1` und `T` sind im neuen Modul auch nicht definiert.

Interessant wäre wo diese Fehlermeldung her kommt. Du könntest das mal ausserhalb der IDE (Canopy?) starten. Vielleicht ist das ja auch ein Bug in der IDE/Umgebung die Du verwendest und Du kannst da nicht wirklich viel gegen machen.

Das Du keine Anmerkungen zum Verfahren selbst haben möchtest schliesst hoffentlich keine Anmerkungen zur Umsetzung aus. Du bekommst jedenfalls jetzt welche. ;-)

Grundsätzlich sei hier mal der Style Guide for Python Code erwähnt was die Konventionen bei der Namensgeben angeht. Allerdings ist solch „mathelastiger” Code IMHO eine Ausnahme, weil diese leserfeindlichen kryptischen Kürzel da ja leider normal sind.

Trotzdem sollte man wo es geht auf gute Namen achten. Das bedeutet zum Beispiel das `math`-Modul nicht unter dem Namen `mat` zu importieren. Das ist ein Buchstabe den man sich da im Quelltext spart gegenüber einem Namen bei dem die Gefahr besteht ihn mit einer Abkürzung für „Matrix” zu verwechseln. Die Module `numpy` und `math` werden ausserdem gar nicht verwendet, müssen also auch nicht importiert werden. Auch mixedCase vs. words_with_underscores wird von der Domäne nicht vorgegeben. Sinnfreies nummerieren von Namen wie bei `line1` sollte man auch sein lassen.

Code auf Modulebene sollte nur Konstanten, Funktionen, und Klassen definieren, alles andere sollte in einer oder mehrere Funktionen verschwinden. Das ist sauberer, man kann die Funktionen einzeln interaktiv testen, und es fällt sofort auf wenn man in einer Funktion versucht etwas zu verwenden was nicht als Argument übergeben wurde. Man wird also gezwungen sich an der Stelle Gedanken darüber zu machen was Konstanten sind und was Variablen. Dann passiert so etwas mit `t1`, `x`, und `T` in der `ANI2D()`-Funktion nicht mehr ohne das es auffällt.

Die `sim_data()`-Funktion lässt sich deutlich vereinfachen. Aus der ``while``-Schleife lässt sich eine ``for``-Schleife machen und die temporären Namen `xa` und `ya` sind nicht nötig:

Code: Alles auswählen

    def sim_data():
        for i in xrange(0, t1 / P1, 100):
            yield X[i], Y[i]
Mit `islice()` und `izip()` aus dem `itertools`-Modul bekommt man diese Funktionalität sogar in einen einzigen Ausdruck. Und `P1` und `t1` werden in der `ANI2D()`-Funktion überhaupt nicht mehr gebraucht.

`sim_points()` ist auch etwas umsändlich geschrieben. Man hätte `x` und `y` mit *einer* Anweisung entpacken können: ``x, y = simData``, oder die `set_data()`-Methode gleich mit dem Argument und einem ``*`` aufrufen können.

Beim ``return`` von ``ANI2D()`` ist ein paar Klammern zu viel.

Im Hauptprogramm in der ``while``-Schleife verwendest Du zweimal das Muster ``S = S + [x]`` um einen Wert an eine Liste anzuhängen. Zum einen passiert da mehr als nötig ist, denn das hängt nicht einen Wert an eine vorhandene Liste an, sondern erzeugt eine neue Liste und kopiert `S` und die einelementige literale Liste in diese neue Liste, was ineffizienter ist als tatsächlich nur ein Element an die vorhandene Liste mit `append()` anzuhängen. Zum anderen kann man diese Listenoperation durch den ``+``-Operator nicht so gut von den Rechenoperationen davor unterscheiden, als wenn dort ein Methodenaufruf stünde.

Bei beiden Listen könnte man mit einer leeren Liste beginnen statt den initialen Wert von `t` und `u` noch einmal beim initialisieren der Listen zu wiederholen wenn man am Anfang der Schleife die Werte anhängt.

Ähnlich ungünstig wirkt sich auch die Platzierung der Berechnung von `a` innerhalb der Schleife aus. Hier muss vor der Schleife nicht nur ein literaler Wert wiederholt werden, sondern eine ganze Formel. Wenn man die Berechnung von `a` in der Schleife weiter nach vorne zieht, kann man sich dagegen die Zuweisung vor der Schleife komplett sparen.

`x` würde ich `X` nennen um Einzelwerte von Sequenzen besser unterscheiden zu können. Und man könnte die Zuweisungen vor der Schleife so ordnen dass erst die angegeben werden, die innerhalb der Schleife konstant bleiben und dann die, die innerhalb der Schleife neu berechnet werden.

In der Schleife machst Du einen „Fehler”, nämlich das wiederholte aufaddieren von Gleitkommazahlen was ungenau ist und wo sich der Fehler bei jedem Schleifendurchlauf mit aufaddieren kann. Das liesse sich bei `T`/`t` einfach vermeiden in dem man das aktuelle `t` nicht durch addieren bestimmt, sondern durch Multiplikation der Schrittnummer mit `d`.

Ich lande dann bei so etwas hier:

Code: Alles auswählen

from __future__ import division
from itertools import islice, izip
import matplotlib.animation as animation
import matplotlib.pyplot as plt


def ANI2D(X, Y):

    def sim_points(point):
        line.set_data(*point)
        return line
 
    fig = plt.figure()
    ax = fig.add_subplot(111)
    line = ax.plot([], [], 'ro')[0]
    ax.set_ylim(min(Y), max(Y))
    ax.set_xlim(min(X), max(X))
    ani = animation.FuncAnimation(
        fig,
        sim_points,
        lambda: islice(izip(X, Y), 0, None, 100),
        blit=False,
        interval=1,
        repeat=True
    )
    #ani.save('HarmonischerOszillator.mp4', fps=15)
    return plt.show()


def main():
    D = 1
    g = 0
    m = 1
    t1 = 5
    d = 0.001
    T = list()
    X = list()
     
    u = 1
    v = 0
    for i in xrange(int(t1 / d)):
        T.append(i * d)
        X.append(u)
        a = - D / m * u - g / m * v
        u = u + v * d
        v = v + a * d
     
    ANI2D(T, X)


if __name__ == '__main__':
    main()
Und als nächstes könnte man dann mal überlegen `numpy` wieder zu importieren und tatsächlich mal zu benutzen. Zum Beispiel kann man `T` mit einem einzigen `numpy.arange()`-Aufruf erstellen.
Antworten