Betreff: Python-Parallelverarbeitung von JMP JSLBeachten Sie, dass der Inhaltseditor einen Wagenrücklauf aus dem Inhalt von defs.py entfernt hat und wie folgt hätte angezeigt werden sollen:
def f(x):
return x*x
Python-Parallelverarbeitung von JMP JSLÜber die Feiertage hatte ich Zeit, an Python zu basteln. Das Problem, auf das wir bei der parallelen Verarbeitung von der Python/JSL-Schnittstelle gestoßen sind, ist, dass Python normalerweise mehrere Kopien von JMP startet, nicht Python. Der Grund dafür ist, dass wir Python in JMP einbetten, sodass die ausführbare Datei JMP anstelle von Python ist. Ich habe entdeckt, dass das Multiprocessing-Paket es ermöglicht, die ausführbare Datei so einzustellen, dass sie beim Ausführen des parallelen Codes gestartet wird. Dies ist das fehlende Puzzleteil.
Das folgende Skript habe ich auf JMP 15.2.1 auf Mac und Windows mit Python 3.9 getestet. Auf meinem Windows-Rechner musste ich das Path()-Argument für Python Init() verwenden. Ich habe mehrere Python-Versionen und möchte sicherstellen, dass die richtige ausgewählt wird. Auf meinem Mac funktioniert Python Init() ohne Argumente für meine Konfiguration gut. Es gibt ein Problem, auf das ich unter Windows gestoßen bin, ich musste den Pfad zu meiner pythonw.exe hartcodieren. Sie müssen dies ändern, damit es Ihrem Python-Standort entspricht. Suche nach FixMe:
Abgesehen von den Python-Init()-Parametern und dem Pfad zu pythonw.exe unter Windows sollte dies problemlos unter JMP 15.2.1 und neuer auf Mac oder Windows ausgeführt werden. Kann auf älteren Versionen von JMP ausgeführt werden, die Python-Unterstützung bieten, dies wurde jedoch nicht getestet.
Sie müssen eine defs.py-Datei im selben Verzeichnis wie dieses Skript erstellen. Das Multiprocessing-Paket erfordert, dass der Code, der parallel ausgeführt werden soll, aus importierbarem Code stammt, nicht aus lokalem Code. Die meiste Arbeit in diesem Skript bestand also darin, sicherzustellen, dass sys.path richtig eingerichtet ist, damit die Datei defs.py einfach vom Skript importiert werden kann.
Der gesamte Inhalt der Datei defs.py befindet sich in den Dateikommentaren, was das Einfügen in eine neue Datei erleichtert. Der unten duplizierte Inhalt ist:
def f(x):
return x*x
Das Skript nimmt eine Liste von Zahlen [1,2,3] und ruft parallel f(x) auf, um den Wert zu quadrieren, der die Ergebnisse liefert [1,4,9].
pyParallel.jsl
Names Default To Here( 1 );
/* Description: Python mulltiprocessing from JMP JSL
* File: pyParallel.jsl, and defs.py
* Author: Paul Nelson
* JMP Statistical Discovery, LLC
*
* Python multiprocessing from an embedded context adds additional challenges.
* Just attempting to run Python parallel processing modules, typically launches
* multiple copies of JMP, not executing the Python code in parallel.
*
* The multiprocessing module allows setting the executable to launch, since we
* are loading Python into JMP, it's an embedded case thus sys.executable => JMP, not Python.
*
*
* Note: Functions to be run in parallel must come from importable code, not local
* code definitions.
* A multiprocessing 'Feature', not bug, See: https://bugs.python.org/issue25053
*
* Known Bugs:
* On Windows within JMP, sys.exec_prefix is empty, so path to pythonw.exe has to be set explicitly
*
* Create a defs.py file with the contents below. Place in the same directory as this JSL file.
def f(x):
return x*x
*/
// On my windows machine since I have multiple versions of Python, I have to specify the
// full path to the python39.dll, on my Mac it just works.
If( Host Is("Windows"),
Python Init(Path("C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\python39.dll")),
Python Init()
);
// send the current working directory where we loaded this script, down to Python so we can find defs.py
script_path = Get Default Directory();
Python Send(script_path);
Python Submit("\[
import sys
import os
import multiprocessing
from multiprocessing import Pool
import platform
# The loaded Python does not have the current working directory initialized, so change it
# to the directory of the script, as sent in from JSL
print('script_path: ' + script_path)
hostPlatform = platform.system()
print(hostPlatform)
try:
if 'Windows' == hostPlatform:
# strip leading / off from path if it exists
if '/' == script_path[0]:
script_path = script_path[1:]
norm_path = os.path.normpath(script_path)
print(norm_path)
os.chdir(norm_path)
else:
os.chdir(script_path)
except:
print("Unable to change current working directory", sys.exc_info())
finally:
print(os.getcwd())
# if '.' is not at the beginning of sys.path, prepend so we can look for modules within CWD
if sys.path[0] != '.':
sys.path.insert(0, '.')
print( sys.path )
# here argv is [''] empty string, doesn't seem to bother us in this use case.
#print( 'argv: ', sys.argv )
# prints the path to JMP, not Python
print('sys.executable: ' + sys.executable)
# build up path prefix to python3 exectable
if 'Darwin' == hostPlatform:
pyPath = os.path.join(sys.exec_prefix, 'bin', 'python3')
elif 'Windows' == hostPlatform:
# Bug - on windows sys.exec_prefix is empty in Python within JMP!
pyPath = None
if sys.exec_prefix:
pyPath = os.path.join(sys.exec_prefix, 'pythonw.exe')
else:
# FixMe: hardcode for now
pyPath = 'C:\\Users\\panels\\AppData\\Local\\Programs\\Python\\Python39\\pythonw.exe'
print ('Python Executable: ' + pyPath)
# Finally down to running something in parallel.
#
# Import the function code that will be executed in parallel.
# JMP is not reporting ImportError or ModuleNotFound errors, the scripting just returns -1
# on an import failure, so wrap it within our own try: except: block
try:
import defs as d
# print( d.f(2) )
# Tell multiprocessing to use the Python executable, not JMP to spawn the workers.
multiprocessing.set_executable(pyPath)
# Run the tasks in parallel - (separate Python instances)
with Pool(5) as p:
print(p.map(d.f, [1,2,3])) # Results in [1,4,9] the square of the numbers
except ModuleNotFoundError as mnf:
print(mnf)
print('Check that the moudule is located on your sys.path:')
print( sys.path )
except ImportError as error:
print(error)
except Exception as ex:
print(ex)
]\");
Integration von DrittanbieternAnalytische AnwendungsentwicklungDienstprogrammeBetreff: Python-Parallelverarbeitung von JMP JSLHallo @Paul_Nelson , wie kann das gleiche Ergebnis (Ausführen eines Python-Skripts oder einer Python-Funktion im Hintergrund) in JMP 18 mit dem eingebetteten Python erzielt werden?
Danke!
Matteo
Betreff: Python-Parallelverarbeitung von JMP JSLParallel.py mit derselben defs.py wie oben ist mit der Integration von JMP 18 viel einfacher zu erledigen. JMP kennt den Speicherort der ausführbaren Python-Datei und wird als „Konstante“ im JMP- Importpaket bereitgestellt. JMP weiß nun, dass das Skriptverzeichnis zum Paketsuchpfad hinzugefügt werden muss, damit lokale Dateien importiert werden können.
# test_multiprocessing.py
# Author: Paul R. Nelson
# JMP Statistical Discovery LLC
#
# Description: Demonstrate using Python's parallel processing capability.
# The file defs.py contains the code that will execute in parallel.
#
# Contents of defs.py:
#def f(x):
# return x*x
#
import jmp
import multiprocessing
from multiprocessing import Pool
# jmp.PYTHON_EXE is the file path to JMP's Python executable.
print(jmp.PYTHON_EXE)
try:
# defs.py is the code to be run in parallel, located in same directory as this file.
import defs as d
# tell multiprocessing to use Python executable, not spawn JMP for the workers
multiprocessing.set_executable(jmp.PYTHON_EXE)
# square each number in parallel - separate Python instances
with Pool(5) as p:
print(p.map(d.f, [1,2,3])) # result is the square of the numbers
except ModuleNotFoundError as mnf:
print(mnf)
print('Check that the module is located on your sys.path:')
print( sys.path )
except ImportError as error:
print(error)
except Exception as ex:
print(ex)Betreff: Python-Parallelverarbeitung von JMP JSLDanke für die schnelle Antwort. Ihr Code funktioniert so wie er ist einwandfrei, aber ich habe Probleme, ihn auf den Fall meines Interesses zu erweitern. Ich versuche, die cmdstanpy-Beispielmethode (Bayesian MCMC) von JMP aus auszuführen. Wenn man es einfach in seiner Grundform verwendet, funktioniert es gut, aber es öffnet ein cmd.exe-Terminal pro MCMC-Kette und hält sie offen, bis der Algorithmus fertig ist (was ziemlich lange dauern kann), und erlaubt keine Interaktion oder zeigt bis dahin kein Protokoll an das Ende.
Dies ist der Basiscode:
import cmdstanpy as cs
model = cs.CmdStanModel(stan_file=stan_file)
fit = model.sample(data=datafile, show_progress=True, show_console=True)
fit_table = fit.draws_pd(vars=genVariables+modelParams)
Ich möchte es im Hintergrund laufen lassen und Protokollinformationen anzeigen, während der Algorithmus ausgeführt wird. Ich habe diese Anpassung anhand Ihres Vorschlags ausprobiert:
def sample_chain(chain_id):
return model.sample(data=datafile, chains=1, parallel_chains=1, show_progress=True, show_console=True)
try:
multiprocessing.set_executable(jmp.PYTHON_EXE)
with multiprocessing.Pool(4) as pool:
results = pool.map(sample_chain, range(4))Es öffnet zwar 4 cmd.exe-Fenster, wird dann aber nie beendet und darüber hinaus gibt es immer noch nicht die Kontrolle an das Hauptprogramm zurück, während die 4 Fenster geöffnet sind. Irgendwelche weiteren Vorschläge?
Danke
Matteo
Betreff: Python-Parallelverarbeitung von JMP JSLIch kenne das Paket nicht, aber ich frage mich, ob die Parameter show_progress=True oder show_console=True ein Problem verursachen könnten. Ich würde auch sicherstellen, dass der parallel ausgeführte Code über Ausnahmehandler verfügt, die bei einem Fehler beendet werden. Es kann daran liegen, dass die Fenster noch geöffnet sind, sodass es nicht zurückkehrt. Versuchen Sie, am Ende des Parallelskripts einen Exit hinzuzufügen (nicht den in JMP).
Da JMP die gemeinsam genutzte Python-Bibliothek in seinen Prozessraum lädt, hat Python bei der Ausführung die Kontrolle über die Ereignisschleife und nicht über JMP. Es ist möglich, die Kontrolle regelmäßig an JMP zurückzugeben, indem jmp.run_jsl('Wait(0);') verwendet wird, beispielsweise innerhalb einer Schleife. Dadurch wird die Kontrolle kurzzeitig an JMP zurückgegeben, um die JMP-Ereignisschleife auszuführen. Möglicherweise können Sie damit auch in Ihrem JMP-Ausführungscode experimentieren, wissen nicht genau, wo, da es sich nicht um eine Schleife, sondern um einen parallelen Start handelt und der gestartete parallele Code keinen Zugriff auf die Import-JMP-Funktionalität hat. Ich spiele mit Ihrem Codebit, um zu sehen, ob etwas auffällt.
Betreff: Python-Parallelverarbeitung von JMP JSLIch finde, klein und einfach anzufangen ist der beste Weg, etwas zu debuggen. Bevor Sie versuchen, zu „rennen“, indem Sie in Multithreading springen, „gehen“, indem Sie „cmdstanpy“ in JMP ausführen.
Ich habe Folgendes versucht
import os
from cmdstanpy import CmdStanModel
stan_file = os.path.join('/Users/_me_/Desktop', 'bernoulli.stan')
print(stan_file)
model = CmdStanModel(stan_file=stan_file)
print(model)
print(model.exe_info())
die Ergebnisse
/*:
//:*/
import os
from cmdstanpy import CmdStanModel
stan_file = os.path.join('/Users/_me_/Desktop', 'bernoulli.stan')
print(stan_file)
model = CmdStanModel(stan_file=stan_file)
print(model)
print(model.exe_info())
/*:
/Users/_me_/Desktop/bernoulli.stan
19:01:04 - cmdstanpy - INFO - No CmdStan installation found.
19:01:04 - cmdstanpy - INFO - Cannot determine whether version is before 2.27.
19:01:04 - cmdstanpy - INFO - No CmdStan installation found.
19:01:04 - cmdstanpy - INFO - Cannot determine whether version is before 2.27.
Traceback (most recent call last):
File "", Zeile 8, in Datei "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", Zeile 212, in __init__ model_info = self.src_info() ^^^^^^^^^^^^^^^^^ Datei "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", Zeile 318, in src_info return compilation.src_info(str(self.stan_file), self._compiler_options) ^ ... "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/compilation.py", Zeile 354, in src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)] ^^^^^^^^^^^^^^^ Datei "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py", Zeile 170, in cmdstan_path raise ValueError( ValueError: Keine CmdStan-Installation gefunden, führen Sie den Befehl "install_cmdstan" aus oder aktivieren Sie Ihre Conda-Umgebung (erneut)!
Verstehen Sie, was selbst dieses Beispiel von JMP verlangt. CmdStan ist ein Befehlszeilenprogramm und das cmdstanpy-Paket ist ein Python-Wrapper. Es scheint also, dass es so etwas wie einen Python-Unterprozess ausführt, um das Betriebssystem aufzurufen, das Befehlszeilenprogramm zu starten und die Shell-Ausgabe zu überwachen. JMP und die vom JMP-JPIP-Wrapper installierten Pakete landen im privaten Site-Packages-Verzeichnis von JMP. Wir führen keine Conda-Initialisierung aus, da es sich nicht um eine Conda-Distribution handelt und die install_cmdstan-Pakete die Umgebung möglicherweise ausreichend konfigurieren, um in der virtuellen JMP-Umgebung ausgeführt zu werden. Normalerweise richtet die Conda- oder Venv-Aktivierung Umgebungsvariablen für Paketpfade und Verzeichnispfade ein. Wahrscheinlich macht install_cmdstan dasselbe. Allerdings gehen alle davon aus, dass dieselbe Shell-Umgebung, die gerade konfiguriert wurde, zum Ausführen der ausführbaren Python-Datei verwendet wird, die cmdstan importieren wird. Diese Annahmen gelten nicht. JMP muss diese Pfade selbst einrichten, um die JMP-Paketumgebung von anderen Python-Instanzen auf Ihrem System isolieren zu können. Während JMP also das cmdstanpy-Paket importieren und ausführen kann, sucht das Paket selbst nach der ausführbaren cmdstan-Befehlszeilendatei und findet diese nicht. Wenn man dann versucht, das Multiprocessing hinzuzufügen, fordert man praktisch JMPs Python auf, jmp zu starten. PYTHON_EXE kopiert ein Python-Skript, das dann wiederum versucht, einen Unterprozess zu starten, um das Befehlszeilenprogramm auszuführen, das die Aufgabe ausführt ...
Meine ursprünglichen Beispiele stellten den Anwendungsfall der Erstellung einer reinen Python-Funktion dar, die durch den Start mehrerer Python-Instanzen parallel ausgeführt werden würde. Was Sie ausführen möchten, führt zu Komplikationen durch den Python-Wrapper in einer ausführbaren Befehlszeilendatei und zu Problemen mit verschachtelten Shell-Umgebungen.
Ich glaube nicht, dass der Anwendungsfall, den Sie versuchen, in der aktuellen Form unterstützt werden kann. Bevor Sie es in JMP versuchen, sollten Sie das gleiche Multiprocessing außerhalb von JMP ausprobieren. Wenn Sie es dort zum Laufen bringen, können Sie innerhalb von JMP Erfolg haben. (Ersetzen Sie einfach jmp.PYTHON_EXE durch den vollständigen Pfad zu python.exe und importieren Sie jmp nicht.)
Ich weiß, dass ein paralleles Skript mit reinem Python funktioniert. Ich glaube, dass sogar importierte Python-Pakete, die nicht von gemeinsam genutzten Bibliotheksimporten abhängen, in den parallelen Skripten funktionieren sollten ... (aber das habe ich nicht getestet). Möglicherweise muss die site.USER_BASE der JMP-Umgebung an die parallele jmp.PYTHON_EXE weitergegeben und deren Umgebung so eingestellt werden, dass sie mit derselben Verzeichnisumgebung übereinstimmt, die JMP selbst einstellt.
Teilen Sie Ihre Entdeckungen bei Ihren Erkundungen bitte der Community mit.
Die Einschränkung von JMP besteht darin, dass Python in der Hauptereignisschleife von JMP ausgeführt wird. Eine der möglichen Lösungen für JMP und Multithreading-Python ist die Einführung unabhängiger Python-Subinterpreter. Das erfordert Python 3.13, wenn alle Probleme durch die Entfernung von Pythons Global Interpreter Lock (GIL) gelöst werden. Ich untersuche das, aber Python 3.13 wird nicht in JMP 18 enthalten sein. JMP 18 wird während seines gesamten Lebenszyklus bei Python 3.11.x bleiben. Abhängig vom Zeitplan und der Stabilität von Python 3.13 ist es möglich, dass diese Funktion in JMP 19 oder 20 Einzug hält.
Betreff: Python-Parallelverarbeitung von JMP JSLDanke für die Erklärungen. Ich werde weiter studieren und bei neuen Erkenntnissen zurückkommen.
Matteo