使用多进程时出现Sympy/mpmath/gmpy错误

0 投票
1 回答
773 浏览
提问于 2025-04-18 05:04

编辑:这是一个sympy的bug。我已经把讨论转移到 https://github.com/sympy/sympy/issues/7457

我有一个Python程序,使用 sympy 来执行一些核心功能,比如计算一条线和一个形状的交点。这个操作需要执行几千次,但使用默认的 sympy 纯Python模块时速度非常慢。

为了加快速度,我尝试安装 gmpy 2.0.3(我也试过 gmpy 1.5)。这确实让代码运行得快了一些,但当我使用 multiprocessing 来进一步加速时,程序却因为 TypeError 崩溃了。

Exception in thread Thread-3:
Traceback (most recent call last):
  File "C:\python27\lib\threading.py", line 810, in __bootstrap_inner
    self.run()
  File "C:\python27\lib\threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\python27\lib\multiprocessing\pool.py", line 376, in _handle_results
    task = get()
  File "C:\python27\lib\site-packages\sympy\geometry\point.py", line 91, in __new__
    for f in coords.atoms(Float)]))
  File "C:\python27\lib\site-packages\sympy\simplify\simplify.py", line 3839, in nsimplify
    return _real_to_rational(expr, tolerance)
  File "C:\python27\lib\site-packages\sympy\simplify\simplify.py", line 3781, in _real_to_rational
    r = nsimplify(float, rational=False)
  File "C:\python27\lib\site-packages\sympy\simplify\simplify.py", line 3861, in nsimplify
    exprval = expr.evalf(prec, chop=True)
  File "C:\python27\lib\site-packages\sympy\core\evalf.py", line 1300, in evalf
    re = C.Float._new(re, p)
  File "C:\python27\lib\site-packages\sympy\core\numbers.py", line 673, in _new
    obj._mpf_ = mpf_norm(_mpf_, _prec)
  File "C:\python27\lib\site-packages\sympy\core\numbers.py", line 56, in mpf_norm
    rv = mpf_normalize(sign, man, expt, bc, prec, rnd)
TypeError: ('argument is not an mpz', <class 'sympy.geometry.point.Point'>, (-7.07106781186548, -7.07106781186548))

当程序在单个进程中使用 gmpy 运行时,一切正常;而在没有 gmpy 的情况下使用 multiprocessing.Pool 也没问题。

有没有人遇到过类似的问题?下面的程序可以重现这个问题:

import sympy
import multiprocessing
import numpy

def thread_function(func, data, output_progress=True, extra_kwargs=None, num_procs=None):
    if extra_kwargs:
        func = functools.partial(func, **extra_kwargs)

    if not num_procs:
        num_procs = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(processes=num_procs)
    results = pool.map_async(func, data.T)
    pool.close()

    pool.join()
    return results.get()

def test_fn(data):
    x = data[0]
    y = data[1]
    circle = sympy.Circle((0,0), 10)
    line = sympy.Line(sympy.Point(0,0), sympy.Point(x,y))
    return line.intersection(circle)[0].evalf()

if __name__ == '__main__':
    data = numpy.vstack((numpy.arange(1, 100), numpy.arange(1, 100)))

    print thread_function(test_fn, data) #<--- this line causes the problem
#    print [test_fn(data[:,i]) for i in xrange(data.shape[1])] #<--- this one runs without errors

1 个回答

1

我已经确认,gmpy 对象是可以被序列化的,而使用 gmpympmath.mpf 对象也是可以被序列化的。

当传给 mpf_normalize()man 参数不是 gmpy 对象时,就会出现错误。如果我强制把 man 设置为 mpz,那么就不会再出现错误了。不过,这样得到的结果和单进程版本的结果不一样。

单进程版本:

Point(-223606797749979/50000000000000, -223606797749979/25000000000000)

多进程版本:

Point(-7.07106781186548, -7.07106781186548)

在这两个 Point() 中使用的类型是不同的(一个是有理数,一个是浮点数),而且它们的值也不同(-223606797749979/50000000000000 等于 -4.47213595499958)。

我还在继续研究,如果发现根本原因会更新这个回答。

更新 #1: 不同的值是因为示例代码中的一个错误。线程函数传入的值和非线程版本传入的值不同。

我还在追踪为什么多进程会引发这个异常。我把问题简化成了以下这个例子:

import sympy
import multiprocessing
import numpy

def thread_function(func, data, output_progress=True, extra_kwargs=None, num_procs=None):
    if extra_kwargs:
        func = functools.partial(func, **extra_kwargs)

    if not num_procs:
        num_procs = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(processes=num_procs)
    results = pool.map_async(func, data)
    pool.close()

    pool.join()
    return results.get()

def test_fn(data):
    return sympy.Point(0,1).evalf()

if __name__ == '__main__':
    test_size = 10
    print [test_fn(None) for i in xrange(1, test_size)] #<--- this one runs without errors
    print thread_function(test_fn, [None] * (test_size - 1)) #<--- this line causes the problem

撰写回答