pythonsympysymbolic-mathcommutativity

Is it possible to construct symbols in SymPy that anticommute?


I need to implement some Grassmann variables in python (i.e. anti-commuting variables). In other words, I would like something with behavior as follows

>>> from sympy import *
>>> x, y = symbols('x y')
>>> y*x
-x*y
>>> y*y
0

One other feature I would need from this is the ability to give a canonical ordering to my variables. When I typed >>> y*x, it would certainly have been valid to also output y*x over -x*y. But, I would like the ability to choose that x should appear to the left of y (perhaps only after calling a function simplify(y*x)).

Does SymPy or some other library have this ability? If not, what would be the best way to go about implementing this myself (e.g. should I create a symbolic library myself, extend SymPy, etc.)?


Solution

  • You can make a new class inheriting from Symbol and change its behaviour on multiplication (__mul__) to the desired one. To make this any useful, you need a canonic ordering anyway, which should be the same as SymPy’s (which at a quick glance appears to be by name, i.e., Symbol.name) to avoid problems.

    from sympy import Symbol, S
    
    class AnticomSym(Symbol):
        def __new__(cls,*args,**kwargs):
            return super().__new__(cls,*args,**kwargs,commutative=False)
    
        def __mul__(self,other):
            if isinstance(other,AnticomSym):
                if other==self:
                    return S.Zero
                elif other.name<self.name:
                    return -Symbol.__mul__(other,self)
    
            return super().__mul__(other)
    
        def __pow__(self,exponent):
            if exponent>=2:
                return S.Zero
            else:
                return super().__pow__(exponent)
    
    
    x = AnticomSym("x")
    y = AnticomSym("y")
    
    assert y*x == -x*y
    assert y*y == 0
    assert y**2 == 0
    assert y**1 == y
    assert ((x+y)**2).expand() == 0
    assert x*y-y*x == 2*x*y
    

    Now, this still does not resolve complex products such as x*y*x*y correctly. For this, we can write a function that sorts an arbitrary product (using bubble sort):

    from sympy import Mul
    
    def sort_product(product):
        while True:
            if not isinstance(product,Mul):
                return product
    
            arglist = list(product.args)
            i = 0
            while i < len(arglist)-1:
                slice_prod = arglist[i]*arglist[i+1]
                is_mul = isinstance(slice_prod,Mul)
                arglist[i:i+2] = slice_prod.args if is_mul else [slice_prod]
                i += 1
    
            new_product = Mul(*arglist)
            if product == new_product:
                return new_product
            product = new_product
    
    z = AnticomSym("z")
    assert sort_product(y*(-x)) == x*y
    assert sort_product(x*y*x*y) == 0
    assert sort_product(z*y*x) == -x*y*z
    

    Finally, we can write a function that sorts all products within an expression by iterating through the expression tree and applying sort_product to every product it encounters:

    def sort_products(expr):
        if expr.is_Atom:
            return expr
        else:
            simplified_args = (sort_products(arg) for arg in expr.args)
            if isinstance(expr,Mul):
                return sort_product(Mul(*simplified_args))
            else:
                return expr.func(*simplified_args)
    
    from sympy import exp
    assert sort_products(exp(y*(-x))) == exp(x*y)
    assert sort_products(exp(x*y*x*y)-exp(z*y*z*x)) == 0
    assert sort_products(exp(z*y*x)) == exp(-x*y*z)
    

    Note that I may still not have accounted for every eventuality.