As pointed out in the comments, simultaneous substitution is possible using the method sympy.core.basic.Basic.subs
, for example:
import sympy
a, b, x, y = sympy.symbols('a b x y')
poly = sympy.poly(x + y)
let = {x: (a + b) / sympy.sqrt(2), y: - sympy.I * (a - b) / sympy.sqrt(2)}
new_poly = poly.subs(let, simultaneous=True)
# at this point, `new_poly` is:
# Poly(sqrt(2)*a/2 - sqrt(2)*I*a/2 + sqrt(2)*b/2 + sqrt(2)*I*b/2,
# sqrt(2)*(a + b)/2, -sqrt(2)*I*(a - b)/2, domain='EX')
# so the following assertions pass
assert str(new_poly.gens) == '(sqrt(2)*(a + b)/2, -sqrt(2)*I*(a - b)/2)', (
new_poly.gens)
assert new_poly.monoms() == [(0, 0)], new_poly.monoms()
# worth remarking that
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
# by converting from an instance of the class `sympy.polys.polytools.Poly`
# to an instance of the class `sympy.core.expr.Expr`, and
# back to an instance of the class `sympy.polys.polytools.Poly`,
# the generators become as expected
new_poly = new_poly.as_expr().as_poly()
# the previous statement is equivalent, as far as I know,
# to the statement:
# `new_poly = sympy.poly(new_poly.as_expr())`
# However, the latter raises an exception for some expressions
# (for an example, see below).
# The exception is avoided by using the expression method `as_poly`.
assert str(new_poly.gens) == '(a, b, sqrt(2))', new_poly.gens
assert new_poly.monoms() == [(1, 0, 1), (0, 1, 1)], new_poly.monoms()
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
print(f'polynomial: {new_poly}')
print(f'generators: {new_poly.gens}')
print(f'monomials: {new_poly.monoms()}')
print(f'coefficients: {new_poly.coeffs()}')
which prints:
polynomial: Poly((1/2 - I/2)*a*(sqrt(2)) + (1/2 + I/2)*b*(sqrt(2)), a, b, sqrt(2), domain='QQ_I')
generators: (a, b, sqrt(2))
monomials: [(1, 0, 1), (0, 1, 1)]
coefficients: [1/2 - I/2, 1/2 + I/2]
and ensures, as requested in the comments, that the resulting polynomial have as generators the expressions we would expect if we constructed the new polynomial from scratch, i.e.,
import sympy
a, b = sympy.symbols('a b')
expr = (
(1/2 - sympy.I/2) * a * (sympy.sqrt(2))
+ (1/2 + sympy.I/2) * b *(sympy.sqrt(2)))
new_poly = expr.as_poly()
assert str(new_poly.gens) == '(a, b, sqrt(2))', new_poly.gens
In fact, the polynomial obtained in this way is equal but slightly differently represented, specifically with decimals instead of fractions, i.e., new_poly
in this case is Poly((0.5 - I/2)*a*(sqrt(2)) + (0.5 + I/2)*b*(sqrt(2)), a, b, sqrt(2), domain='EX')
.
It is worth noting that the above conversion from a polynomial to an expression and back to a polynomial is needed only if poly
is an instance of the class sympy.polys.polytools.Poly
, as it is above.
Working with expressions avoids the need to convert the final result from a polynomial to an expression and back to a polynomial. Instead, a conversion from expression to polynomial suffices, as follows (the conversion to polynomial is needed in case one is interested to call the method monoms
, because expressions are instances of the class sympy.core.expr.Expr
, which does not have a method monoms
):
import sympy
a, b, x, y = sympy.symbols('a b x y')
poly = x + y
let = {x: (a + b) / sympy.sqrt(2), y: - sympy.I * (a - b) / sympy.sqrt(2)}
new_poly = poly.subs(let, simultaneous=True)
# at this point, `new_poly` is an instance of the
# class `sympy.core.expr.Expr`,
# so it does not have methods `monoms` and `gens`,
# thus a conversion to polynomial is needed.
# This conversion creates the expected generators, monomials,
# and coefficients, as follows.
new_poly = new_poly.as_expr().as_poly()
assert str(new_poly.gens) == '(a, b, sqrt(2))', new_poly.gens
assert new_poly.monoms() == [(1, 0, 1), (0, 1, 1)], new_poly.monoms()
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
print(f'polynomial: {new_poly}')
print(f'generators: {new_poly.gens}')
print(f'monomials: {new_poly.monoms()}')
print(f'coefficients: {new_poly.coeffs()}')
This block of code prints the same output as the first block of code.
And repeating the above two approaches to the polynomial of interest x**2 + y**2
(this polynomial was noted in a comment):
import sympy
a, b, x, y = sympy.symbols('a b x y')
poly = sympy.poly(x**2 + y**2)
let = {x: (a + b) / sympy.sqrt(2), y: - sympy.I * (a - b) / sympy.sqrt(2)}
new_poly = poly.subs(let, simultaneous=True)
# at this point, `new_poly` is:
# Poly(2*a*b, sqrt(2)*(a + b)/2, -sqrt(2)*I*(a - b)/2, domain='ZZ[a,b]')
# so the following assertions pass
assert str(new_poly.gens) == '(sqrt(2)*(a + b)/2, -sqrt(2)*I*(a - b)/2)', (
new_poly.gens)
assert new_poly.monoms() == [(0, 0)], new_poly.monoms()
# worth remarking that
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
# by converting from an instance of the class `sympy.polys.polytools.Poly`
# to an instance of the class `sympy.core.expr.Expr`, and
# back to an instance of the class `sympy.polys.polytools.Poly`,
# the generators become as expected
new_poly = new_poly.as_expr().as_poly()
assert str(new_poly.gens) == '(a, b)', new_poly.gens
assert new_poly.monoms() == [(1, 1)], new_poly.monoms()
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
print(f'polynomial: {new_poly}')
print(f'generators: {new_poly.gens}')
print(f'monomials: {new_poly.monoms()}')
print(f'coefficients: {new_poly.coeffs()}')
which prints the output:
polynomial: Poly(2*a*b, a, b, domain='ZZ')
generators: (a, b)
monomials: [(1, 1)]
coefficients: [2]
and the code block:
import sympy
a, b, x, y = sympy.symbols('a b x y')
poly = x**2 + y**2
let = {x: (a + b) / sympy.sqrt(2), y: - sympy.I * (a - b) / sympy.sqrt(2)}
new_poly = poly.subs(let, simultaneous=True)
# at this point, `new_poly` is an instance of the
# class `sympy.core.expr.Expr`,
# so it does not have methods `monoms` and `gens`,
# thus a conversion to polynomial is needed.
# This conversion creates the expected generators, monomials,
# and coefficients, as follows.
new_poly = new_poly.as_expr().as_poly()
# if the previous statement is replaced with the statement:
# `new_poly = sympy.poly(new_poly.as_expr())`
# then an exception is raised:
# `CoercionFailed: expected an integer, got 1/2`
assert str(new_poly.gens) == '(a, b)', new_poly.gens
assert new_poly.monoms() == [(1, 1)], new_poly.monoms()
assert new_poly.free_symbols == {a, b}, new_poly.free_symbols
print(f'polynomial: {new_poly}')
print(f'generators: {new_poly.gens}')
print(f'monomials: {new_poly.monoms()}')
print(f'coefficients: {new_poly.coeffs()}')
which prints the same output as the previous code block.
Additional documentation about the method subs
can be found in the sympy
documentation (even though there the discussion is in terms of expression instances, it applies also to instances of polynomials, because both subclass the class Basic
without overriding the method subs
).
Note also that substitution of subexpressions that are not identifiers works roughly by searching the syntax tree of the expression for subtrees that match the syntax tree of the subexpressions to be substituted, and replacing the subtrees. There is a caveat about some heuristics though, as described in this answer, and in the docstring of the method sympy.core.basic.Basic.subs
.
(Also, a multiplication sign appears to be missing in the question.)