Parametervariation/Parallele Prozesse

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
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

Hey Leute,

suche fortgeschrittenen Rat. ;-)

Folgendes Problem:
Ich habe zur Aufgabe ein Python-Skript zu entwickeln, welches selbstständig eine Parametervariation, iVm. einem anderen Simulationsprogramm durchführt.
Das Simulationsprogramm hat als Grundlage eine Textfile mit den dazugehörigen Parametern, welche ich mittels Python manipulieren kann.
Ändern der Parameter und simulieren stellt kein Problem dar.

Wo das Problem liegt ist wie es zu realisieren ist eine bestimmte Anzahl von Simulationen gleichzeitig auszuführen.
Mein Gedanke dazu ist, dass ich dazu eine unbestimmte Anzahl an Textfiles erzeuge mit jeweils einer durchnummerierten anderen Endung. Mit den ersten Textfiles stellt die Simulation auch kein Problem dar.
Nun Enden die Simulationen in unterschiedlichen Zeitabständen.
Die Aufgabe sollte nach der Beendigung der ersten Simulation sein aus dem Parametersatz den nächsten Parameter auswählen und diesen in die Textfile der gerade beendeten Simulation einzusetzen. Nur leider habe ich kein schimmer wie soetwas nur Ansatzweise zu realisieren ist.
Erster Gedanke war, dass es über das Modul threading und queue geht. Weiß aber nicht wie es zu realisieren ist.

Code: Alles auswählen


#Anzahl der Simulationen = Anzahl der Textfiles
#---------------------------------------------------------------------------------------------------

for File_Number in xrange(0, Anzahl_paralleler_Prozesse+1, 1):
    newfilename=TRNSYS_Name+"_"+str(File_Number)+".dck"
    shutil.copyfile(TRNSYS_Name+".dck",newfilename)
    print "Umbenennen von", TRNSYS_Name, "in", newfilename, "..."

#---------------------------------------------------------------------------------------------------
#Parameterlisten
#---------------------------------------------------------------------------------------------------

Liste_Parameter=[
    "Parameter1",
    "Parameter2"
    ]
Liste_Parameter_Werte=[
    [15,20,25],
    [5,6]
    ]
#Parameter setzen - Unabhaengig wieviele Parameter verwendet werden
#---------------------------------------------------------------------------------------------------
    
for a in range(0,len(Liste_Parameter),1): 
    for wert in Liste_Parameter_Werte[a]: #wert = Wert, der eingesetzt wird # Liste_Parameter[a] = Name des Parameters

Hoffentlich versteht ihr, was das Problem ist :oops: :roll:
Zuletzt geändert von Anonymous am Mittwoch 18. September 2013, 17:34, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Mattversuchts: das Beispiel ganz unten in der Dokumentation von Queue hilft Dir nicht weiter? Wie sieht dann Dein Versuch aus?

Ich würde die Config-Dateien nicht am Anfang kopieren sondern vor jedem Aufruf neu erstellen. Die Namen der Parameter und ihre möglichen Werte sollten in einer Liste stehen [("Parameter1",[24,15,16]),("Parameter2", [5,6]), …], dann brauchst Du auch keine for-Schleife über den Index in die Liste.
BlackJack

@Mattversuchts: Also so ganz wird mir das nicht klar was Du mit den Eingabedateien da veranstalten willst. Das fängt schon damit an das Du eine Datei mehr erzeugst als `Anzahl_paralleler_Prozesse` als Wert enthält, der Kommentar aber behauptet die Anzahl der Simulationen entspricht der Anzahl der Textdateien. Das ist verwirrend.

Dann sollte man das „anti pattern” ``for i in xrange(len(sequence)):`` nicht verwenden nur um dann mit dem Index auf Elemente der Sequenz zuzugreifen. Das ist nicht ganz das was Du machst, sondern Du greifst auf die Elemente einer *anderen* Sequenz zu als die von der die Länge genommen wird, aber da haben wir dann schon das nächste Problem: Daten die eigentlich zusammen gehören, stecken in verschiedenen ”parallelen” Datenstrukturen. Die sollten in einer Datenstruktur stehen. Zum Beispiel eine Liste mit Tupeln, welche die zusammengehörigen Daten enthalten. Wobei mir jetzt daraus auch noch nicht klar wird wie denn die Variationen von den Werten überhaupt gebildet werden sollen!?

Zur Modellierung der Parameter:

Code: Alles auswählen

parameters = [('Parameter1', [15, 20, 25]), ('Parameter2', [5, 6])]
# 
# oder:
# 
parameters = {'Parameter1': [15, 20, 25], 'Parameter2': [5, 6]}
Bezüglich der Namenskonventionen könntest Du mal einen Blick in Style Guide for Python Code werfen.

Das zusammensetzen der Dateinamen mit ``+`` und `str()` sieht eher nach BASIC als nach Python aus. In Python würde man dafür Zeichenkettenformatierung mit der `format()`-Methode auf Zeichenketten oder dem ``%``-Operator verwenden.

Wie funktioniert der Aufruf der Simulationssoftware denn? Ist das ein blockierender Aufruf? Oder wie erkennst Du ob eine Simulation komplett durchgelaufen ist?

Ich würde eine Anzahl von Threads starten, die Parametervariationen per Queue übergeben bekommen. Da muss man die Dateien dann auch nicht im vorraus erstellen, sondern erst bevor man eine konkrete Simulation startet, erzeugt man die Datei dafür. Der Dateiname kann dann pro Thread variieren, zum Beispiel durch eine fortlaufende Nummer für die Threads.

Du müsstest das Problem in kleinere Teilprobleme zerlegen. Und die Teilprobleme dann wieder zerlegen. Solange bis es Probleme sind, die man mit ein paar Zeilen Quelltext lösen kann. Dann testet man die. Und wenn sie funktionieren kann man sie als Bausteine zum lösen der komplexeren Teilprobleme verwenden. Solange bis man eine Gesamtlösung hat.
Dami123
User
Beiträge: 225
Registriert: Samstag 23. Februar 2013, 13:01

Guten Rat zum Aufbau hast du nun bekommen.
Hier ein unvollständiger Skript, welcher mittels "Queue" und "threading" Proxies testet.
Evtl. kannst du damit etwas anfangen.

Code: Alles auswählen

class ThreadProxy(threading.Thread):
    def __init__(self, queue, count, timeout, working_proxies, used_proxies):
        threading.Thread.__init__(self)
        self.queue = queue
        self.count = count
        self.timeout = timeout
        self.working_proxies = working_proxies
        self.used_proxies = used_proxies
          
    def run(self):
        while True:
            proxy = self.queue.get() 
            result = check_proxy(proxy, self.timeout)
                
            if result == True and len(self.working_proxies) < self.count and proxy not in self.working_proxies:
                self.working_proxies.append(proxy)
                self.used_proxies.append(proxy)
            else:
                self.used_proxies.append(proxy)

            self.queue.task_done()

def ProxySelection(proxylist, count, timeout, queues=None):
    """Return working proxies from a proxylist using threads.
       Default queues -> 5
    """
    timer = 25
    
    if queues == None:
        queues = 5
    queue = Queue.Queue(queues)
    working_proxies = []
    used_proxies = []

    for i in range(queues):
        t = ThreadProxy(queue, count, timeout, working_proxies, used_proxies)
        t.setDaemon(True)
        t.start()
        
        
    timer = time.time()+timer
    
    while len(working_proxies) < count:# and time.time() < timer:
        try:
            for u in used_proxies:
                try: proxylist.remove(u)
                except: pass
            for i in range(5):
                queue.put(proxylist[i])
            #queue.join()

        except:
            raise IndexError("Not enought working proxies available")


    return working_proxies, used_proxies
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Dami123:
Achherje wo hast du denn das Schnipsel her? Das ist nicht threadsafe - spätestens, wenn der caller über die used- und working-proxies-Listen iteriert während die Threads da munter reinschreiben, geht es krachen.

Hab mir das Problem des OPs nicht weiter angeschaut, klingt beim Überfliegen aber wie eine Sache für einen Worker-Threadpool oder Prozesspool (letzteres falls der GIL zum Pferdefuss wird).
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@Dami123: Die fehlende Threadsicherheit ist nicht die einzige Auffälligkeit. Da gibt es noch einen unnötigen vergleich auf ``x == True``. Denn Vergleich kannst du dir sparen, denn die Bedingung wird nur dann wahr, wenn x bereits wahr ist. Dann kannst du aber auch gleich auf x testen. Wenn du auf ``x == False`` testest, dann solltest du ``not x`` verwenden.

Auch solltest du doppelten Code zusammenfassen:

Code: Alles auswählen

if result == True and len(self.working_proxies) < self.count and proxy not in self.working_proxies:
    self.working_proxies.append(proxy)

self.used_proxies.append(proxy)
"queues" in "ProxySelection" ist ein verwirrender Name. Da stecken gar keine Warteschlangen drin, sondern die Anzahl der zu erzeugenden Warteschlagen. Dann solltest du es auch entsprechend benennen. Auch der Defaultwert ist seltsam von dir gewählt. Warum setzt du den auf None und schreibst dann im Docstring, dass der Standardwert 5 ist. Warum bindest du an queues nicht direkt die 5?

Irgendwie schwirrt da noch ein timer wert rum, welcher gar nichts macht. Hinzu kommt, dass er einfach auf einen beliebigen Wert gesetzt wird und nicht nach außen dokumentiert wurde.

Wenn du testen möchtes, ob ein Wert ``None`` ist, dann solltest du dies nicht mit ``== None`` tun, sondern mittels ``is None``.

Bei Schleifen, bei denen du den Wert verwirfst, solltest du den Wert an ``_`` binden. Damit signalisierst du, dass der Wert nicht mehr gebraucht wird. Dann weiß der Leser gleich, dass er sich darum nicht mehr kümmern muss:

Code: Alles auswählen

for _ in range(queues):
    ...
Aus Listen zu lösche, über die du gerade iterierst, ist eine ganz schlechte Idee. Dabei kann es sehr leicht passieren und wird es mit sehr großer Wahrscheinlichkeit auch, dass der Iterator in einen ungültigen Zustand gerät. Dann wird unter Umständen über nicht vorhandene Listenelemente iteriert oder es wird versucht nicht vorhandene Werte zu löschen.

Ein except ohne die konkrete Angabe eines konkreten Fehlers ist immer eine ganz schlechte Idee. Damit werden nämlich alle Ausnahmen abgefangen. Dazu gehören dann auch Fehler, wie ein NameError, welcher Programmierfehler verdeckt. Damit wird es sehr schwierig Fehler zu finden, da sich diese hinter langen Kaskaden von seltsamen Verhalten verstecken.

Code: Alles auswählen

for i in range(5):
   ... proxylist[i]
ist ein Anti-Pattern in Python. Du kannst direkt über Elemente von Listen iterieren und solltest das auch tun:

Code: Alles auswählen

for element in proxylist:
    queue.put(element)
Falls du doch mal einen Index brauchst, dann verwende dazu die enumerate-Funktion.

Das Werfen eines ``IndexError``s scheint hier nicht unbedingt angebracht, da solltest du besser eine eigene Exception werfen, welche den tatsächlichen Fehler besser beschreibt.

Die Begrenzung der Elemente in der Warteschlange scheint auch überflüssig zu sein. Denn die Anzahl der Elemente in der Warteschlange sollte nichts mit der Anzahl der Arbeiterthreads zu tun haben. Im Normalfall wird es so sein, dass deutlich mehr Elemente in der Warteschlange stecken als es Workerthreads gibt. Sonst hat man wahrscheinlich zu viele Threads.

Und dann hält sich "ProxySelection" nicht an PEP 8.
Das Leben ist wie ein Tennisball.
Dami123
User
Beiträge: 225
Registriert: Samstag 23. Februar 2013, 13:01

Danke für die sehr umfangreiche Fehlerbetrachtung :)
Wie gesagt ist der Code verbesserungswürdig und hat auch ein gewisses Alter.
Beim überfliegen vor dem posten, sind mir selber einige Fehler aufgefallen und als sie sich gestapelt haben, ist die Lust zur Korrektur versickert.
Evtl. sollte ich nichts mehr posten, was direkt an den Pranger gestellt werden kann, doch dann könnte ich nicht aus diesen gut angeführten Fehleranalysen lernen :D
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

Hey,

vielen Dank für eure Antworten. Haben mir sehr weitergeholfen.
Ich habe es jetzt so realisiert, dass ich die gewünschte Anzahl an Threads starte. Eine Thread-Laufnummer und die Parameter via Queue übergebe.
Die Dateien werden direkt bevor die Simulation gestartet wird erstellt und nach der Laufnummer benannt.
Die Simulationssoftware ist ein blockierender Aufruf.

Aktuell sitze ich noch an Schönheitsfehlern, die ich irgendwie nicht so ganz hinbekomme:

Code: Alles auswählen

Liste_Parameter=[
    ("Parameter1",[1]),
    ("Parameter2",[1,2]),
    ("Parameter3",[1,2,3]),
    ("Parameter4",[1,2,3,4]),
    ("Parameter5",[1,2,3,4,5])
    ]

Parametervariation = []
for a in Liste_Parameter[0][1]:
    for b in Liste_Parameter[1][1]:
        for c in Liste_Parameter[2][1]:
            for d in Liste_Parameter[3][1]:
                for e in Liste_Parameter[4][1]:
                    newline_a = "Parameter 1 = "+ str(a)
                    newline_b = "Parameter 2 = "+ str(b)
                    newline_c = "Parameter 3 = "+ str(c)
                    newline_d = "Parameter 4 = "+ str(d)
                    newline_e = "Parameter 5 = "+ str(e)
                    Parametervariation = [Threads_Laufnummer,newline_a, newline_b, newline_c, newline_d, newline_e]
                    Threads_Laufnummer=Threads_Laufnummer+1
                    time.sleep(1)
                    q.put(Parametervariation)
Die Parametervariation wird aktuell wie oben durchgeführt. Besser wäre natürlich, dass die Anzahl der Parameter variieren kann ohne, dass ich den kompletten Quelltext umschreiben muss.
Hätte da jmd eine zündende Idee?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Code: Alles auswählen

import itertools

PARAMETERS = [1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]

def create_configurations():
    for configuration in itertools.product(*PARAMETERS):
        q.put(configuration)
Das Leben ist wie ein Tennisball.
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

Danke für die Antwort!

Leider läuft das bei mir nicht :-(
Gibts noch eine Alternative?
BlackJack

@Mattversuchts: Was läuft nicht? Das kannst Du nicht 1:1 übernehmen, aber `itertools.product()` ist die Funktion die Du suchst.
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

Das kann sein, dass diese Funktion zur Lösung führt.
Nur leider ist es mir nicht möglich diesen Weg zu erkennen... :K

Nochmal:
Wenn ich von der Liste mit Tupeln ausgehe:

Code: Alles auswählen

Liste_Parameter=[
    ("Parameter1",[1]),
    ("Parameter2",[1,2]),
    ("Parameter3",[1,2,3]),
    ("Parameter4",[1,2,3,4]),
    ("Parameter5",[1,2,3,4,5])
    ]
Ist es das Ziel einzelne Listen an die Queue zu übergeben, die die Parameter-Zeile der Variierten Parameter enthalten:
Als Beispiel die erste und letzte Liste:
1. [Parameter1=1, Parameter2=1, Parameter3=1, Parameter4=1, Parameter5=1]
Letzte Liste der Queue: [Parameter1=1, Parameter2=2, Parameter3=3, Parameter4=4, Parameter5=5]

leider bekomme ich das mit der itertools.product nicht gebacken...
ich weiß ich sollte das selbst erarbeiten... habs aber leider nach drei tagen immernoch nicht hinbekommen :(

help?! :roll:
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Du must doch nur deine Form der Parameter

Code: Alles auswählen

[
    ("Parameter1",[1]),
    ("Parameter2",[1,2]),
    ("Parameter3",[1,2,3]),
    ("Parameter4",[1,2,3,4]),
    ("Parameter5",[1,2,3,4,5])
    ]
in eine andere transformieren

Code: Alles auswählen

[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]
und in ``itertools.product`` stecken.
Das Leben ist wie ein Tennisball.
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

@EyDu Danke für die Antwort!
Leider stimmt das so nicht. Auch mit der anderen Form der Parameter stimmt das Ergebnis nicht überein. :K
Zum selbst überprüfen habe ich mal die lauffähigen Schnipsel hier angefügt:

Code: Alles auswählen

import itertools
from Queue import Queue
q = Queue()

PARAMETERS = [1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]

def create_configurations():
    for configuration in itertools.product(PARAMETERS):
        print configuration
        q.put(configuration)
        
create_configurations()
item = q.get()
print item
([1],)
([1, 2],)
([1, 2, 3],)
([1, 2, 3, 4],)
([1, 2, 3, 4, 5],)
([1],)
und:

Code: Alles auswählen

from Queue import Queue

Liste_Parameter=[
    ("Parameter1",[1]),
    ("Parameter2",[1,2]),
    ("Parameter3",[1,2,3]),
    ("Parameter4",[1,2,3,4]),
    ("Parameter5",[1,2,3,4,5])
    ]

q = Queue()

Parametervariation = []
for a in Liste_Parameter[0][1]:
    for b in Liste_Parameter[1][1]:
        for c in Liste_Parameter[2][1]:
            for d in Liste_Parameter[3][1]:
                for e in Liste_Parameter[4][1]:
                    newline_a = str(a)
                    newline_b = str(b)
                    newline_c = str(c)
                    newline_d = str(d)
                    newline_e = str(e)
                    Parametervariation = [newline_a, newline_b, newline_c, newline_d, newline_e]
                    q.put(Parametervariation)
                    print Parametervariation
Das Ergebnis sollte so aussehen(2tes Skript):
['1', '1', '1', '1', '1']
['1', '1', '1', '1', '2']
['1', '1', '1', '1', '3']
['1', '1', '1', '1', '4']
['1', '1', '1', '1', '5']
['1', '1', '1', '2', '1']
['1', '1', '1', '2', '2']
['1', '1', '1', '2', '3']
['1', '1', '1', '2', '4']
['1', '1', '1', '2', '5']
['1', '1', '1', '3', '1']
['1', '1', '1', '3', '2']
['1', '1', '1', '3', '3']
['1', '1', '1', '3', '4']
['1', '1', '1', '3', '5']
['1', '1', '1', '4', '1']
['1', '1', '1', '4', '2']
['1', '1', '1', '4', '3']
['1', '1', '1', '4', '4']
['1', '1', '1', '4', '5']
['1', '1', '2', '1', '1']
['1', '1', '2', '1', '2']
['1', '1', '2', '1', '3']
['1', '1', '2', '1', '4']
['1', '1', '2', '1', '5']
['1', '1', '2', '2', '1']
['1', '1', '2', '2', '2']
['1', '1', '2', '2', '3']
['1', '1', '2', '2', '4']
['1', '1', '2', '2', '5']
['1', '1', '2', '3', '1']
['1', '1', '2', '3', '2']
['1', '1', '2', '3', '3']
['1', '1', '2', '3', '4']
['1', '1', '2', '3', '5']
['1', '1', '2', '4', '1']
['1', '1', '2', '4', '2']
['1', '1', '2', '4', '3']
['1', '1', '2', '4', '4']
['1', '1', '2', '4', '5']
['1', '1', '3', '1', '1']
['1', '1', '3', '1', '2']
['1', '1', '3', '1', '3']
['1', '1', '3', '1', '4']
['1', '1', '3', '1', '5']
['1', '1', '3', '2', '1']
['1', '1', '3', '2', '2']
['1', '1', '3', '2', '3']
['1', '1', '3', '2', '4']
['1', '1', '3', '2', '5']
['1', '1', '3', '3', '1']
['1', '1', '3', '3', '2']
['1', '1', '3', '3', '3']
['1', '1', '3', '3', '4']
['1', '1', '3', '3', '5']
['1', '1', '3', '4', '1']
['1', '1', '3', '4', '2']
['1', '1', '3', '4', '3']
['1', '1', '3', '4', '4']
['1', '1', '3', '4', '5']
['1', '2', '1', '1', '1']
['1', '2', '1', '1', '2']
['1', '2', '1', '1', '3']
['1', '2', '1', '1', '4']
['1', '2', '1', '1', '5']
['1', '2', '1', '2', '1']
['1', '2', '1', '2', '2']
['1', '2', '1', '2', '3']
['1', '2', '1', '2', '4']
['1', '2', '1', '2', '5']
['1', '2', '1', '3', '1']
['1', '2', '1', '3', '2']
['1', '2', '1', '3', '3']
['1', '2', '1', '3', '4']
['1', '2', '1', '3', '5']
['1', '2', '1', '4', '1']
['1', '2', '1', '4', '2']
['1', '2', '1', '4', '3']
['1', '2', '1', '4', '4']
['1', '2', '1', '4', '5']
['1', '2', '2', '1', '1']
['1', '2', '2', '1', '2']
['1', '2', '2', '1', '3']
['1', '2', '2', '1', '4']
['1', '2', '2', '1', '5']
['1', '2', '2', '2', '1']
['1', '2', '2', '2', '2']
['1', '2', '2', '2', '3']
['1', '2', '2', '2', '4']
['1', '2', '2', '2', '5']
['1', '2', '2', '3', '1']
['1', '2', '2', '3', '2']
['1', '2', '2', '3', '3']
['1', '2', '2', '3', '4']
['1', '2', '2', '3', '5']
['1', '2', '2', '4', '1']
['1', '2', '2', '4', '2']
['1', '2', '2', '4', '3']
['1', '2', '2', '4', '4']
['1', '2', '2', '4', '5']
['1', '2', '3', '1', '1']
['1', '2', '3', '1', '2']
['1', '2', '3', '1', '3']
['1', '2', '3', '1', '4']
['1', '2', '3', '1', '5']
['1', '2', '3', '2', '1']
['1', '2', '3', '2', '2']
['1', '2', '3', '2', '3']
['1', '2', '3', '2', '4']
['1', '2', '3', '2', '5']
['1', '2', '3', '3', '1']
['1', '2', '3', '3', '2']
['1', '2', '3', '3', '3']
['1', '2', '3', '3', '4']
['1', '2', '3', '3', '5']
['1', '2', '3', '4', '1']
['1', '2', '3', '4', '2']
['1', '2', '3', '4', '3']
['1', '2', '3', '4', '4']
['1', '2', '3', '4', '5']
Also ich hab das Gefühl, dass ich mit der Aufgabe entweder total auf dem Schlauch steh oder ich einfach nur falsch verstanden worden bin.

Grüße
Matt
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Manchmal kommt es eben auf ein * im Quellcode an ;-)
Das Leben ist wie ein Tennisball.
BlackJack

@Mattversuchts: Vergleiche den Aufruf von `product()` noch mal *genau* mit dem Vorschlag. Du hast da nämlich genau 1 Zeichen nicht übernommen.
Mattversuchts
User
Beiträge: 22
Registriert: Dienstag 4. September 2012, 09:34

:roll:
peinlich... :lol:

Vielen Dank!!! Funktioniert jetzt einwandfrei! :)
Antworten