是否有一种非黑客方法可以在不评估的情况下使用整数在 SymPy 中打印连分数?

Is there a non-hack way to print continued fractions in SymPy with integers without evaluation?

我希望看到以 SymPy 形式显示的带整数的连续分数,但我似乎无法使 SymPy 符合要求。我发现 Stack Overflow 问答非常有用(见下文),但无法达到我的目标:

这是 $\frac{13}{5}$ 的连分式展开。此扩展的常用符号是仅给出方框内的术语,如下面的 SymPy 所示,即来自 SymPy continued_fraction_iterator 的 $[2,1,1,2]$:

Rat_13_5 = list(continued_fraction_iterator(Rational(13, 5)))
print( Rat_13_5 )
Rat_13_5 = list(continued_fraction_iterator(Rational(13, 5)))
( Rat_13_5 )
print( Rat_13_5 )

输出[2, 1, 1, 2].

2019 年 12 月 9 日 Sympy 手册版本 1.5 的第 37 页给出了一个代码片段来打印这样一个扩展的分数列表:

def list_to_frac(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr += i
        expr = 1/expr
    return l[0] + expr

如果您使用 Rat_13_5 连分数扩展列表调用 list_to_fracSymPy 会起飞并计算它:

print( list_to_frac( Rat_13_5 ) )

输出13/5

如果您改用符号列表,则 list_to_frac 会打印所需的连分数,例如,

n1, n2, n3, n4, n5, n6, n7, n8, n9 = symbols('n1:10')
cont_frac_list = [n2, n1, n1, n2]
contfrac12201015 = list_to_frac( [n2,n1,n1,n2] )
contfrac122010154

这会产生所需的(我在 JupyterLab 环境中工作,所以我实际上在整个过程中都获得了 typset LaTeX 输出):

n2 + 1/(n1 + 1/(n1 + 1/n2))

我重写了 list_to_frac 以使用 Francesco 在我之前引用的 Whosebug 问题中提供的 UnevaluatedExpr 工具:

def list_to_frac_noEval(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr = UnevaluatedExpr(expr + i)
        expr = UnevaluatedExpr( 1/expr )
    return l[0] + expr

在 $\frac{13}{5}$ 扩展列表上调用 list_to_frac_noEval

list_to_frac_noEval( [2,1,1,2] )

我获得输出

2 + (1 + (1 + 2**(-1))**(-1))**(-1)

有些人使用这种表示法(所以我想在任何情况下分享 list_to_frac_noEval,如果你想看到连分数,那比最终得到一个评估的单一有理数要好),例如 Roger Penrose在 The Road to Reality (2004) 的 $\unicode{x00A7}3.2$ 部分,但我仍然觉得很烦人,因为在使用整数而不是符号时我无法获得显式连分数格式.

我尝试使用 subs 方法和 Subs 函数用 evaluate=False 替换符号的整数,查看 sympifysreprparse_exprevaluate=False, ,但不能说服 SymPy 1.4 打印我用 list_to_frac 对符号参数进行操作获得的显式分数形式。有没有办法在不修改 SymPy 代码或对一组特定数字进行特殊封装的情况下完成此操作?

您可以构造表达式,将 evaluate=False 显式传递给表达式树的每个部分:

def list_to_frac(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr = Add(i, expr, evaluate=False)
        expr = Pow(expr, -1, evaluate=False)
    return Add(l[0], expr, evaluate=False)

这给出:

In [2]: nums = list(continued_fraction_iterator(Rational(13, 5)))

In [3]: nums
Out[3]: [2, 1, 1, 2]

In [4]: list_to_frac(nums)
Out[4]: 
      1          
───────────── + 2
    1            
───────── + 1    
  1              
───── + 1        
0 + 2  

看起来这是错误的方式,但这正是使用默认设置打印的方式:

In [5]: init_printing(order='old')

In [6]: list_to_frac(nums)
Out[6]: 
          1      
2 + ─────────────
            1    
    1 + ─────────
              1  
        1 + ─────
            0 + 2

您可以使用 doit 触发评估:

In [7]: _.doit()
Out[7]: 13/5