The nature of $ x^2-2x+3$ depends on the nature of $x$. If $x$ is a variable, then we have for instance:
sage: var('x');
sage: E = x^2 - 2*x + 3
sage: E
x^2 - 2*x + 3
sage: type(E)
<class 'sage.symbolic.expression.Expression'>
sage: f(x) = x^2 - 2*x + 3
sage: f
x |--> x^2 - 2*x + 3
sage: type(f)
<class 'sage.symbolic.expression.Expression'>
sage: type(f(x))
<class 'sage.symbolic.expression.Expression'>
sage: f(f(x))
(x^2 - 2*x + 3)^2 - 2*x^2 + 4*x - 3
sage: type( f(f(x)) )
<class 'sage.symbolic.expression.Expression'>
However, if all computations involve only polynomial (or up so some complexity rational) expressions, then it may be useful to work inside a polynomial ring (or respectively tacitly inside the related fraction field).
Here, we define $x$ to be the transcendent generator of the polynomial ring over the needed field (of coefficients). I will work in the samples below over $\Bbb Q$. In this case:
R.<x> = PolynomialRing(QQ)
f(x) = x^2 - 2*x + 3
type(f)
f(f(x))
type( f(f(x)) )
delivers:
<class 'sage.symbolic.expression.Expression'>
(x^2 - 2*x + 3)^2 - 2*x^2 + 4*x - 3
<class 'sage.symbolic.expression.Expression'>
But we can "do better", work with $f$ as a polynomial. So replace f(x)
which is in sage "something else" by f
, so that f
becomes a polynomial.
R.<x> = PolynomialRing(QQ)
f = x^2 - 2*x + 3
type(f)
f(f(x))
type( f(f(x)) )
This gives:
<class 'sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint'>
x^4 - 4*x^3 + 8*x^2 - 8*x + 6
<class 'sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint'>
Here, sage implements the "universal property of a polynomial", namely the fact that we can plug in inside $f=f(\cdot)=f(x)$. So we can plug in something like $x^2$ instead of $x$, written as f( x^2 )
. Also, we can plug in the polynomial f(x)
itself, so we write f( f(x) )
as above, and we get as a result also a polynomial, an element of the polynomial ring $R=\Bbb Q[x]$. And in this case there is no need for a simplification. For instance:
R.<x> = PolynomialRing(QQ)
f = x^2 - 2*x + 3
g = x + 4
f(g(f(g(f(g(f(g(x))))))))
This is:
x^16 + 48*x^15 + 1120*x^14 + 16800*x^13 + 180900*x^12 + 1480464*x^11
+ 9516352*x^10 + 48983520*x^9 + 204005360*x^8 + 689831040*x^7
+ 1888420352*x^6 + 4144340736*x^5 + 7156320280*x^4 + 9415022880*x^3
+ 8921080320*x^2 + 5457719232*x + 1632644838
(Result was manually adjusted.)
Alternatively, we can build some recursion, to see also the in between results:
def compose(polynomial_list):
h = x
for pol in polynomial_list[::-1]:
h = pol(h)
print(h)
And this function delivers:
sage: compose( [f, f, g, g, f] )
x^2 - 2*x + 3
x^2 - 2*x + 7
x^2 - 2*x + 11
x^4 - 4*x^3 + 24*x^2 - 40*x + 102
x^8 - 8*x^7 + 64*x^6 - 272*x^5 + 1098*x^4 - 2728*x^3 + 6448*x^2 - 8080*x + 10203
Doing the same with bare hands:
sage: f(x)
x^2 - 2*x + 3
sage: g(f(x))
x^2 - 2*x + 7
sage: g(g(f(x)))
x^2 - 2*x + 11
sage: f(g(g(f(x))))
x^4 - 4*x^3 + 24*x^2 - 40*x + 102
sage: f(f(g(g(f(x)))))
x^8 - 8*x^7 + 64*x^6 - 272*x^5 + 1098*x^4 - 2728*x^3 + 6448*x^2 - 8080*x + 10203
Even if the expressions are not polynomial / rational, inserting a simplification in (a rewritten version of) the above code should be easy and lead to a good solution.