回复:来自 JMP JSL 的 Python 并行处理请注意,内容编辑器从 defs.py 的内容中删除了一个回车符,应该显示为:
def f(x):
return x*x
来自 JMP JSL 的 Python 并行处理在假期休息期间,我有时间修补 Python。 我们在 Python/JSL 接口的并行处理中遇到的问题是 Python 通常会启动 JMP 的多个副本,而不是 Python。 原因是我们在 JMP 中嵌入了 Python,因此可执行文件是 JMP 而不是 Python。 我发现 multiprocessing 包允许将可执行文件设置为在运行并行代码时启动。 这是拼图中缺失的部分。
下面的脚本我已经在 Mac 和 Windows 上使用 Python 3.9 在 JMP 15.2.1 上进行了测试。在我的 Windows 机器上,我必须使用 Python Init() 的 Path() 参数。 我有多个版本的 Python,并希望确保选择正确的版本。 在我的 Mac 上,不带参数的 Python Init() 适用于我的配置。 我在 Windows 上遇到了一个问题,我不得不硬编码我的 pythonw.exe 的路径。 您将需要更改它以匹配您的 Python 位置。 搜索修复我:
除了 Python Init() 参数和 Windows 上的 pythonw.exe 路径之外,这应该在 JMP 15.2.1 和更新版本的 Mac 或 Windows 上运行没有问题。 可以在具有 Python 支持但尚未经过测试的旧版 JMP 上运行。
您需要在与此脚本相同的目录中创建一个 defs.py 文件。 多处理包要求并行运行的代码来自可导入的代码,而不是脚本的本地代码。 所以这个脚本中的大部分工作是确保 sys.path 设置正确,以便脚本可以简单地导入 defs.py 文件。
defs.py 文件的全部内容都在文件注释中,便于粘贴到新文件中。 下面重复的内容是:
def f(x):
return x*x
该脚本接受一个数字列表 [1,2,3] 并并行调用 f(x) 来平方给出结果 [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)
]\");
第三方集成分析应用程序开发实用程序回复:JMP JSL 中的 Python 并行处理您好@Paul_Nelson ,如何使用嵌入式 Python 在 JMP 18 中实现相同的结果(在后台运行 python 脚本或函数)?
谢谢!
马泰奥
回复:JMP JSL 中的 Python 并行处理使用与上面相同的 defs.py 的 Parallel.py 通过 JMP 18 的集成变得更加容易。 JMP 知道 Python 可执行文件的位置,并在jmp导入包中作为“常量”提供,并且 JMP 现在知道将脚本的目录添加到包搜索路径,以便可以导入本地文件。
# 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)回复:JMP JSL 中的 Python 并行处理感谢您及时的回复。您的代码按原样工作正常,但我无法将其扩展到我感兴趣的案例。我正在尝试从 JMP 运行 cmdstanpy 示例方法(贝叶斯 MCMC)。仅以基本形式使用它,它工作得很好,但它会为每个 MCMC 链打开一个 cmd.exe 终端,并保持它们打开直到算法完成(这可能会相当长的时间),不允许任何交互,也不允许显示任何日志,直到结束。
这是基本代码:
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)
我想让它在后台运行并在算法运行时显示日志信息。我根据你的建议尝试了这种改编:
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))它确实打开了 4 个 cmd.exe 窗口,但它永远不会完成,而且在 4 个窗口打开时,它仍然没有将控制权交还给主程序。还有进一步的建议吗?
谢谢
马泰奥
回复:JMP JSL 中的 Python 并行处理我不熟悉该软件包,但我想知道参数 show_progress=True 或 show_console=True 是否会出现问题。我还要确保在并行运行的代码中,它具有异常处理程序,如果发生故障,该处理程序会退出。可能是因为窗口仍然打开,所以它没有返回。尝试在并行脚本的末尾添加一个退出(而不是 JMP 中的退出)。
由于 JMP 将 Python 共享库加载到其进程空间中,因此当 Python 运行时,它可以控制事件循环,而不是 JMP。可以通过使用 jmp.run_jsl('Wait(0);') (例如在循环内)定期将控制权交还给 JMP。这会将控制权短暂返回给 JMP 以运行 JMP 事件循环。您也可以在 JMP 运行代码中进行试验,但不确定具体位置,因为您不是循环而是并行启动,并且启动的并行代码无法访问导入 jmp 功能。我玩了一下你的代码,看看是否有什么突出的地方。
回复:JMP JSL 中的 Python 并行处理我发现从小而简单的开始是调试某些东西的最佳方法。在尝试通过跳转到多线程来“运行”之前,请通过从 JMP 中运行 cmdstanpy 来“行走”。
我尝试了以下方法
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())
结果
/*:
//:*/
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 "”,第 8 行,在文件“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py”,第 212 行,位于 __init__ model_info = self.src_info() ^^^^^ ^^^^^^^^^^ 文件“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/model.py”,第 318 行,在 src_info 返回编译中.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”,第 354 行,在 src_info [os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)] ^^^^^^^^^^^ ^^^ 文件“/Users/_me_/Library/Application Support/JMP/Python/3.11/lib/python/site-packages/cmdstanpy/utils/cmdstan.py”,第 170 行,在 cmdstan_path 中引发 ValueError( ValueError: No CmdStan找到安装,运行命令“install_cmdstan”或(重新)激活您的 conda 环境!
了解这个示例要求 JMP 做什么。 CmdStan 是一个命令行程序,cmdstanpy 包是一个 Python 包装器。因此,它看起来就像一个 Python 子进程,调用操作系统,启动命令行程序,并监视 shell 输出。 JMP 和 JMP jpip 包装器安装的包最终位于 JMP 的私有 site-packages 目录中。我们没有运行 Conda init,因为这不是 Conda 发行版,并且 install_cmdstan 软件包可能会也可能不会配置足以在 JMP 虚拟环境中运行的环境。通常,Conda 或 venv 激活会为包路径和目录路径设置环境变量。 install_cmdstan 可能会做同样的事情。然而,所有这些都假设刚刚配置的相同 shell 环境将用于运行将导入 cmdstan 的 python 可执行文件。这些假设不成立,JMP 必须自行设置这些路径,以便能够将 JMP 包环境与系统上的其他 Python 实例隔离。因此,虽然 JMP 可以导入并运行 cmdstanpy 包,但该包本身正在查找 cmdstan 命令行可执行文件,而不是找到它。然后尝试添加多处理,实际上是要求 JMP 的 Python 启动运行 Python 脚本的 jmp.PYTHON_EXE 副本,然后依次尝试启动子进程来运行执行任务的命令行程序...
我最初的示例介绍了创建纯 Python 函数的用例,该函数将通过启动多个 Python 实例并行运行。您尝试运行的内容会在命令行可执行文件上添加 Python 包装器的复杂性以及嵌套 shell 环境的问题。
我认为当前形式不支持您尝试的用例。在尝试使用 JMP 之前,您应该在 JMP 之外尝试相同的多处理。如果您能让它在那里发挥作用,您可能会在 JMP 中取得成功。 (只需将 jmp.PYTHON_EXE 替换为 python.exe 的完整路径,并且不要导入 jmp)。
我知道基本纯Python 的并行脚本可以工作。我相信即使是不依赖于共享库导入的导入Python包也应该在并行脚本中工作......(但我还没有测试过)。它可能需要将 JMP 环境的 site.USER_BASE 传递到并行 jmp.PYTHON_EXE 并正确设置其环境以匹配 JMP 本身设置的相同目录环境。
如果您在探索时,请将您的发现发布回社区。
JMP 受到以下事实的限制:Python 在 JMP 的主事件循环上运行。 JMP 和多线程 Python 的可能解决方案之一是启动独立的 Python 子解释器。如果他们解决了删除 Python 全局解释器锁 (GIL) 的所有问题,则需要 Python 3.13。我正在研究这一点,但 Python 3.13 不会出现在 JMP 18 中。JMP 18 在其整个生命周期中将保留在 Python 3.11.x 中。根据 Python 3.13 的时间表和稳定性,此类功能可能会进入 JMP 19 或 20。
回复:JMP JSL 中的 Python 并行处理感谢您的解释。我将进一步研究并在有新发现时回来。
马泰奥