【问题标题】:Step by step differentiation with sympy用同情逐步区分
【发布时间】:2020-11-21 13:42:06
【问题描述】:

我正在尝试制作一个 python proram 来查找导数和积分以及如何展示。到目前为止,我发现有一个 integer_steps 函数可以返回使用的步骤,但我还没有找到一个等价的微分。

有没有人知道有没有类似的?

如果没有,您对如何找到找到导数所需的步骤有任何想法吗?

【问题讨论】:

    标签: python math sympy calculus differentiation


    【解决方案1】:

    方法一(手动)

    查看代码,the Derivative class 是顶级逻辑所在的位置。这只是顶层部分。从那时起,计算需要计算expression tree内不同节点的导数。

    表达式树的每个特定节点的逻辑存在于与每个特定节点类型对应的_eval_derivative method

    这将允许您向那些 _eval_derivative 方法添加代码,以便跟踪整个过程并找到所有步骤。

    方法 2(使用示踪剂)

    Python 有多个 tracing 包。 @ionelmc 编写的 python-hunter 实际上非常好,非常适合这个用例。

    在许多其他功能中,它允许在函数开始执行时安装某些回调,并在函数返回其值时安装另一个回调。事实上,这正是我们所需要的。

    这是一个展示如何使用它的示例(我在 Python 3.7.3、SymPy 1.7 和 Hunter 3.3.1 上运行并测试了它):

    import hunter
    import sys 
    from hunter import Q, When, Stop
    hunter.trace(
            Q(module_contains="sympy",function='_eval_derivative',kind_in=["call","return"],action=hunter.CallPrinter(repr_func=str))
            )
    
    
    from sympy import *
    x = symbols('x')
    f = 1/(x * sin(x)**2)
    f.diff(x)
    

    因此,这允许我们选择我们想要检查的数据结构、我们想要如何打印它们,并且它允许我们查看微分过程的中间步骤:

    [...]7/site-packages/sympy/core/power.py:1267  call      => _eval_derivative(self=sin(x)**(-2), s=x)
    [...]7/site-packages/sympy/core/power.py:1267  call         => _eval_derivative(self=<sympy.core.power.Pow object at 0x7f5925337150>, s=<sympy.core.symbol.Symbol object at 0x7f5925b6a2b0>)
    [...]ite-packages/sympy/core/function.py:598   call            => _eval_derivative(self=sin(x), s=x)
    [...]ite-packages/sympy/core/function.py:598   call               => _eval_derivative(self=<sympy.functions.elementary.trigonometric.sin object at 0x7f592589ee08>, s=<sympy.core.symbol.Symbol object at 0x7f5925b6a2b0>)
    [...]ite-packages/sympy/core/function.py:612   return             <= _eval_derivative: cos(x)
    [...]ite-packages/sympy/core/function.py:612   return          <= _eval_derivative: <sympy.functions.elementary.trigonometric.cos object at 0x7f592525fef8>
    [...]7/site-packages/sympy/core/power.py:1271  return       <= _eval_derivative: -2*cos(x)/sin(x)**3
    [...]7/site-packages/sympy/core/power.py:1271  return    <= _eval_derivative: <sympy.core.mul.Mul object at 0x7f5925259b48>
    [...]7/site-packages/sympy/core/power.py:1267  call      => _eval_derivative(self=1/x, s=x)
    [...]7/site-packages/sympy/core/power.py:1267  call         => _eval_derivative(self=<sympy.core.power.Pow object at 0x7f5925337200>, s=<sympy.core.symbol.Symbol object at 0x7f5925b6a2b0>)
    [...]7/site-packages/sympy/core/power.py:1271  return       <= _eval_derivative: -1/x**2
    [...]7/site-packages/sympy/core/power.py:1271  return    <= _eval_derivative: <sympy.core.mul.Mul object at 0x7f5925259f10>
    

    如果您还想覆盖 diff 函数,您可以更改上面的代码并拥有 function_in=['_eval_derivative','diff'] 。这样不仅可以查看部分结果,还可以查看diff函数的调用及其返回值。

    方法3(使用跟踪器,构建调用图并可视化)

    使用 graphviz、latex 和示踪剂(同样是python-hunter),您实际上可以更清楚地看到call graph。渲染每个中间步骤的所有公式确实需要一些时间,因为正在使用pdflatex(不过我确信有更快的乳胶渲染器)。

    每个节点的值格式如下:

    function_name
    argument => return_value
    

    似乎有一些 diff 节点的参数等于返回值,我现在不知道如何解释。

    如果图表以某种方式提及每条规则的应用位置,它可能会更有用(我想不出一个简单的方法来做到这一点)。

    这也是代码:

    import hunter
    import sys
    from hunter import Q, When, Stop, Action
    from hunter.actions import  ColorStreamAction
    
    formula_ltx = r'''
    \documentclass[border=2pt,varwidth]{letter}
    \usepackage{amsmath}
    \pagenumbering{gobble}
    \begin{document}
    \[ \texttt{TITLE} \]
    \[ FORMULA \]
    \end{document}
    '''
    
    # ==============
    # == Tracing ===
    # ==============
    
    from sympy.printing.latex import LatexPrinter, print_latex, latex
    
    global call_tree_root
    
    # a node object to hold an observed function call
    # with its argument, its return value and its function name
    class Node(object):
        def __init__(self, arg=None, retval=None, func_name=None):
            self.arg = arg
            self.retval = retval
            self.arg_ascii = ""
            self.retval_ascii = ""
            self.func_name = func_name
            self.uid = 0
            self.children = []
    
    # this is a hunter action where we build a call graph and populate it
    # so we can later render it
    #
    # CGBAction (Call Graph Builder Action)
    class CGBAction(ColorStreamAction):
        def __init__(self, *args, **kwargs):
            super(ColorStreamAction, self).__init__(*args, **kwargs)
            # a custom call stack
            self.tstack = []
            global call_tree_root
            call_tree_root = Node(arg="",func_name="root")
            self.node_idx = 1
            self.tstack.append(call_tree_root)
    
        def __call__(self, event):
            if event.kind in ['return','call']:
                if event.kind == 'return':
                    print(str(event.arg))
                    if len(self.tstack) > 0:
                        top = self.tstack.pop()
                        top.retval = latex(event.arg)
                        top.retval_ascii = str(event.arg)
    
                elif event.kind == 'call':
                    print(str(event.locals.get('self')))
                    new = Node()
                    new.uid = self.node_idx
                    new.arg = latex(event.locals.get('self'))
                    new.arg_ascii = str(event.locals.get('self'))
                    top = self.tstack[-1]
                    self.tstack.append(new)
                    top.children.append(new)
                    new.func_name = event.module + ":" + event.function
                    self.node_idx += 1
    
    hunter.trace(
            Q(module_contains="sympy",function_in=['_eval_derivative','diff'],kind_in=["call","return"],action=CGBAction)
            )
    
    from sympy import *
    x = symbols('x')
    f = 1 / (x * sin(x)**2)
    #f = 1 / (x * 3)
    #f = sin(exp(cos(x)*asin(x)))
    f.diff(x)
    
    # ============================
    # == Call graph rendering ====
    # ============================
    
    import os
    import re
    OUT_DIR="formulas"
    
    if not os.path.exists(OUT_DIR):
        os.mkdir(OUT_DIR)
    
    def write_formula(prefix,uid,formula,title):
        TEX = uid + prefix + ".tex"
        PDF = uid + prefix + ".pdf"
        PNG = uid + prefix + ".png"
    
        TEX_PATH = OUT_DIR + "/" + TEX
        with open(TEX_PATH,"w") as f:
            ll = formula_ltx
            ll = ll.replace("FORMULA",formula)
            ll = ll.replace("TITLE",title)
            f.write(ll)
    
        # compile formula
        CMD = """
            cd formulas ; 
            pdflatex {TEX} ;
            convert -trim -density 300 {PDF} -quality 90 -colorspace RGB {PNG} ;
        """.format(TEX=TEX,PDF=PDF,PNG=PNG)
    
        os.system(CMD)
    
    buf_nodes = ""
    buf_edges = ""
    def dfs_tree(x):
        global buf_nodes, buf_edges
    
        arg = ("" if x.arg is None else x.arg)
        rv  = ("" if x.retval is None else x.retval)
        arg = arg.replace("\r","")
        rv = rv.replace("\r","")
    
        formula = arg + "\\Rightarrow " + rv
        print(x.func_name + " -> " + x.arg_ascii + " -> " + x.retval_ascii)
    
        x.func_name = x.func_name.replace("_","\\_")
        write_formula("",str(x.uid),formula,x.func_name)
    
        buf_nodes += """
        {0} [image="{0}.png" label=""];
        """.format(x.uid);
    
        for y in x.children:
            buf_edges += "{0} -> {1};\n".format(x.uid,y.uid);
            dfs_tree(y)
    
    dfs_tree(call_tree_root)
    
    g = open(OUT_DIR + "/graph.dot", "w")
    g.write("digraph g{")
    g.write(buf_nodes)
    g.write(buf_edges)
    g.write("}\n")
    g.close()
    os.system("""cd formulas ; dot -Tpng graph.dot > graph.png ;""")
    

    将 SymPy 逻辑映射到微分规则​​

    我认为剩下的一步是将中间节点从 SymPy 映射到 differentiation rules。以下是我能够映射的一些:

    我没有在 sympy.core 中看到 Fraction 类,所以 quotient rule 可能是通过 product rule 和指数为 -1 的 generalized power rule 间接处理的。

    跑步

    为了让它运行,你需要:

    sudo apt-get install graphviz imagemagick texlive texlive-latex-base
    

    并且文件/etc/ImageMagick-6/policy.xml 必须使用以下行更新以允许从PDF-&gt;PNG 转换:

    <policy domain="coder" rights="read|write" pattern="PDF" />
    

    还有另一个调用图库,名为 jonga,但它有点通用,无法完全过滤掉不需要的调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多