Re: elaborazione parallela Python da JMP JSLNota che l'editor dei contenuti ha eliminato un ritorno a capo dal contenuto di defs.py e avrebbe dovuto essere visualizzato come:
def f(x):
return x*x
Elaborazione parallela Python da JMP JSLDurante le vacanze, ho avuto il tempo di armeggiare con Python. Il problema che abbiamo riscontrato con l'elaborazione parallela dall'interfaccia Python/JSL è che Python in genere avvia più copie di JMP, non Python. Il motivo è che incorporiamo Python in JMP, quindi l'eseguibile è JMP invece di Python. Ho scoperto che il pacchetto multiprocessing consente di impostare l'eseguibile per l'avvio durante l'esecuzione del codice parallelo. Questo è il pezzo mancante del puzzle.
Lo script seguente l'ho testato su JMP 15.2.1 sia su Mac che su Windows con Python 3.9. Sulla mia macchina Windows ho dovuto usare l'argomento Path() su Python Init(). Ho più versioni di Python e voglio assicurarmi che venga scelta quella corretta. Sul mio Mac, Python Init() senza argomenti funziona bene per la mia configurazione. C'è un problema che ho riscontrato su Windows, ho dovuto codificare il percorso del mio pythonw.exe. Dovrai cambiarlo in modo che corrisponda alla tua posizione Python. Cerca FixMe:
Oltre ai parametri Python Init() e al percorso di pythonw.exe su Windows, questo dovrebbe essere eseguito senza problemi su JMP 15.2.1 e versioni successive su Mac o Windows. Può essere eseguito su versioni precedenti di JMP che supportano Python, ma non è stato testato.
Dovrai creare un file defs.py nella stessa directory di questo script. Il pacchetto multiprocessing richiede che il codice per l'esecuzione in parallelo provenga da codice importabile, non locale allo script. Quindi la maggior parte del lavoro in questo script è stato assicurarsi che sys.path sia impostato correttamente in modo che il file defs.py possa essere semplicemente importato dallo script.
L'intero contenuto del file defs.py è nei commenti del file, il che semplifica l'incollaggio in un nuovo file. Quel contenuto duplicato di seguito è:
def f(x):
return x*x
Lo script prende una lista di numeri [1,2,3] e in parallelo chiama f(x) per quadrare il valore che dà i risultati [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)
]\");
Integrazione di terze partiSviluppo di applicazioni analiticheUtilitàRi: Elaborazione parallela Python da JMP JSLCiao @Paul_Nelson , come è possibile ottenere lo stesso risultato (esecuzione di uno script o di una funzione Python in background) in JMP 18, utilizzando Python incorporato?
Grazie!
Matteo
Ri: Elaborazione parallela Python da JMP JSLParallel.py utilizzando lo stesso defs.py di cui sopra, è molto più semplice con l'integrazione di JMP 18. JMP conosce la posizione dell'eseguibile Python e viene fornito come "costante" nel pacchetto di importazione jmp e ora JMP sa come aggiungere la directory dello script al percorso di ricerca del pacchetto in modo che i file locali possano essere importati.
# 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)Ri: Elaborazione parallela Python da JMP JSLGrazie per la risposta rapida. Il tuo codice funziona bene così com'è, ma ho difficoltà a estenderlo al mio caso di interesse. Sto cercando di eseguire il metodo di esempio cmdstanpy (bayesiano MCMC) da JMP. Usandolo nella forma base, funziona bene, ma apre un terminale cmd.exe per catena MCMC e li mantiene aperti fino al termine dell'algoritmo (che può richiedere molto tempo), non consentendo alcuna interazione né visualizzando alcun registro fino al termine fine.
Questo è il codice base:
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)
Voglio lasciarlo andare in background e visualizzare le informazioni di registro mentre l'algoritmo viene eseguito. Ho provato questo adattamento dal tuo suggerimento:
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))Apre 4 finestre cmd.exe ma poi non finisce mai e inoltre non cede ancora il controllo al programma principale mentre le 4 finestre sono aperte. Qualche ulteriore suggerimento?
Grazie
Matteo
Ri: Elaborazione parallela Python da JMP JSLNon ho familiarità con il pacchetto, ma mi chiedo se i parametri show_progress=True o show_console=True potrebbero dare un problema. Mi assicurerei anche che nel codice in esecuzione in parallelo, ti assicuri che abbia gestori di eccezioni, che escono in caso di errore. Può darsi che le finestre siano ancora aperte che non ritorni. Prova ad aggiungere un'uscita alla fine dello script parallelo (non quello all'interno di JMP).
Poiché JMP carica la libreria condivisa Python nel suo spazio di processo, quando Python è in esecuzione ha il controllo sul ciclo degli eventi anziché su JMP. È possibile restituire periodicamente il controllo a JMP utilizzando jmp.run_jsl('Wait(0);') ad esempio all'interno di un ciclo. Ciò restituisce brevemente il controllo a JMP per eseguire il ciclo di eventi JMP. Potresti essere in grado di sperimentarlo anche nel codice di esecuzione JMP, non sei sicuro di dove, dal momento che il tuo non è un loop ma un lancio parallelo e il codice parallelo avviato non ha alcun accesso alla funzionalità di importazione jmp. Gioco con il tuo bit di codice per vedere se qualcosa risalta.
Ri: Elaborazione parallela Python da JMP JSLTrovo che iniziare in modo piccolo e semplice sia il modo migliore per eseguire il debug di qualcosa. Prima di provare a "eseguire" passando al multi-threading, "camminare" eseguendo cmdstanpy da JMP.
Ho provato quanto segue
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())
i risultati
/*:
//:*/
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 "", riga 8, in File "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", riga 212, in __init__ model_info = self.src_info() ^^^^^ ^^^^^^^^^^ File "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", riga 318, nella compilazione del ritorno src_info .src_info(str(self.stan_file), self._compiler_options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python /site-packages/cmdstanpy/compilation.py", riga 354, in src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)] ^^^^^^^^^^^ ^^^ File "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py", riga 170, in cmdstan_path raise ValueError( ValueError: No CmdStan installazione trovata, esegui il comando "install_cmdstan" o (ri)attiva il tuo ambiente conda!
Comprendi cosa anche questo esempio chiede a JMP di fare. CmdStan è un programma a riga di comando e il pacchetto cmdstanpy è un wrapper Python. Quindi sembra che faccia qualcosa di simile a un sottoprocesso Python, per chiamare il sistema operativo, avviare il programma da riga di comando e monitorare l'output della shell. JMP e i pacchetti installati dal wrapper jpip JMP finiscono nella directory privata dei pacchetti del sito JMP. Non stiamo eseguendo un init Conda, poiché questa non è una distribuzione Conda e i pacchetti install_cmdstan potrebbero o meno configurare l'ambiente in modo sufficiente per l'esecuzione nell'ambiente virtuale JMP. In genere l'attivazione di Conda o venv imposta le variabili di ambiente per i percorsi dei pacchetti e dei percorsi delle directory. Probabilmente install_cmdstan fa la stessa cosa. Tuttavia, tutti presuppongono che lo stesso ambiente shell appena configurato verrà utilizzato per eseguire l'eseguibile Python che importerà cmdstan. Questi presupposti non sono validi, JMP deve impostare autonomamente tali percorsi per poter isolare l'ambiente del pacchetto JMP da altre istanze Python sul sistema. Pertanto, mentre JMP può importare ed eseguire il pacchetto cmdstanpy, il pacchetto stesso cerca l'eseguibile della riga di comando cmdstan e non lo localizza. Quindi provare ad aggiungere il multiprocessing significa in effetti chiedere a Python di JMP di avviare copie jmp.PYTHON_EXE che eseguono uno script Python che a sua volta tenta di avviare un sottoprocesso per eseguire il programma da riga di comando che esegue l'attività...
I miei esempi originali presentavano il caso d'uso della creazione di una funzione Python pura che verrebbe eseguita in parallelo avviando più istanze Python. Ciò che stai tentando di eseguire aggiunge la complicazione del wrapper Python su un eseguibile della riga di comando e problemi con gli ambienti shell nidificati.
Non penso che il caso d'uso che stai tentando sia supportabile nella forma attuale. Prima di provare in JMP dovresti provare lo stesso multiprocessing al di fuori di JMP. Se riesci a farlo funzionare lì, potresti avere successo con JMP. (basta sostituire jmp.PYTHON_EXE con il percorso completo di python.exe e non importare jmp).
Conosco uno script parallelo di lavori base di Python puro. Credo che anche i pacchetti Python importati che non dipendono dalle importazioni di librerie condivise dovrebbero funzionare negli script paralleli... (ma non l'ho testato). Potrebbe essere necessario passare il sito site.USER_BASE dell'ambiente JMP al parallelo jmp.PYTHON_EXE e impostare correttamente il suo ambiente in modo che corrisponda allo stesso ambiente di directory impostato da JMP stesso.
Se mentre esplori, pubblica le tue scoperte nella community.
JMP è limitato dal fatto che Python è in esecuzione sul ciclo di eventi principale di JMP. Una delle possibili soluzioni per JMP e Python multi-threading è il lancio di subinterpreti Python indipendenti. Ciò richiede Python 3.13 se riescono a risolvere tutti i problemi derivanti dalla rimozione del Global Interpreter Lock (GIL) di Python. Sto effettuando ricerche in merito, ma Python 3.13 non sarà in JMP 18. JMP 18 rimarrà con Python 3.11.x per tutto il suo ciclo di vita. A seconda delle tempistiche e della stabilità di Python 3.13, è possibile che tale funzionalità possa farsi strada in JMP 19 o 20.
Ri: Elaborazione parallela Python da JMP JSLGrazie per le spiegazioni Studierò ulteriormente e tornerò in caso di nuovi risultati.
matteo