Lecture 14: Substitution, Expansion, and Factorization
======================================================
Substitution is one of the fundamental tools to work with expressions.
Expansion is the counterpart of factorization.
Substitution
------------
As a first application of :index:`substitution`, we can replace an expression
by a new variable to prevent expansion when we manipulate expression.
Substitution is normally executed in sequence, but expressions are
callable objects and calling an expression by keyword arguments
performs the substitution simultaneously, as required when we want
to permute the variables in an expression.
Knowing the list of operands in an expression is useful.
With string manipulations we can perform a pure syntactical substitution.
Suppose we would like to rewrite \ :math:`(x+y)^2 + \frac{1}{(x+y)^2}`
into \ :math:`\frac{(x+y)^4 + 1}{(x+y)^2}`.
We consider then an expression in ``x`` and ``y``
that we want to bring on the same denominator.
::
var('x,y')
p = (x+y)^2 + 1/(x+y)^2
print(p, 'has type', type(p))
If we want to bring the expression on a :index:`common denominator`,
then we could try the ``factor()`` method.
::
p.factor()
The output is
``(x^4 + 4*x^3*y + 6*x^2*y^2 + 4*x*y^3 + y^4 + 1)/(x + y)^2``.
The ``factor`` expands the numerator, which is not what we want.
To shield ``(x+y)^2`` from expanding,
we will save ``x+y`` into a new variable ``z``.
Then we will :index:`factor` the expression in ``z``.
After this, we substitute ``z`` back to ``x + y``.
::
var('z')
q = p.subs({(x+y): z})
print('after substitution of x+y into z :', q)
fq = q.factor()
print('after factoring :', fq)
qq = fq.subs(z=x+y)
print('after substitution of z into x+y :', qq)
And we see printed as output:
::
after substitution of x+y into z : z^2 + 1/z^2
after factoring : (z^4 + 1)/z^2
after substitution of z into x+y : ((x + y)^4 + 1)/(x + y)^2
To view the expression nicely typeset, we do
::
qq.show()
and then we see \ :math:`\frac{(x+y)^4 + 1}{(x+y)^2}`.
Observe that even when we submit a dictionary as argument
of the ``subs()`` method, the substitution happens in sequence
and not simultaneously. Suppose we want to permute the
variables in an expression. For example, we want to replace
``a`` by ``b``, ``b`` by ``c``, and ``c`` by ``a``.
Then the expression ``a + 2*b + 3*c`` turns into ``b + 2*c + 3*a``
via a :index:`simultaneous substitution`, that is: if the substitution
is executed simultaneously.
The simultaneous substitution of the variables in an expression
is illustrated in :numref:`figsubsim`.
.. _figsubsim:
.. figure:: ./figsubsim.png
:align: center
Simultaneous substitution of the variables in an expression
as done by ``e(a=b, b=c, c=a)`` for ``e = a + 2*b + 3*c``.
The sequential substitution of the variables in an expression
is illustrated in :numref:`figsubseq`.
.. _figsubseq:
.. figure:: ./figsubseq.png
:align: center
Sequential substitution of the variables in an expression,
as done by ``e.subs(a=b).subs(b=c).subs(c=a)`` for ``e = a + 2*b + 3*c``.
Expressions are callable objects and when we evaluate by keyword argument,
then the substitution is executed simultaneously.
::
var('a,b,c')
e = a + 2*b + 3*c
print('the expression :', e)
print('substitution with dictionary argument :', e.subs({a:b, b:c, a:c}))
print('evaluation by keyword arguments :', e(a=b,b=c,c=a))
The result of ``e.subs({a:b, b:c, a:c})`` turns out to be
the same as ``e(a=b, b=c, c=a)`` as those statements both
perform the substitution simultaneously.
Returning to the first application of substitution on p,
if we had tried to replace ``(x+y)^2`` by ``z``, then we
would have noticed that only the numerator changed.
Looking at the operands of ``p`` explains why this is.
::
print(p)
print(p.operands())
and we see that the list of operands is
``[(x + y)^2, (x + y)^(-2)]``.
With string manipulation, we can perform
a pure :index:`syntactical substitution`.
::
s = str(p)
print(s)
t = s.replace('(x + y)^2', 'z')
print(t)
which then shows the *string* ``z + 1/z``.
Knowing the list of operands, we can substitute ``(x+y)^2`` by ``z^2``
and ``(x+y)^(-2)`` by ``z^(-1)``.
::
p.subs({(x+y)^2:z, (x+y)^(-2):z^(-1)})
which will show the *expression* ``z + 1/z``.
Expansion
---------
When we give an expression in factored form,
such as \ :math:`(a + b + c) (x^3 + 9 x + 8)`
then we see that Sage does not expand the expression automatically.
Why not? The main reason is :index:`expression swell`.
::
var('a,b,c,x')
p = (a + b + c)*(x^3 + 9*x + 8)
print(p)
If we want to :index:`expand` the expression,
then we apply the ``expand()`` method.
::
print(p.expand())
The ``expand()`` did too much, it expanded everything and
we have lost the structure of the polynomial in ``x``.
Suppose we want to keep the factor ``(a + b + c)`` intact, what do we do then?
Well, we declare a new variable and substitute the expression
``a + b + c`` to this new variable before calling the ``expand()`` method.
::
var('d')
dp = p.subs({a+b+c:d})
print(dp)
Now we expand and then replace ``d`` with the original ``a + b + c``.
::
edp = dp.expand()
print(edp)
pp = edp.subs({d:(a+b+c)})
print(pp)
and we see
``d*x^3 + 9*d*x + 8*d`` and
``(a + b + c)*x^3 + 9*(a + b + c)*x + 8*a + 8*b + 8*c``.
There is an alternative and shorter way to obtain the above result.
We view ``p`` as a polynomial in ``x`` with coefficients in ``QQ[a,b,c]``.
Converting ``p`` into the ring ``QQ[a,b,c][x]`` is straightforward:
::
q = QQ[a,b,c][x](p)
The outcome of ``print(q)`` is the same as the result
of the above ``print(pp)``.
The conversion of an expression into an element of a polynomial ring
of course only works if the expression is a polynomial.
Factorization
-------------
The opposite to expand is factor.
We distinguish between exact, symbolic, and numeric factorization.
A complete :index:`exact factorization` is only possible if all the roots
are rational numbers.
::
f = factor(x^2 - 1)
print(f, 'has type', type(f))
and we see the expression ``(x + 1)*(x - 1)``.
In a symbolic factorization, we add a :index:`formal root`,
a so-called algebraic number.
For example, if we start with the rational numbers,
then we extend ``QQ`` with a root of an irreducible polynomial,
using the symbol ``a`` in the extended number field ``K``.
::
y = polygen(QQ)
q = y^2 + 2
print(factor(q))
print(q.is_irreducible())
K. = QQ.extension(q)
kq = (K[y])(q)
print(factor(kq))
Then the :index:`symbolic factorization` is ``(x - a) * (x + a)``.
The :index:`numerical factorization` happens always over a complex field.
::
z = polygen(CC)
print(factor(z^2 + 2))
and the numerical (approximate) factorization is
``(x - 1.41421356237310*I) * (x + 1.41421356237310*I)``.
Assignments
-----------
1. Give the Sage commands to
transform \ :math:`(x+y)^2 + \frac{1}{x+y}`
into \ :math:`\frac{(x+y)^3 + 1}{x+y}` and vice versa.
2. Give the Sage commands to
transform \ :math:`x^2 + 2 x + 1 + \frac{1}{x^2 + 2 x + 1}`
into \ :math:`\frac{(x+1)^4 + 1}{(x+1)^2}`
and vice versa.
3. Give the Sage commands to
transform \ :math:`x^3 - x y^2 - y x^2 + y^3 + x^2 - y^2`
into \ :math:`(x^2 - y^2)(x-y+1)`.
4. Give the Sage commands to
transform \ :math:`(x + z^2 + 1)(y - z^2 - 1)`
into \ :math:`x y - x (z^2 + 1) + (z^2 + 1) y - (z^2 + 1)^2`.