Skript hängt an willkürlicher Stelle! Threadproblem?

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.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Hallo!

Ich möchte Lösungen für die stationäre Schrödingergleichung finden per Iteration. Während der Iteration soll die entstehende Kurve auf ein TKinter.Canvas ge"malt" werden. Dafür hab ich die Iteration und den "Mal-Befehl" in einen eigenen Thread gepackt, sodass diese Dinge von der Tkinter mainloop unabhängig laufen. Mit diesem Verfahrensprinzip hatte ich auch schonmal Erfolg bei einem anderen Problem, wo ich auch Punkte während einer Rechnung/Iteration auf ein Canvas gezeichnet habe.

Mein jetziges Skript hängt sich samt IDLE (ich arbeite unter Windows) aber zu einem willkürlichen Zeitpunkt auf, bzw. ich habe nichtmal die Möglichkeit, den Punkt genau zu erkennen. Der Zeitpunkt ist aber immer weniger als eine Sekunde nach Programmstart. Ich habe soviele Log-Ausgaben in eine Ausgabe-Datei gemacht, wie ich konnte und nur anhand dieser stelle ich fest, dass das Skript mal weiter und mal weniger weit kommt in der Iteration. Das IDLE Fenster und mein Tkinter Fenster sind dann "eingefroren". Fehlermeldungen kommen nicht in der Shell (zumindest nicht, bevor das Shellfenster einfriert).

Aber: Ohne den extra Thread hängt sich die Iteration in genau derselben Form (also die gleichen Funktionen FindPsi(),NumerovInt(),NumerovUpdate(),k(),x(),V() ) NICHT auf!

Mir fällt nichts anderes ein, als Euch erstmal den gesamten Quelltext zu zeigen. Habe ihn mal hier ausgelagert:

http://nopaste.ch/e61060a444eb25d.html

Kurz zur Erläuterung:
In Zeile 24/25 wird der Rechen/Zeichnen-Thread definiert und gestartet:

Code: Alles auswählen

 calcPsiThread = CalcThread(sgn=1, E=0.7, dE=0.03, dEabort=1e-013)
 calcPsiThread.start()
Innerhalb des Threads calcPsiThread wird (s. Zeile 63) dann mit

Code: Alles auswählen

E = FindPsi(self.sgn, self.E, self.dE, self.dEabort)
die Iteration gestartet.

In Zeile 116

Code: Alles auswählen

      if n%20 == 0: #window.Point((n*breite/N),newPsi,2)
soll dann während der Iteration eigentlich jeder 20. Punkt gezeichnet werden. Ich habs hier auskommentiert; trotzdem gleiches Verhalten des Programms. Am Zugriff auf die Globale "window" liegts also schonmal nicht.

Überhaupt interagiert durch dieses Auskommentieren der Thread calcPsiThread gar nicht mehr mit dem Hauptprogramm. Wo könnte nur der Fehler liegen?

Ich wünschte ich könnte mein Problem ein bisschen besser durchschauen oder eingrenzen...
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Ich habe mir Dein Programm nicht angeschaut. Tkinter-Scripts sollte man aber nicht in IDLE ausführen, da diese selbst ein Tkinter-Programm ist und das zu unvorhersehbarem Verhalten führen kann. Verhält sich Dein Script denn auch so, wenn Du es aus der Konsole startest?
MfG
HWK
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Ja, tut es. Aber es hängt sich erst ein kleines bisschen später auf!
EDIT: es verhält sich nicht exakt gleich. Das Fenster friert nicht ein! Aber die Berechnung bricht trotzdem irgendwann ab. Laut Log-Datei mit Timestamp auch innerhalb der ersten Sekunde. Aber es wird trotzdem mehr geschafft, als wenn ichs aus IDLE starte.
BlackJack

Und auf die GUI darf man nur von dem Thread aus zugreifen, in dem auch die `mainloop()` läuft!
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

BlackJack:

Ich sehe bei meiner Methode gar nicht die Möglichkeit dazu! Übersehe ich etwas? Ich habe ja keine Interaktion des Nutzers mit der GUI.

Wenn die `mainloop()` erstmal gestartet wird und läuft, dann hängt das Programm bzw der Hauptthread, in meiner naiven Sprache zumindest, in dieser "Zeile" fest und verarbeitet Befehle, die an die GUI kommen. (So hab ich das bisher verstanden).

Nehmen wir mal an, ich will in diesem Zustand meine Methode `Point(self, x, y, dotsize)` aufrufen, um einen Punkt auf mein Canvas zu malen. Von wo aus soll ich bzw. der Iterationsalgorithmus (der muss dann doch auf jeden Fall in einem anderen Thread laufen?!) denn diese Methode aufrufen?
BlackJack

Man kann wohl mittels Tkinter-Variablen (StringVar, IntegerVar, …) threadsafe kommunizieren. (Da bin ich mir jetzt aber nicht 100% sicher)

Man kann mit der `after()`-Methode auf den Widgets regelmässig Code ausführen lassen, zum Beispiel um über eine `Queue.Queue` mit den Threads zu kommunizieren. Die Threads packen Ergebnisse in die Queue und man schaut mit `after()` regelmässig nach ob's was zu tun gibt.

Und man kann seine eigene Mainloop schreiben. Muss man natürlich aufpassen, dass man regelmässig die GUI aktualisiert, damit die nicht scheinbar "einfriert" wenn nur gerechnet wird und gerade nichts anzuzeigen ist.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

hm... also in diesem Thread gings vor ein paar Monaten um ein sehr sehr ähnliches Problem. http://www.python-forum.de/topic-8622.html

bei effbot.org hab ich folgende verschachtelung gefunden:

Code: Alles auswählen

class App:
    def __init__(self, master):
        self.master = master
        self.poll() # start polling

    def poll(self):
        ... do something ...
        self.master.after(100, self.poll)
koennte ich statt "... do something ... " meine FindPsi(self.sgn, self.E, self.dE, self.dEabort) - Methode aufrufen? Hab ich richtig verstanden, dass die `after` Methode niemals zum einfrieren der GUI führt? Selbst, wenn ich mit ihr etwas extrem aufwändiges, wie meine Iteration, aufrufe?
BlackJack

Die führt zum Einfrieren wenn sie lange braucht. Wie der Name andeutet ist die zum Abfragen gedacht, ob etwas zu tun ist, also Ergebnisse vorliegen die gezeichnet werden müssen.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Guten Morgen!

Ich hab mich jetzt bisschen von Euch irritieren lassen.. :-) Ich schrieb ja folgendes:
Überhaupt interagiert durch dieses Auskommentieren der Thread calcPsiThread gar nicht mehr mit dem Hauptprogramm. Wo könnte nur der Fehler liegen?
Also: So, wie das Skript im Moment gestrickt ist, kommt es auch zu dem Fehler, ohne, dass ein Thread etwas mit dem anderen zu tun hat. Daher bin ich jetzt wieder total hilflos :/ Wie kann es dann zu so einem willkürlichen Hänger kommen?

Ich sehe ein, dass das vielleicht ne ganze Menge Quelltext ist und keiner Lust hat, sich da reinzudenken. Mit meinen Hinweisen aus dem ersten Posting gelingt das aber, hoffe ich, schon relativ zügig..
BlackJack

Also bei mir läuft das Programm. Ich habe zwar keine Ahnung was es tut, aber es logt fleissig vor sich hin. Darum ein paar allgemeine Anmerkungen:

Punkt eins: Style Guide. Du machst es ja nicht einmal konsistent "falsch", sondern mischt munter alle möglichen Stile. :-)

``global`` ist böse! Nicht benutzen! Auf Modulebene ist es völlig sinnlos und in den meisten Funktionen brauchst Du es auch gar nicht! Man braucht es nur wenn man in einer Funktion einem Namen auf Modulebene etwas zuweisen möchte. Und das sollte man nicht wollen, weil es den Datenfluss im Programm undurchsichtiger macht. Zum Beispiel wird in `canvasWindow.__init__()` ein lokales `breite` benutzt und nicht das globale. Das sind Sachen die man sich immer überlegen muss, wenn man ``global`` im Programm hat ─ wo kommt der Name hier jetzt eigentlich gerade her und wo wird er verändert.

Überflüssig ist zum Beispiel ``global`` für `breite` in `main()` oder `h` in `x()` und `NumerovUpdate()`.

`canvasWindow.onQuit()` ist in der Form überflüssig, man könnte gleich `sys.exit` als Funktion für 'WM_DELETE_WINDOW' angeben.

Bei `canvasWindow.Point()` wird das `dotsize`-Argument nicht benutzt.

`CalcThread` ist ein wenig kompliziert um eine Funktion zu starten. Das geht auch mit dem `target`-Argument von `threading.Thread()`.

Ansonsten könnte es der Übersichtlichkeit helfen, wenn die ``global``\s verschwinden und die Berechnung von der GUI abgekoppelt wird.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Danke fuer die Tipps, BlackJack.

Es loggt bei Dir FLEISSIG vor sich hin? Heißt das, dass es ca 100 Zeilen loggt und dann nicht mehr (das ist das Problem) oder treibt es wirklich mit hoher Geschwindigkeit die log.txt in die MegaBytes?

Wie gesagt. In der Form, wie es da jetzt steht, hängt das GUI nicht mehr. Aber der Rechenthread hängt trotzdem nach sehr kurzer Zeit und nix wird mehr gelogged. Könntest Du das bei Dir nochmal angucken und mir das Verhalten beschreiben? Das wär lieb :-)

Den Styleguide werde ich mir zu Herzen nehmen. Hatte ich sogar schonmal gelesen. Ich hab bei dem Skript nur sehr viel rumgeschustert und hin und her gecopy/pasted und bei sowas mach ich das "schöne" immer erst hinterher. Ist blöd, ich weiß.

Zu den Globals: Die hätte ich auch gerne vermieden. Nur habe ich nach einer Möglichkeit gesucht, im Datei"kopf" einfach Werte / Parameter für das Skript einzustellen. Übersichtlich ganz oben in der Datei. Sowas wie define in php oder c++ gibts in python nicht oder? Wie kann man das in Python lösen ohne dafür extra eine Funktion zu schreiben?
BlackJack

Es loggt wirklich fleissig Textzeilen. Ich habe sie mal nummerieren lassen: ``python test.py | nl`` und nach ca. 5 Sekunden war es bei Logzeile 67779.

Werte und Parameter über globale Namen ist kein Problem, die schreibt man in Grossbuchstaben, damit man erkennen kann, dass es sich um Konstanten handelt. Die Konvention gibt's ja in vielen Programmiersprachen. ``global`` braucht man nur, wenn man so einem Namen innerhalb einer Funktion einen neuen Wert zuweisen möchte. Also Konstanten ändern ─ das hört sich ja schon falsch an.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Okay.. ich räume den Quelltext jetzt ein bisschen auf. Hm... auf die Idee, dass ich ohne irgendein DEFINE oder so für Konstanten im Skript auskomme, bin ich nicht gekommen. ;)

Nebenbei..:
Warum geht

Code: Alles auswählen

def Point(self, x, y, dotsize=self.dotsize):
nicht? Es kommt "NameError: name 'self' is not defined". Kann man allgemein in einer Methode einer Klasse keine Standardparameter auf diese Weise festlegen?




Und..:
Woran kann es liegen, dass das Skript bei Dir ewig logged und bei mir der Rechenthread offenbar hängen bleibt? Ich hab den Python 2.5.1 Interpreter unter Windows laufen.

edit: OUAAAH:
Also. Bei mir läuft es jetzt auch. Meine Dateiendung des Skripts war .pyw . Ich weiß nicht genau, was das unter Windows dann bedeutet. Ich hatte es gemacht, weil dann das Konsole-Fenster nicht auftaucht. Mit .py und dem normalen Konsolefenster läuft alles glatt. Kann das jetzt jemand erklären? :)


edit2: Also:
Wenn ihr mal selbst Energieeigenwerte zur stationären Schrödingergleichung für das Potential V=|x| per Numerov - Iteration bestimmen wollt:
http://paste.pocoo.org/show/8714/
Jetzt läuft es :-) Die Grafikausgabe funktioniert auch nach meinen vorstellungen. Und das Ergebnis war sogar noch ein Stück genauer als das von Mathematica.. ich freu mich :)

Was kann ich an dem Skript noch verbessern? Bezogen auf Style / Aufbau und vielleicht auch Ausführungsgeschwindigkeit? Kommentieren werde ich noch vernünftig. Aber ich hätte gern noch ein bisschen Kritik..
BlackJack

Die default-Werte werden ausgewertet wenn die ``def``-Anweisung ausgeführt wird und nicht beim Aufruf. Zu dem Zeitpunkt gibt es noch keine Instanz die an `self` gebunden ist. Üblicherweise benutzt man `None` und ein Überprüfung innerhalb der Funktion oder Methode:

Code: Alles auswählen

def point(self, x, y, dotsize=None):
    if dotsize is None:
        dotsize = self.dotsize
Mit `*.pyw` wird das Programm als "Windowsprogramm" gestartet d.h. da sind die Standardein und -ausgabe nicht mit einem Terminal verbunden das Ausgaben mit ``print`` entgegennimmt und darstellt. Wenn man etwas ausgibt blockiert das Programm solange bis die Daten abgenommen wurden. Ohne Terminal passiert das nie.

Zum Quelltext:

Die Einrückung ist jetzt wenigstens konsequent "falsch". ;-)

Ein paar Zeilen sind länger als 80 Zeichen.

Bitte nochmal die Wirkungsweise von ``global`` nachlesen. Auf Modulebene hat das absolut keinen Effekt. Wäre schön, wenn der Compiler da zumindest eine Warnung ausgeben würde. Und ``global`` ist auch immer noch "böse". In `NumerovUpdate()` ist das ``global`` überflüssig weil `PSI` nichts zugewiesen wird. Gleiches gilt für `window` in `NumerovInt()`.

Falls das Argument `logtime` bei `logit` als Schalter gedacht ist, wären `True` und `False` als mögliche Werte selbsterklärender.

Da Funktionen auch nur Objekte wie jedes andere sind, kann man `V` ganz einfach so definieren: ``V = abs``. Denn im Grunde ist `V` ja einfach nur die `abs`-Funktion unter einem anderen Namen.

Insgesamt würde ich immer noch Vorschlagen die globalen Namen loszuwerden bzw. nicht direkt darauf zuzugreifen, sondern sie den Funtkionen als Argumente mitzugeben. Und die Berechnung ist immer noch an die GUI gekoppelt.

Psyco bringt bei diesem Programm was. Ohne dauerte es bei mir ca. 5½ Minuten, mit nur 3½.
BlackJack

Nachtrag: Ich habe `NumerovUpdate()` und was damit zusammenhängt mal zusammengefasst und etwas umgeschrieben:

Code: Alles auswählen

def f(a, n, E):
    return (H * H * (E - abs(n - a) * H)) / 6.0;


def NumerovUpdate(E):
  n = len(PSI)
  newPsi = ((2 * PSI[-1] * (1 - 5 * f(1.5, n, E))
             - PSI[-2] * (f(2.5, n, E) + 1))
            / (f(0.5, n, E) + 1))
  PSI.append(newPsi)
  return newPsi
`k()`, `x()` und `V()` sind ersatzlos gestrichen. Dann habe ich die GUI "ausgebaut" und zwei Testläufe gemacht. Ohne Psyco: 1 Minute und 6 Sekunden, mit Psyco nur 15 Sekunden Laufzeit.

Die GUI bremst das Program gewaltig aus, weil bei jedem neuen Punkt alle alten erneut gezeichnet werden. Vektorgrafik halt. Je mehr Punkte um so langsamer wird's.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Nehmen wir mal diese beiden Funktionen aus dem Skript:

Code: Alles auswählen

def NumerovUpdate(E, PSI):
  n = len(PSI)
  newPsi = (2*(1-5.0/12*H*H*k(n-1,E))*PSI[n-1]-1*(1+H*H/12*k(n-2,E))*PSI[n-2])/(1+H*H/12*k(n,E))
  return newPsi

  
def NumerovInt(drawWin, sgn, E):
  if sgn == 1:
    PSI = [0, 1]
  else:
    PSI = [-H, H]
  for n in xrange(int(N)):
    newPsi = NumerovUpdate(E, PSI)
    PSI.append(newPsi)
    #if abs(newPsi) < PSICUT:
      #if n%2000 == 0:
        #drawWin.Point((n*BREITE/N),newPsi,2)
    if abs(newPsi) > PSIMAX:
      logit ("... abgebrochen (PSIMAX ueberschritten)")
      PSI[:] = [] # leere die Liste
      if newPsi > 0: return 1
      else: return -1
Jetzt komme ich ohne `global` aus. Aber ist es jetzt nicht so, dass in beiden Funktionen je eine Kopie der LANGEN Liste `PSI`existiert? Oder ist das in Python nicht merh so zu trennen... also call by reference, call by value. Call by reference würde hier nur Sinn machen. Macht Python das in dem Fall automatisch?

edit: @ blackjack: oh, jetzt erst deinen letzten post gelesen.. ich gucks mir mal an, moment :)
Zuletzt geändert von gurke111 am Samstag 3. November 2007, 19:31, insgesamt 1-mal geändert.
BlackJack

Es wird keine Kopie gemacht. Es werden *immer* Referenzen auf Objekte übergeben.

Die Unterscheidung "by reference" und "by value" macht nur Sinn wenn es wirklich beide Arten in einer Sprache gibt und wenn es "Referenz" als Datentyp gibt. In einem Java-Buch von Sun steht zum Beispiel Java macht immer "Call by Value" wobei die Werte bei nicht-primitiven Typen eben Referenzen auf Objekte sind.
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Danke fuer Deine Muehe!

Wegen der Funktionen x, V und k: Naja.. in der physikalischen Problemstellung ist V(x) das Potential. Wenn die Funktion getrennt definiert ist, ist das Skript schneller an ein anderes Problem anzupassen. Genauso ist `k` ein feststehender Ausdruck der "Numerov"-Iteration für die Schrödingergleichung. Bringt Deine Aufteilung einen Geschwindigkeitsvorteil? Wenn nicht, dann nimm es mir nicht übel, wenn ich es so stehen lasse. Trotzdem nochmal danke :)
Es wird keine Kopie gemacht. Es werden *immer* Referenzen auf Objekte übergeben.
Ah, gut zu wissen. Liegt wohl in der Philisophie von Python, ja?
Edit: Aber was ist denn nun, wenn ich eine Variable an eine Funktion übergebe (welches ja, wie ich nun weiß, per Referenz geschieht) und diese dann in der neuen Funktion ändere? Dann sollte ja in der übergebenden Funktion diese Variable nicht mitgeändert werden. Wird in dem Moment dann doch eine Kopie (die dann ja keine echte Kopie mehr ist) angelegt?
Dann habe ich die GUI "ausgebaut" und zwei Testläufe gemacht. Ohne Psyco: 1 Minute und 6 Sekunden, mit Psyco nur 15 Sekunden Laufzeit.
Ich möchte ja eine graphische Ausgabe haben. Auch während der Iteration. Ist einfach anschaulich :) Aber Du hast völlig recht. Die GUI bremst das Skript sehr aus. Ohne einen Punkt zu zeichnen (durch Auskommentieren von `window.Point`) braucht die Iteration bei mir 73s (also Deine Größenordnung ohne Psyco). Wenn ich jeden 2000. Punkt zeichne reicht das für die Anschauung noch aus und die Zeit geht hoch auf 87s. Ich denke, dass das für mich ein Kompromiss ist.

Dieses Psyco schau ich mir bei Gelegenheit gerne mal an. Hört sich ja magisch an. Hab eben mal bisschen gelesen. 10x - 100x Speedup bei rechenintesiven Algorithmen versprechen die, ja? Wow. Problem: Dieses Skript muss auf dem Uni-System laufen. Die haben, denke ich, nur Standardbibliothek. Dann kann ich es gleich vergessen oder?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gurke111 hat geschrieben:Edit: Aber was ist denn nun, wenn ich eine Variable an eine Funktion übergebe (welches ja, wie ich nun weiß, per Referenz geschieht) und diese dann in der neuen Funktion ändere? Dann sollte ja in der übergebenden Funktion diese Variable nicht mitgeändert werden. Wird in dem Moment dann doch eine Kopie (die dann ja keine echte Kopie mehr ist) angelegt?
Es werden nie Kopien implizit angelegt. Du kannst Kopien mit dem Modul `copy` anfertigen.

Es sieht so aus: du hast zwei Typen von Objekten: mutable (änderbare) und immutable (nicht-änderbare). Wenn du einer Funktion ein immutables Objekt übergibst, dann *kann* sie das Objekt nicht verändern. Wenn es ein mutables Objekt ist, kann sie dieses verändern. Beispiel:

Code: Alles auswählen

vier = 3
drei = [1, 2]

def fix_content(drei, vier):
    vier = 4
    drei.append(3)
    drei = [4, 5, 6]

fix_content(drei, vier)
print vier
print drei
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gurke111
User
Beiträge: 28
Registriert: Freitag 26. Oktober 2007, 22:55

Okay, Ausgabe:
3
[1, 2, 3]

ich sehe:
`vier` wird nicht verändert, `drei` schon.

`drei` wurde aber auf zwei verschiedene Weisen versucht zu ändern:

Code: Alles auswählen

    drei.append(3)
    drei = [4, 5, 6]
Die erste der beiden Zeilen hat offenbar Wirkung gehabt "nach außen", die zweite nicht. Dann hängt es also auch noch von der Art der Veränderung ab, ob ein Objekt mutable oder immutable ist?
Antworten