3

I have a complex multivariate polynomial in variables (x,y) and I need to obtain a new polynomial in variables (a,b) by setting

x = (a+b) / sqrt(2)
y = -i (a-b) / sqrt(2)

Which you might recognise as a change of variables from/to complex coordinates.

I am aware of the function "compose", but it seems to only replace the "x" variable of my polynomial, is there a way to do multivariate composition?

Thanks, Marco

  • 2
    I don't understand your question. Can you not just use `subs` for this? – Oscar Benjamin Apr 16 '21 at 22:59
  • 1
    which is defined here: https://github.com/sympy/sympy/blob/cb57534f1d4d5dfc4c887ce4ffba608a5b04e949/sympy/core/basic.py#L764-L876 – 0 _ Apr 16 '21 at 23:04
  • Relevant also to: https://stackoverflow.com/a/32930285/1959808 – 0 _ Apr 17 '21 at 15:48
  • Regarding substitution of general subexpressions using the method `subs`, the following is useful: https://stackoverflow.com/a/45383358/1959808 – 0 _ Apr 17 '21 at 16:11

2 Answers2

4

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.)

0 _
  • 10,524
  • 11
  • 77
  • 109
  • Thanks! However I am still having issues with this change of variables. The new polynomial has the incorrect generators: they are not a and b, but some other expressions. This is problematic because, by calling "monoms()" I obtain a single degree zero monomial, whose "coefficient" is the whole polynomial. Effectively, it thinks that the polynomial is over C[a,b] – Marco Bellan Apr 17 '21 at 15:28
  • Good point, I updated the answer to avoid this. I assume that by "as symbols" you mean the polynomial's generators, because `assert new_poly.free_symbols == {sympy.Symbol('a'), sympy.Symbol('b')}` passes, whereas indeed `str(new_poly.gens)` is `'(sqrt(2)*(a + b)/2, -sqrt(2)*I*(a - b)/2)'` (the expressions from the change of variables, as you remarked). – 0 _ Apr 17 '21 at 15:36
  • In my first version of the answer, I was actually constructing expressions, which do not have a `monoms` method. I assume that by ".monoms()" you mean the method `sympy.polys.polytools.Poly.monoms`. So a conversion to polynomials is needed. Working with expressions, and converting the final result to a polynomial, the generators are as expected. Only working with polynomials directly yields an unexpected result, which then necessitates converting to an expression and back to a polynomial. – 0 _ Apr 17 '21 at 15:37
  • For the polynomial `x**2 + y**2`, I do not get an empty `list` (the updated answer has more details), though indeed `monoms` does not return what would be expected by comparing to constructing the same polynomial from scratch. I do not know whether the unexpected representation of the resulting polynomial in terms of generators, monomials, and coefficients could be considered a bug in the package `sympy`. Also, I assume you mean `poly = x**2 + y**2` (writing `x^2` raises `TypeError: unsupported operand type(s) for ^: 'Symbol' and 'int'`). – 0 _ Apr 17 '21 at 15:37
  • Thanks, I will check this out in a minute. Yes, I meant the proper python syntax. I'm jumping back and forward between Latex and Python, so my notation is a bit messy! – Marco Bellan Apr 17 '21 at 15:40
0

Perhaps you mean that you want to rewrite a and b as functions of x and y?

from sympy.abc import a,b,x,y
from sympy import I, sqrt, solve
eqs = [
x - (a+b) / sqrt(2),
y - -I* (a-b) / sqrt(2)]
>>> solve(eqs,(a,b))
{a: sqrt(2)*x/2 + sqrt(2)*I*y/2, b: sqrt(2)*x/2 - sqrt(2)*I*y/2}
smichr
  • 16,948
  • 2
  • 27
  • 34