常见问题和陷阱¶
介绍¶
SymPy 在 Python 编程语言 下运行,因此某些行为可能与其他独立的计算机代数系统(如 Maple 或 Mathematica)不同。以下是一些在使用 SymPy 时可能遇到的常见问题和陷阱。另请参阅 入门教程、SymPy 文档的其余部分以及 官方 Python 教程。
如果您已熟悉 C 或 Java,您可能还想查看此 4 分钟 Python 教程。
忽略示例中的 #doctest: +SKIP
。这与示例的内部测试有关。
等号 (=)¶
单个等号¶
等号 (=
) 是赋值运算符,而不是相等运算符。如果您要进行 \(x = y\),请使用 Eq(x, y)
表示相等。或者,所有表达式都被认为等于零,因此您可以只减去一边并使用 x - y
。
等号的正确用法是将表达式赋给变量。
例如
>>> from sympy.abc import x, y
>>> a = x - y
>>> print(a)
x - y
双等号¶
双等号 (==
) 用于测试相等性。但是,这会精确地测试表达式,而不是符号地。例如
>>> (x + 1)**2 == x**2 + 2*x + 1
False
>>> (x + 1)**2 == (x + 1)**2
True
如果你想要测试符号等式,一种方法是从另一个表达式中减去一个表达式,并将其通过函数运行,比如 expand()
、simplify()
和 trigsimp()
,看看方程是否简化为 0。
>>> from sympy import simplify, cos, sin, expand
>>> simplify((x + 1)**2 - (x**2 + 2*x + 1))
0
>>> eq = sin(2*x) - 2*sin(x)*cos(x)
>>> simplify(eq)
0
>>> expand(eq, trig=True)
0
变量¶
变量赋值不会在表达式之间创建关系¶
当你使用 =
进行赋值时,请记住,在 Python 中,就像在大多数编程语言中一样,如果你改变你分配给它的值,变量不会改变。你输入的方程使用创建时的值来“填充”值,就像普通的 Python 定义一样。它们不会被之后所做的更改所改变。考虑以下情况
>>> from sympy import Symbol
>>> a = Symbol('a') # Symbol, `a`, stored as variable "a"
>>> b = a + 1 # an expression involving `a` stored as variable "b"
>>> print(b)
a + 1
>>> a = 4 # "a" now points to literal integer 4, not Symbol('a')
>>> print(a)
4
>>> print(b) # "b" is still pointing at the expression involving `a`
a + 1
改变数量 a
不会改变 b
;你并不是在处理一组联立方程。记住,当你打印一个引用 SymPy 对象的变量时,打印出来的字符串是它在创建时被赋予的字符串;该字符串不必与你分配给它的变量相同,这可能会有所帮助。
>>> from sympy import var
>>> r, t, d = var('rate time short_life')
>>> d = r*t
>>> print(d)
rate*time
>>> r = 80
>>> t = 2
>>> print(d) # We haven't changed d, only r and t
rate*time
>>> d = r*t
>>> print(d) # Now d is using the current values of r and t
160
如果你需要相互依赖的变量,你可以定义函数。使用 def
运算符。缩进函数体。有关定义函数的更多信息,请参见 Python 文档。
>>> c, d = var('c d')
>>> print(c)
c
>>> print(d)
d
>>> def ctimesd():
... """
... This function returns whatever c is times whatever d is.
... """
... return c*d
...
>>> ctimesd()
c*d
>>> c = 2
>>> print(c)
2
>>> ctimesd()
2*d
如果你定义了一个循环关系,你将得到一个 RuntimeError
。
>>> def a():
... return b()
...
>>> def b():
... return a()
...
>>> a()
Traceback (most recent call last):
File "...", line ..., in ...
compileflags, 1) in test.globs
File "<...>", line 1, in <module>
a()
File "<...>", line 2, in a
return b()
File "<...>", line 2, in b
return a()
File "<...>", line 2, in a
return b()
...
RuntimeError: maximum recursion depth exceeded
符号¶
符号是变量,与所有其他变量一样,它们需要在使用之前被赋值。例如
>>> import sympy
>>> z**2 # z is not defined yet
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined
>>> sympy.var('z') # This is the easiest way to define z as a standard symbol
z
>>> z**2
z**2
如果你使用 isympy,它会为你运行以下命令,为你提供一些默认的符号和函数。
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)
你也可以从 sympy.abc
中导入常见的符号名称。
>>> from sympy.abc import w
>>> w
w
>>> import sympy
>>> dir(sympy.abc)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'Symbol', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'__builtins__', '__doc__', '__file__', '__name__', '__package__', '_greek',
'_latin', 'a', 'alpha', 'b', 'beta', 'c', 'chi', 'd', 'delta', 'e',
'epsilon', 'eta', 'f', 'g', 'gamma', 'h', 'i', 'iota', 'j', 'k', 'kappa',
'l', 'm', 'mu', 'n', 'nu', 'o', 'omega', 'omicron', 'p', 'phi', 'pi',
'psi', 'q', 'r', 'rho', 's', 'sigma', 't', 'tau', 'theta', 'u', 'upsilon',
'v', 'w', 'x', 'xi', 'y', 'z', 'zeta']
如果你想要控制变量的假设,请使用 Symbol
和 symbols()
。请参阅下文的 关键字参数。
最后,建议不要使用 I
、E
、S
、N
、C
、O
或 Q
作为变量或符号名称,因为它们分别用于虚数单位 (\(i\))、自然对数的底 (\(e\))、sympify()
函数(见下文的 符号表达式)、数值求值(N()
等效于 evalf())、大 O 阶符号(如 \(O(n\log{n})\))以及保存支持的询问键列表(如 Q.real
)的假设对象。你可以使用助记符 OSINEQ
来记住 SymPy 中默认定义了哪些符号。或者更好的是,始终使用小写字母作为符号名称。Python 不会阻止你覆盖默认的 SymPy 名称或函数,所以要小心。
>>> cos(pi) # cos and pi are a built-in sympy names.
-1
>>> pi = 3 # Notice that there is no warning for overriding pi.
>>> cos(pi)
cos(3)
>>> def cos(x): # No warning for overriding built-in functions either.
... return 5*x
...
>>> cos(pi)
15
>>> from sympy import cos # reimport to restore normal behavior
要获得 SymPy 中所有默认名称的完整列表,请执行以下操作
>>> import sympy
>>> dir(sympy)
# A big list of all default sympy names and functions follows.
# Ignore everything that starts and ends with __.
如果你安装了 IPython 并使用 isympy,你也可以按 TAB 键来获取所有内置名称的列表并自动完成。此外,请参阅 此页面,了解在常规 Python 控制台中获取制表符完成的技巧。
函数¶
一个像 f(x)
这样的函数可以通过定义函数和变量来创建
>>> from sympy import Function
>>> f = Function('f')
>>> x = Symbol('x')
>>> f(x)
f(x)
如果你将 f(x)
赋值给 Python 变量 \(f\),你将失去复制和粘贴该函数或创建具有不同参数的函数的能力:Function('f')
是可调用的,但 Function('f')(x)
不是
>>> f1 = Function('f1')
>>> f2 = Function('f2')('x')
>>> f1
f1
>>> f2
f2(x)
>>> f1(1)
f1(1)
>>> f2(1)
Traceback (most recent call last):
...
TypeError: 'f2' object is not callable
>>> f2.subs(x, 1)
f2(1)
符号表达式¶
Python 数字与 SymPy 数字¶
SymPy 使用它自己的类来表示整数、有理数和浮点数,而不是默认的 Python int
和 float
类型,因为它允许更多的控制。但你必须小心。如果你输入一个只包含数字的表达式,它将默认为 Python 表达式。使用 sympify()
函数,或者只是 S
,以确保某物是 SymPy 表达式。
>>> 6.2 # Python float. Notice the floating point accuracy problems.
6.2000000000000002
>>> type(6.2) # <type 'float'> in Python 2.x, <class 'float'> in Py3k
<... 'float'>
>>> S(6.2) # SymPy Float has no such problems because of arbitrary precision.
6.20000000000000
>>> type(S(6.2))
<class 'sympy.core.numbers.Float'>
如果你在 SymPy 表达式中包含数字,它们将被自动符号化,但有一个需要注意的地方。如果你在 SymPy 表达式中执行 <number>/<number>
,Python 将在 SymPy 有机会获取它们之前对这两个数字进行求值。解决方法是对其中一个数字使用 sympify()
,或者使用 Rational
。
>>> x**(1/2) # evaluates to x**0 or x**0.5
x**0.5
>>> x**(S(1)/2) # sympyify one of the ints
sqrt(x)
>>> x**Rational(1, 2) # use the Rational class
sqrt(x)
对于 1/2
的幂,你也可以使用 sqrt
简写
>>> sqrt(x) == x**Rational(1, 2)
True
如果两个整数没有被除号直接隔开,那么你不必担心这个问题
>>> x**(2*x/3)
x**(2*x/3)
注意
一个常见的错误是复制打印的表达式并重复使用它。如果表达式中有一个 Rational
(即 <number>/<number>
),你将不会得到相同的结果,而是得到 Python 对除法的结果,而不是 SymPy 有理数。
>>> x = Symbol('x')
>>> print(solve(7*x -22, x))
[22/7]
>>> 22/7 # If we just copy and paste we get int 3 or a float
3.142857142857143
>>> # One solution is to just assign the expression to a variable
>>> # if we need to use it again.
>>> a = solve(7*x - 22, x)[0]
>>> a
22/7
另一种解决方法是在表达式周围加上引号,并通过 S()(即将其符号化)运行它
>>> S("22/7")
22/7
此外,如果你没有使用 isympy,你可以使用 from __future__ import division
来防止 /
符号执行 整数除法。
>>> from __future__ import division >>> 1/2 # With division imported it evaluates to a python float 0.5 >>> 1//2 # You can still achieve integer division with // 0但要小心:你现在将收到浮点数,而你可能希望得到有理数
>>> x**(1/2) x**0.5
Rational
仅适用于 number/number,并且仅适用于有理数。如果你想要一个包含符号或表达式的分数,只需使用 /
。如果你执行 number/expression 或 expression/number,那么该数字将自动转换为 SymPy 数字。你只需要注意 number/number。
>>> Rational(2, x)
Traceback (most recent call last):
...
TypeError: invalid input: x
>>> 2/x
2/x
使用浮点数和有理数求值表达式¶
SymPy 会跟踪 Float
对象的精度。默认精度为 15 位。当包含 Float
的表达式被求值时,结果将以 15 位精度表示,但这些位数(取决于参与计算的数字)可能并不都具有意义。
首先要记住的是 Float
是如何创建的:它使用一个值和一个精度创建的。精度指示当该 Float
(或它出现的表达式)被求值时,使用多精确的值。
这些值可以作为字符串、整数、浮点数或有理数给出。
字符串和整数被解释为精确的
>>> Float(100) 100.000000000000 >>> Float('100', 5) 100.00
为了使精度与数字位数匹配,可以使用空字符串作为精度。
>>> Float(100, '') 100. >>> Float('12.34') 12.3400000000000 >>> Float('12.34', '') 12.34>>> s, r = [Float(j, 3) for j in ('0.25', Rational(1, 7))] >>> for f in [s, r]: ... print(f) 0.250 0.143
接下来,请注意这些值中的每一个在 3 位数中看起来都是正确的。但是,如果我们尝试将它们评估到 20 位数,就会发现差异。
精度为 3 的 0.25 表示一个具有非循环二进制小数的数字;1/7 在二进制和小数中是循环的 - 它无法被准确地表示到这 3 位数之后太远(正确的小数是一个循环的 142857)。
>>> s.n(20) 0.25000000000000000000 >>> r.n(20) 0.14285278320312500000重要的是要意识到,尽管 Float 以任意精度以小数形式显示,但它实际上是存储在二进制中的。一旦创建 Float,它的二进制信息就会以给定的精度设置。该值的精度无法随后更改;因此,精度为 3 位的 1/7 可以用二进制零填充,但这不会使它成为 1/7 的更精确值。
如果精度较低的数字参与了高精度值的计算,evalf 引擎会提高精度较低的值的精度,并将得到不精确的结果。这是有限精度计算的一个特征。
>>> Float('0.1', 10) + Float('0.1', 3)
0.2000061035
尽管 evalf
引擎试图保持 10 位精度(因为这是表示的最高精度),但所使用的 3 位精度将精度限制在约 4 位 - 您看到的并非所有数字都是有效的。evalf 不会试图跟踪有效数字的位数。
这个非常简单的表达式涉及两个具有不同精度的数字相加,希望它能帮助您理解为什么更复杂的表达式(如可能未简化的三角表达式)即使在适当的简化后,也不会评估为精确的零,因为它们应该是零。请考虑这个未简化的三角恒等式,它乘以一个很大的数字。
>>> big = 12345678901234567890
>>> big_trig_identity = big*cos(x)**2 + big*sin(x)**2 - big*1
>>> abs(big_trig_identity.subs(x, .1).n(2)) > 1000
True
当 \(\cos\) 和 \(\sin\) 项以 15 位精度进行评估并乘以该大数时,它们得出了一个仅精确到 15 位(大约)的大数,当从该 20 位大数中减去时,结果不是零。
有三件事可以帮助您获得表达式更精确的数值。
1) 在调用评估时传递所需的替换。通过首先进行替换,
Float
值无法根据需要更新。通过在调用 evalf 时传递所需的替换,可以获得重新评估的必要能力,并且结果令人印象深刻地更好。>>> big_trig_identity.n(2, {x: 0.1}) -0.e-912) 使用有理数而不是浮点数。在评估过程中,有理数可以计算到任意精度,而 Float,一旦创建 - 默认情况下为 15 位 - 则无法。将上面
-1.4e+3
的值与在调用评估之前用表示 1/10 的有理数替换 x 时获得的几乎为零的值进行比较。>>> big_trig_identity.subs(x, S('1/10')).n(2) 0.e-913) 尝试简化表达式。在本例中,SymPy 将识别三角恒等式并将其简化为零,因此您甚至不需要在数值上进行评估。
>>> big_trig_identity.simplify() 0
表达式的不可变性¶
SymPy 中的表达式是不可变的,无法通过就地操作进行修改。这意味着函数将始终返回一个对象,并且原始表达式不会被修改。以下示例代码段演示了它是如何工作的。
def main():
var('x y a b')
expr = 3*x + 4*y
print('original =', expr)
expr_modified = expr.subs({x: a, y: b})
print('modified =', expr_modified)
if __name__ == "__main__":
main()
输出显示 subs()
函数已将变量 x
替换为变量 a
,将变量 y
替换为变量 b
。
original = 3*x + 4*y
modified = 3*a + 4*b
subs()
函数不会修改原始表达式 expr
。相反,它会返回表达式的修改副本。该返回对象存储在变量 expr_modified
中。请注意,与 C/C++ 和其他高级语言不同,Python 不需要您在使用变量之前声明它。
数学运算符¶
SymPy 使用与 Python 相同的默认运算符。其中大多数,如 */+-
,是标准的。除了上面 Python 数字与 SymPy 数字 中讨论的整除之外,您还应该注意不允许隐式乘法。您需要在希望将某个东西乘以另一个东西时使用 *
。此外,要将某个东西提高到幂,使用 **
,而不是 ^
,因为许多计算机代数系统使用 ^
。括号 ()
会按预期改变运算符优先级。
在 isympy 中,使用 ipython shell
>>> 2x
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> 2*x
2*x
>>> (x + 1)^2 # This is not power. Use ** instead.
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for ^: 'Add' and 'int'
>>> (x + 1)**2
(x + 1)**2
>>> pprint(3 - x**(2*x)/(x + 1))
2*x
x
- ----- + 3
x + 1
反三角函数¶
SymPy 使用与大多数计算机代数系统不同的函数名称。特别地,反三角函数使用 python 的名称 asin
、acos
等,而不是通常的 arcsin
和 arccos
。使用上面 符号 中描述的方法查看所有 SymPy 函数的名称。
Sqrt 不是函数¶
与存在指数函数 (exp
) 不同,没有 sqrt
函数。 sqrt(x)
用于表示 Pow(x, S(1)/2)
,因此如果您想知道某个表达式中是否包含任何平方根,expr.has(sqrt)
将不起作用。您必须查找指数为二分之一(或如果在分母中为负二分之一,例如)的 Pow
。
>>> (y + sqrt(x)).find(Wild('w')**S.Half)
{sqrt(x)}
>>> (y + 1/sqrt(x)).find(Wild('w')**-S.Half)
{1/sqrt(x)}
如果您对 sqrt
的任何幂感兴趣,那么以下模式将是合适的。
>>> sq = lambda s: s.is_Pow and s.exp.is_Rational and s.exp.q == 2
>>> (y + sqrt(x)**3).find(sq)
{x**(3/2)}
特殊符号¶
符号 []
、{}
、=
和 ()
在 Python 以及 SymPy 中具有特殊含义。有关更多信息,请参阅上面链接到的 Python 文档。
列表¶
方括号 []
表示一个列表。列表是一个容器,可以容纳任意数量的不同对象。列表可以包含任何内容,包括不同类型的内容。列表是可变的,这意味着您可以在创建列表后更改列表的元素。您还可以使用方括号访问列表的元素,并将它们放在列表或列表变量之后。使用元素之前的空格对元素进行编号。
注意
列表索引从 0 开始。
示例
>>> a = [x, 1] # A simple list of two items
>>> a
[x, 1]
>>> a[0] # This is the first item
x
>>> a[0] = 2 # You can change values of lists after they have been created
>>> print(a)
[2, 1]
>>> print(solve(x**2 + 2*x - 1, x)) # Some functions return lists
[-1 + sqrt(2), -sqrt(2) - 1]
注意
有关列表和方括号表示法以访问列表元素的更多信息,请参阅 Python 文档。
字典¶
花括号 {}
表示字典,或简称 dict。字典是一个无序的非重复键和值列表。语法是 {key: value}
。您可以使用方括号表示法访问键的值。
>>> d = {'a': 1, 'b': 2} # A dictionary.
>>> d
{'a': 1, 'b': 2}
>>> d['a'] # How to access items in a dict
1
>>> roots((x - 1)**2*(x - 2), x) # Some functions return dicts
{1: 2, 2: 1}
>>> # Some SymPy functions return dictionaries. For example,
>>> # roots returns a dictionary of root:multiplicity items.
>>> roots((x - 5)**2*(x + 3), x)
{-3: 1, 5: 2}
>>> # This means that the root -3 occurs once and the root 5 occurs twice.
注意
有关字典的更多信息,请参阅 Python 文档。
元组¶
括号 ()
除了改变运算符优先级及其在函数调用中的使用(如 cos(x)
)之外,还用于元组。 tuple
与 list 相同,只是它不可变。这意味着您无法在创建后更改它们的值。通常情况下,您不需要 SymPy 中的元组,但有时键入括号比键入方括号更方便。
>>> t = (1, 2, x) # Tuples are like lists >>> t (1, 2, x) >>> t[0] 1 >>> t[0] = 4 # Except you cannot change them after they have been created Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: 'tuple' object does not support item assignment单元素元组与列表不同,必须包含一个逗号。
>>> (x,) (x,)如果没有逗号,没有逗号的单个表达式不是元组。
>>> (x) xintegrate 如果您想使用限制进行积分(并且元组或列表都可以)会将一个序列作为第二个参数。
>>> integrate(x**2, (x, 0, 1)) 1/3 >>> integrate(x**2, [x, 0, 1]) 1/3
注意
有关元组的更多信息,请参阅 Python 文档。
关键字参数¶
除了上面 描述 的用法之外,等号 (=
) 还用于向函数提供命名参数。任何在参数列表中具有 key=value
的函数(请参阅下面的关于如何找出这一点的信息),则默认情况下 key
设置为 value
。您可以通过在函数调用中使用等号提供自己的值来更改键的值。此外,参数列表中具有 **
后跟名称的函数(通常是 **kwargs
或 **assumptions
)允许您添加任意数量的 key=value
对,它们都将根据函数进行评估。
sqrt(x**2)
不会自动简化为 x,因为默认情况下 x 被假定为复数,例如sqrt((-1)**2) == sqrt(1) == 1 != -1
>>> sqrt(x**2) sqrt(x**2)向符号提供假设是使用关键字参数的一个示例。
>>> x = Symbol('x', positive=True)平方根现在将简化为 x,因为它知道
x >= 0
。>>> sqrt(x**2) xpowsimp 的默认参数为
combine='all'
>>> pprint(powsimp(x**n*x**m*y**n*y**m)) m + n (x*y)将 combine 设置为默认值与不设置它相同。
>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='all')) m + n (x*y)非默认选项是
'exp'
,它组合指数…>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='exp')) m + n m + n x *y…以及 'base',它组合底数。
>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='base')) m n (x*y) *(x*y)
注意
有关函数参数的更多信息,请参阅 Python 文档。
从 SymPy 内部获取帮助¶
help()¶
虽然所有文档都可以在 docs.sympy.org 或 SymPy Wiki 上找到,但您也可以从运行 SymPy 的 Python 解释器中获取有关函数的信息。最简单的方法是执行 help(function)
,或者如果您使用的是 ipython,则执行 function?
In [1]: help(powsimp) # help() works everywhere
In [2]: # But in ipython, you can also use ?, which is better because it
In [3]: # it gives you more information
In [4]: powsimp?
这些将为您提供 powsimp()
的函数参数和文档字符串。输出将类似于以下内容
- sympy.simplify.simplify.powsimp(expr, deep=False, combine='all', force=False, measure=<function count_ops>)[source]
通过组合具有相似底数和指数的幂来简化表达式。
解释
如果
deep
为True
,则 powsimp() 还将简化函数的参数。默认情况下,deep
设置为False
。如果
force
为True
,则将在不检查假设的情况下组合底数,例如 sqrt(x)*sqrt(y) -> sqrt(x*y),如果 x 和 y 都为负数,则不成立。您可以通过更改 combine=’base’ 或 combine=’exp’ 使 powsimp() 仅组合底数或仅组合指数。默认情况下,combine=’all’,它同时执行两者。combine=’base’ 将仅组合
a a a 2x x x * y => (x*y) as well as things like 2 => 4
而 combine=’exp’ 将仅组合
a b (a + b) x * x => x
combine=’exp’ 将严格地仅以以前自动的方式组合指数。如果需要旧行为,请使用 deep=True。
当 combine=’all’ 时,‘exp’ 首先被评估。请考虑以下第一个示例,了解何时可能存在与之相关的歧义。这样做是为了使像第二个示例这样的内容能够完全组合。如果您希望 ‘base’ 首先组合,请执行类似 powsimp(powsimp(expr, combine=’base’), combine=’exp’) 的操作。
示例
>>> from sympy import powsimp, exp, log, symbols >>> from sympy.abc import x, y, z, n >>> powsimp(x**y*x**z*y**z, combine='all') x**(y + z)*y**z >>> powsimp(x**y*x**z*y**z, combine='exp') x**(y + z)*y**z >>> powsimp(x**y*x**z*y**z, combine='base', force=True) x**y*(x*y)**z
>>> powsimp(x**z*x**y*n**z*n**y, combine='all', force=True) (n*x)**(y + z) >>> powsimp(x**z*x**y*n**z*n**y, combine='exp') n**(y + z)*x**(y + z) >>> powsimp(x**z*x**y*n**z*n**y, combine='base', force=True) (n*x)**y*(n*x)**z
>>> x, y = symbols('x y', positive=True) >>> powsimp(log(exp(x)*exp(y))) log(exp(x)*exp(y)) >>> powsimp(log(exp(x)*exp(y)), deep=True) x + y
如果 combine=’exp’,则具有 Mul 底数的根式将被组合
>>> from sympy import sqrt >>> x, y = symbols('x y')
两个根式会自动通过 Mul 连接
>>> a=sqrt(x*sqrt(y)) >>> a*a**3 == a**4 True
但如果该根式的整数幂已被自动展开,则 Mul 不会连接生成的因子
>>> a**4 # auto expands to a Mul, no longer a Pow x**2*y >>> _*a # so Mul doesn't combine them x**2*y*sqrt(x*sqrt(y)) >>> powsimp(_) # but powsimp will (x*sqrt(y))**(5/2) >>> powsimp(x*y*a) # but won't when doing so would violate assumptions x*y*sqrt(x*sqrt(y))