Re: procesamiento paralelo de Python desde JMP JSLTenga en cuenta que el editor de contenido eliminó un retorno de carro del contenido de defs.py y debería haberse mostrado como:
def f(x):
return x*x
Procesamiento paralelo de Python desde JMP JSLDurante las vacaciones, tuve tiempo de jugar con Python. El problema con el que nos encontramos con el procesamiento paralelo desde la interfaz de Python/JSL es que Python normalmente lanza varias copias de JMP, no de Python. El motivo de esto es que incorporamos Python en JMP, por lo que el ejecutable es JMP en lugar de Python. Descubrí que el paquete de multiprocesamiento permite configurar el ejecutable para que se inicie cuando se ejecuta el código paralelo. Esta es la pieza que falta en el rompecabezas.
El siguiente script lo probé en JMP 15.2.1 tanto en Mac como en Windows con Python 3.9. En mi máquina con Windows tuve que usar el argumento Path() para Python Init(). Tengo varias versiones de Python y quiero asegurarme de que se elija la correcta. En mi Mac, Python Init() sin argumentos funciona bien para mi configuración. Hay un problema con el que me encontré en Windows, tuve que codificar la ruta a mi pythonw.exe. Deberá cambiar eso para que coincida con su ubicación de Python. Buscar FixMe:
Aparte de los parámetros Python Init() y la ruta a pythonw.exe en Windows, esto debería ejecutarse sin problemas en JMP 15.2.1 y posteriores en Mac o Windows. Puede ejecutarse en versiones anteriores de JMP que admitan Python, pero no se han probado.
Deberá crear un archivo defs.py en el mismo directorio que este script. El paquete de multiprocesamiento requiere que el código que se ejecuta en paralelo provenga de un código importable, no local del script. Por lo tanto, la mayor parte del trabajo en este script fue asegurarse de que sys.path esté configurado correctamente para que el script pueda importar el archivo defs.py.
Todo el contenido del archivo defs.py se encuentra en los comentarios del archivo, lo que facilita pegarlo en un nuevo archivo. Ese contenido duplicado a continuación es:
def f(x):
return x*x
El script toma una lista de números [1,2,3] y en paralelo llama a f(x) para elevar al cuadrado el valor que da los resultados [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)
]\");
Integración de tercerosDesarrollo de aplicaciones analíticasUtilidadesRe: Procesamiento paralelo de Python desde JMP JSLHola @Paul_Nelson , ¿cómo se puede lograr el mismo resultado (ejecutar un script o función de Python en segundo plano) en JMP 18, utilizando Python integrado?
¡gracias!
mateo
Re: Procesamiento paralelo de Python desde JMP JSLParallel.py usando el mismo defs.py que el anterior, se hace mucho más fácilmente con la integración de JMP 18. JMP conoce la ubicación del ejecutable de Python y se proporciona como una 'constante' en el paquete de importación jmp , y ahora JMP sabe agregar el directorio del script a la ruta de búsqueda del paquete para que se puedan importar archivos locales.
# 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)Re: Procesamiento paralelo de Python desde JMP JSLGracias por la rápida respuesta. Su código funciona bien tal como está, pero tengo problemas para extenderlo a mi caso de interés. Estoy intentando ejecutar el método de muestra cmdstanpy (MCMC bayesiano) desde JMP. Simplemente usándolo en forma básica, funciona bien, pero abre un terminal cmd.exe por cadena MCMC y los mantiene abiertos hasta que finaliza el algoritmo (lo que puede llevar bastante tiempo), sin permitir ninguna interacción ni mostrar ningún registro hasta el fin.
Este es el código básico:
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)
Quiero dejarlo en segundo plano y mostrar información de registro mientras se ejecuta el algoritmo. Probé esta adaptación de tu sugerencia:
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))Abre 4 ventanas de cmd.exe pero nunca termina y, además, todavía no devuelve el control al programa principal mientras las 4 ventanas están abiertas. ¿Alguna sugerencia adicional?
gracias
mateo
Re: Procesamiento paralelo de Python desde JMP JSLNo estoy familiarizado con el paquete, pero me pregunto si los parámetros show_progress=True o show_console=True podrían estar dando un problema. También me aseguraría de que en el código que se ejecuta en paralelo, se asegure de que tenga controladores de excepciones, que salgan si ocurre una falla. Puede ser que no vuelva a aparecer porque las ventanas siguen abiertas. Intente agregar una salida al final del script paralelo (no el que está dentro de JMP).
Dado que JMP carga la biblioteca compartida de Python en su espacio de proceso, cuando Python se ejecuta, tiene control sobre el bucle de eventos en lugar de JMP. Es posible devolver el control a JMP periódicamente utilizando jmp.run_jsl('Wait(0);') como dentro de un bucle. Esto devuelve brevemente el control a JMP para ejecutar el bucle de eventos de JMP. Es posible que también pueda experimentar con eso en su código de ejecución JMP, no estoy seguro de dónde exactamente, ya que no es un bucle sino un inicio paralelo y el código paralelo iniciado no tiene ningún acceso a la funcionalidad de importación jmp. Juego con tu código para ver si algo destaca.
Re: Procesamiento paralelo de Python desde JMP JSLCreo que empezar de a poco y de forma sencilla es la mejor manera de depurar algo. Antes de intentar "ejecutar" saltando a subprocesos múltiples, "camine" ejecutando cmdstanpy desde JMP.
Probé lo siguiente
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())
los resultados
/*:
//:*/
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 "", línea 8, en Archivo "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", línea 212, en __init__ model_info = self.src_info() ^^^^^ ^^^^^^^^^^ Archivo "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", línea 318, en la compilación de devolución de src_info .src_info(str(self.stan_file), self._compiler_options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Archivo "/Usuarios/_me_/Biblioteca/Soporte de aplicaciones/JMP/Python/3.11/lib/python /site-packages/cmdstanpy/compilation.py", línea 354, en src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSIÓN)] ^^^^^^^^^^^ ^^^ Archivo "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py", línea 170, en cmdstan_path aumenta ValueError( ValueError: No CmdStan Se encontró la instalación, ejecute el comando "install_cmdstan" o (re)active su entorno Conda.
Comprenda lo que incluso este ejemplo le pide a JMP que haga. CmdStan es un programa de línea de comandos y el paquete cmdstanpy es un contenedor de Python. Entonces parece que hace algo así como un subproceso de Python, para llamar al sistema operativo, iniciar el programa de línea de comando y monitorear la salida del shell. JMP y los paquetes instalados por el contenedor jpip de JMP terminan en el directorio de paquetes de sitio privado de JMP. No estamos ejecutando un inicio de Conda, ya que esta no es una distribución de Conda y los paquetes install_cmdstan pueden o no configurar el entorno lo suficiente para ejecutarse en el entorno virtual JMP. Normalmente, la activación de Conda o venv configura variables de entorno para rutas de paquetes y rutas de directorio. Probablemente install_cmdstan haga lo mismo. Sin embargo, todos ellos suponen que se utilizará el mismo entorno de shell recién configurado para ejecutar el ejecutable de Python que importará cmdstan. Esas suposiciones no se cumplen, JMP tiene que configurar esas rutas él mismo para poder aislar el entorno del paquete JMP de otras instancias de Python en su sistema. Entonces, si bien JMP puede importar y ejecutar el paquete cmdstanpy, el paquete en sí busca el ejecutable de la línea de comando cmdstan y no lo localiza. Luego, intentar agregar el multiprocesamiento es, de hecho, pedirle a Python de JMP que inicie las copias jmp.PYTHON_EXE ejecutando un script de Python que luego, a su vez, intenta iniciar un subproceso para ejecutar el programa de línea de comando que realiza la tarea...
Mis ejemplos originales presentaban el caso de uso de la creación de una función pura de Python que se ejecutaría en paralelo mediante el lanzamiento de varias instancias de Python. Lo que está intentando ejecutar agrega la complicación del contenedor de Python en un ejecutable de línea de comando y problemas con los entornos de shell anidados.
No creo que el caso de uso que está intentando sea compatible en la forma actual. Antes de probar en JMP, deberías probar el mismo multiprocesamiento fuera de JMP. Si puede hacer que funcione allí, es posible que tenga éxito en JMP. (simplemente reemplace jmp.PYTHON_EXE con la ruta completa a python.exe y no importe jmp).
Conozco un script paralelo de trabajos básicos de Python puro. Creo que incluso los paquetes de Python importados que no dependen de las importaciones de bibliotecas compartidas deberían funcionar en los scripts paralelos... (pero no lo he probado). Es posible que sea necesario pasar el sitio.USER_BASE del entorno JMP al jmp.PYTHON_EXE paralelo y configurar correctamente su entorno para que coincida con el mismo entorno de directorio que el propio JMP.
Si mientras exploras, publica tus descubrimientos en la comunidad.
JMP está limitado por el hecho de que Python se ejecuta en el bucle de eventos principal de JMP. Una de las posibles soluciones para JMP y Python multiproceso es lanzar subintérpretes de Python independientes. Eso requiere Python 3.13 si eliminan todos los problemas al eliminar el bloqueo global de intérprete (GIL) de Python. Estoy investigando eso, pero Python 3.13 no estará en JMP 18. JMP 18 permanecerá con Python 3.11.x durante todo su ciclo de vida. Dependiendo de los plazos y la estabilidad de Python 3.13, es posible que dicha capacidad llegue a JMP 19 o 20.
Re: Procesamiento paralelo de Python desde JMP JSLGracias por las explicaciones. Estudiaré más y volveré en caso de nuevos hallazgos.
mateo