如何在 Jupyter Notebook 中为新结构扩展 SymPy 漂亮打印?

How do I extend SymPy pretty printing for new structures in Jupyter notebook?

注意: 这是 this question in Math Stack Exchange. I had to first pose the question in Math StackExchange because Whosebug doesn't have MathJax 的副本。但是,几乎所有 SymPy 问题都在 Whosebug 上。有关所需输出的排版,请参阅 Math Stack Exchange 版本。 Math StackExchange 上的一位编辑建议我在这里交叉 post 它。

在 Jupyter Notebook 中,如果我们执行此代码:

import sympy as sp
sp.init_printing()
x,y=sp.symbols('x,y')
x**2+sp.sin(y)

由于SymPy's pretty printing process,我们将得到一个不错的输出,无需进一步编码,看起来像

现在假设我们这样做:

class MetricSpace:
    def __init__(self, M, d):
        self.M = M
        self.d = d
        
    def __repr__(self):
        return f"$({self.M}, {self.d})$ {self.__class__.__name__}"

Re,d=sp.symbols(r'\Re,d')

MetricSpace(Re,d)

那么我们得到的输出是

如果我们这样做

from IPython.core.display import Markdown
Markdown(repr(MetricSpace(Re,d)))

然后我得到了想要的输出,看起来像

我们如何编写上述代码,以便 SymPy 的漂亮打印机在 Jupyter notebook 中提供所需的输出,而无需将其包装在 Markdown(repr(...)) 中?

这是一个可以正常运行的片段。它可能无法完成所有事情 你还想要,但希望它能让你开始。

import sympy as sp
sp.init_printing()

class MetricSpace(sp.Expr):
    def __init__(self, M, d):
        self.M = M
        self.d = d

    def _latex(self, printer=None):
        return f"({printer.doprint(self.M)}, {printer.doprint(self.d)})\ \text{{{self.__class__.__name__}}}"

Re, d = sp.symbols(r'\Re,d')

MetricSpace(Re, d)

有关更多信息,请参阅 here 有关 Sympy 打印系统的信息以及如何连接到它。关键的事情 需要注意的是,如果你想让你的对象进入,你应该子类化 Expr Sympy 漂亮打印的域,乳胶打印机调用 _latex 获取其 LaTeX 的方法。它不需要用美元符号包裹, 你应该使用 printer.doprint 来获取嵌套表达式的 LaTeX。

这是在我的笔记本上生成的屏幕截图:

PS:我对一个指标 space 感兴趣,其基础集用“\Re”表示。如果 你是说实数,我可以建议使用 sp.Reals?

注:上面Izaak van Dongen的回答我已经接受了。这个答案是为那些可能感兴趣的人提供问题的背景。

我在读一本关于 SDE 的书,我问了 a question on the measure theory presentation of probability space. There are a large number of structural definitions involved. I wanted to use SymPy to organize them and present the type signatures in something mimicking Axiom style

为了在 Python 3 和 SymPy 中愉快地做到这一点,我需要一些东西:

  • 一种动态执行的方式function signatures
  • 一种pretty-printing复杂代数类型签名的方法(本题)。

我开始实施这些定义。为了检查它们是否组织正确,我问了

有了上面的 pretty-printing 解决方案,以下几个定义给出了我的解决方案的样式(没有引用大约 85 个定义的整个内容):

import sympy as sp  # I am at version 1.6.1
from typen import strict_type_hints, enforce_type_hints
from traits.api import Array, Either, Enum, Instance, Int, Str, Tuple

class Concept(sp.Expr):
    def __init__(self, name, value):
        self.name = name
        self.value = value
        
    def _latex(self, printer=None):
        return f"{self.name}:\ \text{{{self.__class__.__name__}}}"

class NonemptySet(Concept):
    def __init__(self, name, value):
        if value==sp.S.EmptySet:
            raise ValueError("Set must not be empty")
        super().__init__(name, value)
        
    def _latex(self, printer=None):
        return self.name

class Reals(NonemptySet):
    
    @strict_type_hints
    def __init__(self):
        self.name = sp.symbols('\Re')
        super().__init__(self.name,sp.Reals)
        
    def _latex(self, printer=None):
        return self.name

class SampleSpace (NonemptySet):
    pass

class Algebra(NonemptySet):
    
    @strict_type_hints
    def __init__(self, 
                 name: sp.Symbol, 
                 Ω: SampleSpace, 
                 A: Either(NonemptySet, sp.Symbol)):
        self.Ω=Ω
        super().__init__(name, A)

    def _latex(self, printer=None):
        math=str(self.name).replace('$','')
        math2 = self.Ω._latex(printer)
        return f"{math}:\ \text{{{self.__class__.__name__} on }} ({math2})"

class Algebra(Algebra):
    
    @strict_type_hints
    def __init__(self, name: sp.Symbol, Ω: SampleSpace, A: Algebra):
        self.Ω=Ω
        super().__init__(name, Ω, A)

class EventSpace(Algebra):
    
    @strict_type_hints
    def __init__(self, name: sp.Symbol, Ω: SampleSpace, A: Algebra):
        super().__init__(name, Ω, A)

class AdditiveFunction(Concept):
    
    @strict_type_hints
    def __init__(self, 
                 name: sp.core.function.UndefinedFunction, 
                 Ω: SampleSpace, 
                 A: Algebra, 
                 f: sp.core.function.UndefinedFunction):
        self.Ω = Ω
        self.A = A
        super().__init__(name, f)

    def _latex(self, printer=None):
        math2 = self.A._latex(printer)
        return f"{self.name}: {self.A.name} \to \Re \ \text{{{self.__class__.__name__} on }} {math2}"

等等。任何关于改进上述草图的更“SymPy-thonic”方式的评论或建议将不胜感激。

如果您只关心与 Jupyter 笔记本的集成,那么您需要 IPython _repr_latex_ 挂钩:

class MetricSpace:
   def _repr_latex_(self):
       # this is text-mode latex, so needs $ to enter math mode
       return f"$({self.M}, {self.d})$ {self.__class__.__name__}"

这将使 MetricSpace(...) 在 jupter notebook 中显示为 latex,并且完全独立于 sympy。


另外,sympy 有一个 latex 函数。如果你想支持它,你需要实现 _latex:

class MetricSpace:
   def _latex(self, printer):
       # this is math-mode latex, so needs \text to enter text mode
       # if the class members support this hook, you can use `printer._print` to recurse
       return f"{printer._print(self.M)}, {printer._print(self.d)}$ \text{{{self.__class__.__name__}}}"

这将使 sympy.latex(MetricSpace(...)) 工作。


最后,在调用 init_printing(use_latex='mathjax') 之后,还有将这两种模式连接在一起的 sympy 集成。这在 Sympy 1.6 和 1.7 之间发生了变化。

  • 在 Sympy 1.6 中,提供了一个 ipython latex 格式化程序
    • classsympy.Basic(和其他一些)
    • lists、sets 等这些对象
  • 在 Sympy 1.7 中,提供了一个 ipython latex 格式化程序
    • 子class属于sympy.printing.defaults.Printable
    • lists、sets 等任何实现 _latex 或 subclasses Printable
    • 的对象

Subclassing Expr 是一个坏主意,因为这会向您的 class 添加数十个可能毫无意义的方法和运算符重载。 Subclassing Basic 没有那么糟糕,但是如果你的 class 不打算在 sympy

中使用,那么它会遇到相同问题的较小版本