如何利用SymPy实现两个函数的公共偏应用分解?
用SymPy提取双表达式公共子结构并重构函数
问题背景
给定两个SymPy表达式$f(a,b,c)$和$g(a,b,d)$,需要找到函数$h(a,b)$、$f'(x,c)$、$g'(x,d)$,满足:
$$f'(h(a,b),c) = f(a,b,c)$$
$$g'(h(a,b),d) = g(a,b,d)$$
举个具体例子:
from sympy import symbols, sin a,b,c,d = symbols('a b c d') eq1 = a - b + c eq2 = sin(2*a - 2*b)*d # 期望得到: # h(a,b) = a-b # f'(x,c) = x + c # g'(x,d) = sin(2x)*d
SymPy没有直接内置该功能,这个问题本质是提取两个表达式中指定变量组的公共子表达式,再将原表达式重构为以该子式为新变量的函数。
实现思路与代码
核心步骤分为两步:
- 提取两个表达式中仅包含目标变量(这里是{a,b})的公共子表达式,选取最复杂的那个作为$h(a,b)$(避免匹配单个变量这类无意义的公共子式)
- 用新变量替换原表达式中的$h(a,b)$,得到$f'$和$g'$
以下是实现代码:
from sympy import symbols, sin, replace from sympy.core.expr import Expr a,b,c,d,x = symbols('a b c d x') def common_partial(expr1: Expr, expr2: Expr, shared_vars: set): # 递归提取表达式中仅包含指定变量的所有子式 def extract_subexprs(expr, target_vars): subexprs = set() # 处理当前表达式的所有子节点 for arg in expr.args: if all(var in target_vars for var in arg.free_symbols): subexprs.add(arg) subexprs.update(extract_subexprs(arg, target_vars)) # 处理表达式本身 if all(var in target_vars for var in expr.free_symbols): subexprs.add(expr) return subexprs # 获取两个表达式的目标变量子式集合 subexprs1 = extract_subexprs(expr1, shared_vars) subexprs2 = extract_subexprs(expr2, shared_vars) common_subexprs = subexprs1 & subexprs2 if not common_subexprs: raise ValueError("未找到指定变量组中的公共子表达式") # 选取最复杂的公共子式作为h(a,b)(用表达式结构复杂度衡量) h = max(common_subexprs, key=lambda e: len(e.args) + len(str(e))) # 替换得到f'和g' f_prime = expr1.replace(h, x) g_prime = expr2.replace(h, x) return h, f_prime, g_prime # 测试示例 eq1 = a - b + c eq2 = sin(2*a - 2*b)*d h, f_p, g_p = common_partial(eq1, eq2, {a, b}) print(f"h(a,b) = {h}") print(f"f'(x,c) = {f_p}") print(f"g'(x,d) = {g_p}")
注意事项
- 对于含因式的表达式(如示例中的
2a-2b),可以先对表达式执行factor()处理,让公共子式更明显(比如2*(a-b)),这样能匹配到更简洁的$h(a,b)$ - 如果存在多个公共子式,当前逻辑会选取结构最复杂的那个,符合多数场景的需求
- 对于嵌套层级较深的表达式,递归提取逻辑可以覆盖大部分常规情况,特殊场景可能需要调整子式匹配规则
内容的提问来源于stack exchange,提问作者Abogical
相关产品推荐
相关产品推荐

