Re: Traitement parallèle Python de JMP JSLNotez que l'éditeur de contenu a supprimé un retour chariot du contenu de defs.py et aurait dû s'afficher sous la forme :
def f(x):
return x*x
Traitement parallèle Python à partir de JMP JSLPendant les vacances, j'ai eu le temps de bricoler avec Python. Le problème que nous avons rencontré avec le traitement parallèle à partir de l'interface Python/JSL est que Python lance généralement plusieurs copies de JMP, pas Python. La raison en est que nous intégrons Python dans JMP, donc l'exécutable est JMP au lieu de Python. J'ai découvert que le package de multitraitement permet de configurer l'exécutable pour qu'il se lance lors de l'exécution du code parallèle. C'est la pièce manquante du puzzle.
Le script ci-dessous que j'ai testé sur JMP 15.2.1 sur Mac et Windows avec Python 3.9. Sur ma machine Windows, je devais utiliser l'argument Path() pour Python Init(). J'ai plusieurs versions de Python et je veux m'assurer que la bonne est choisie. Sur mon Mac, Python Init() sans arguments fonctionne bien pour ma configuration. Il y a un problème que j'ai rencontré sous Windows, j'ai dû coder en dur le chemin d'accès à mon pythonw.exe. Vous devrez modifier cela pour qu'il corresponde à votre emplacement Python. Recherchez FixMe :
Outre les paramètres Python Init() et le chemin d'accès à pythonw.exe sous Windows, cela devrait fonctionner sans problème sur JMP 15.2.1 et plus récent sur Mac ou Windows. Peut fonctionner sur les anciennes versions de JMP qui prennent en charge Python, mais qui n'ont pas été testées.
Vous devrez créer un fichier defs.py dans le même répertoire que ce script. Le package de multitraitement nécessite que le code à exécuter en parallèle provienne du code importable, et non local du script. Ainsi, la plupart du travail dans ce script consistait à s'assurer que sys.path est correctement configuré afin que le fichier defs.py puisse être simplement importé par le script.
L'intégralité du contenu du fichier defs.py se trouve dans les commentaires du fichier, ce qui facilite le collage dans un nouveau fichier. Ce contenu dupliqué ci-dessous est :
def f(x):
return x*x
Le script prend une liste de nombres [1,2,3] et en parallèle appelle f(x) pour mettre au carré la valeur donnant les résultats [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)
]\");
Intégration de tiersDéveloppement d'applications analytiquesUtilitairesRe : Traitement parallèle Python depuis JMP JSLBonjour @Paul_Nelson , comment obtenir le même résultat (exécuter un script ou une fonction python en arrière-plan) dans JMP 18, en utilisant le Python intégré ?
merci!
Matéo
Re : Traitement parallèle Python depuis JMP JSLParallel.py utilisant le même defs.py que ci-dessus est beaucoup plus facile avec l'intégration de JMP 18. JMP connaît l'emplacement de l'exécutable Python et est fourni comme « constante » dans le package d'importation jmp , et JMP sait désormais ajouter le répertoire du script au chemin de recherche du package afin que les fichiers locaux puissent être importés.
# 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 : Traitement parallèle Python depuis JMP JSLMerci pour la réponse rapide. Votre code fonctionne bien tel quel, mais j'ai du mal à l'étendre à mon cas d'intérêt. J'essaie d'exécuter la méthode exemple cmdstanpy (MCMC bayésien) à partir de JMP. En l'utilisant simplement sous sa forme de base, cela fonctionne bien, mais il ouvre un terminal cmd.exe par chaîne MCMC et le maintient ouvert jusqu'à ce que l'algorithme soit terminé (ce qui peut être assez long), ne permettant aucune interaction ni affichant aucun journal jusqu'à ce que la fin.
Voici le code de 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)
Je souhaite le laisser passer en arrière-plan et afficher les informations du journal pendant l'exécution de l'algorithme. J'ai essayé cette adaptation de votre suggestion:
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))Il ouvre 4 fenêtres cmd.exe mais il ne se termine jamais et de plus, il ne cède toujours pas le contrôle au programme principal pendant que les 4 fenêtres sont ouvertes. Une autre suggestion ?
merci
Matéo
Re : Traitement parallèle Python depuis JMP JSLJe ne connais pas le package, mais je me demande si les paramètres show_progress=True ou show_console=True pourraient poser problème. Je m'assurerais également que dans le code exécuté en parallèle, vous vous assurez qu'il dispose de gestionnaires d'exceptions, qui se terminent en cas d'échec. C'est peut-être parce que les fenêtres sont encore ouvertes qu'il ne revient pas. Essayez d'ajouter une sortie à la fin du script parallèle (pas celui de JMP).
Étant donné que JMP charge la bibliothèque partagée Python dans son espace de processus, lorsque Python est en cours d'exécution, il contrôle la boucle d'événements au lieu de JMP. Il est possible de rendre périodiquement le contrôle à JMP en utilisant jmp.run_jsl('Wait(0);') par exemple dans une boucle. Cela rend brièvement le contrôle à JMP pour exécuter la boucle d'événements JMP. Vous pourrez peut-être également expérimenter cela dans votre code d'exécution JMP, sans savoir exactement où, car il ne s'agit pas d'une boucle mais d'un lancement parallèle et le code parallèle lancé n'a aucun accès à la fonctionnalité d'importation jmp. Je joue avec votre code pour voir si quelque chose ressort.
Re : Traitement parallèle Python depuis JMP JSLJe trouve que commencer petit et simple est la meilleure façon de déboguer quelque chose. Avant d'essayer de « exécuter » en passant au multithreading, « marchez » en exécutant cmdstanpy depuis JMP.
J'ai essayé ce qui suit
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())
les résultats
/*:
//:*/
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 "", ligne 8, dans Fichier "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", ligne 212, dans __init__ model_info = self.src_info() ^^^^^ ^^^^^^^^^^ Fichier "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py", ligne 318, dans la compilation de retour src_info .src_info(str(self.stan_file), self._compiler_options) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Fichier "/Users/_me_/Bibliothèque/Application Support/JMP/Python/3.11/lib/python /site-packages/cmdstanpy/compilation.py", ligne 354, dans src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)] ^^^^^^^^^^^ ^^^ Fichier "/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py", ligne 170, dans cmdstan_path raise ValueError (ValueError : No CmdStan installation trouvée, exécutez la commande "install_cmdstan" ou (ré)activez votre environnement conda !
Comprenez ce que même cet exemple demande à JMP de faire. CmdStan est un programme en ligne de commande et le package cmdstanpy est un wrapper Python. Il semble donc qu'il fasse quelque chose comme un sous-processus Python, pour appeler le système d'exploitation, lancer le programme de ligne de commande et surveiller la sortie du shell. JMP et les packages installés par le wrapper JMP jpip se retrouvent dans le répertoire privé des packages de site de JMP. Nous n'exécutons pas d'initialisation Conda, car il ne s'agit pas d'une distribution Conda et les packages install_cmdstan peuvent ou non configurer l'environnement de manière suffisante pour s'exécuter dans l'environnement virtuel JMP. Généralement, l'activation Conda ou venv configure des variables d'environnement pour les chemins de package et les chemins de répertoire. Il est probable que install_cmdstan fasse la même chose. Cependant, tout cela suppose que le même environnement shell qui vient d'être configuré sera utilisé pour exécuter l'exécutable python qui va importer cmdstan. Ces hypothèses ne sont pas valables, JMP doit configurer lui-même ces chemins pour pouvoir isoler l'environnement du package JMP des autres instances Python de votre système. Ainsi, même si JMP peut importer et exécuter le package cmdstanpy, le package lui-même recherche l'exécutable de ligne de commande cmdstan et ne le localise pas. Ensuite, essayer d'ajouter le multitraitement revient en fait à demander au Python de JMP de lancer des copies jmp.PYTHON_EXE exécutant un script Python qui tente ensuite de lancer un sous-processus pour exécuter le programme de ligne de commande effectuant la tâche...
Mes exemples originaux présentaient le cas d'utilisation de la création d'une fonction Python pure qui serait exécutée en parallèle en lançant plusieurs instances Python. Ce que vous essayez d'exécuter ajoute la complication du wrapper Python sur un exécutable de ligne de commande et des problèmes avec les environnements shell imbriqués.
Je ne pense pas que le cas d'utilisation que vous tentez soit supportable sous la forme actuelle. Avant d'essayer dans JMP, vous devriez essayer le même multitraitement en dehors de JMP. Si vous parvenez à le faire fonctionner là-bas, vous pourriez réussir au sein de JMP. (remplacez simplement jmp.PYTHON_EXE par le chemin complet vers python.exe et n'importez pas jmp).
Je connais un script parallèle de travaux de base en Python pur. Je pense que même les packages Python importés qui ne dépendent pas des importations de bibliothèques partagées devraient fonctionner dans les scripts parallèles... (mais je n'ai pas testé cela). Cela peut nécessiter de transmettre le site.USER_BASE de l'environnement JMP au parallèle jmp.PYTHON_EXE et de configurer correctement son environnement pour qu'il corresponde au même environnement de répertoire que celui défini par JMP lui-même.
Si vous explorez, veuillez publier vos découvertes dans la communauté.
JMP est limité par le fait que Python s'exécute sur la boucle d'événements principale de JMP. L'une des solutions possibles pour JMP et Python multithread consiste à lancer des sous-interprètes Python indépendants. Cela nécessite Python 3.13 s'ils éliminent tous les problèmes liés à la suppression du Global Interpreter Lock (GIL) de Python. Je fais des recherches à ce sujet, mais Python 3.13 ne sera pas dans JMP 18. JMP 18 restera avec Python 3.11.x tout au long de son cycle de vie. En fonction des délais et de la stabilité de Python 3.13, il est possible qu'une telle fonctionnalité soit intégrée à JMP 19 ou 20.
Re : Traitement parallèle Python depuis JMP JSLMerci pour les explications. J'étudierai plus en détail et je reviendrai en cas de nouvelles découvertes.
Matéo