<jupyter_start><jupyter_text>Fun with algebra  Preamble In this notebook, we will learn more about some keywords (function) in `sympy`, specific to common algebraic manipulations.  We begin with importing `sympy` and initializing the use of LaTex printing to the screen.<jupyter_code>from sympy import init_printing
init_printing()<jupyter_output><empty_output><jupyter_text>Simplifying algebraic expressions The `sympy` package has a general function called `simplify()` which is great at simplifying algebraic expressions.<jupyter_code>from sympy import symbols, simplify
x, y = symbols('x y')  # Creating more than one symbol<jupyter_output><empty_output><jupyter_text>Let's simplify expression (1). $$\frac{x^3 + x^2 - x + 1}{x^2 + 2x + 1} \tag{1}$$<jupyter_code>simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1))<jupyter_output><empty_output><jupyter_text>We can factor both the numerator and the denominator using the `.factor()` method on each of the expresssions.<jupyter_code>(x**3 + x**2 - x - 1).factor()  # Factoring the numerator
(x**2 + 2*x + 1).factor()  # Factoring the denominator<jupyter_output><empty_output><jupyter_text>Now it's easy to see the result of the simplification as we ${\left( x+1 \right)}^2$ in both the numerator and the denominator. Note that `simplify()` is a relatively slow function as it _tries_ many possible solutions.  When appropriate rather factor or expand.  We have already seen the power of the `factor()` and `expand()` functions and methods.  Collect The `collect()` function collects common powers.  Expression (2) has three mathematical variables with the $x$ variable being of varying degree. $$x y + x - 3 + 2 x^2 - z x^2 + x^3 \tag{2}$$ We have to set `z` as a mathematical variable too.<jupyter_code>z = symbols('z')  # Setting z as a mathematical variable<jupyter_output><empty_output><jupyter_text>The `expr` computer variable is overwritten to contain expression (2).<jupyter_code>expr = x * y - 2 + 2 * x**2 - z * x**2 + x**3
expr<jupyter_output><empty_output><jupyter_text>The `collect()` function is imported from `sympy`.<jupyter_code>from sympy import collect<jupyter_output><empty_output><jupyter_text>Now we use the `collect()` function with two arguments.  The first is the computer variable that contains expression (2) and the second is the mathematical variable of interest.<jupyter_code>collect(expr, x)<jupyter_output><empty_output><jupyter_text>The results is an expression with descending powers of $x$.  Cancel The `cancel()` function puts a rational function in canonical form, i.e. $\frac{p}{q}$.  We will import the function and use it for expression (3).<jupyter_code>((x-3)*(x+2)*(x-4)).expand()
((x-1)*(x-3)).expand()<jupyter_output><empty_output><jupyter_text>$$\frac{x^3 - 5x^2 - 2x +24}{x^2 - 4x + 3} \tag{3}$$<jupyter_code>from sympy import cancel
# Instead of first creating a computer variable we can pass the expression directly as an argument
cancel(((x - 3) * (x + 2) * (x - 4)) / ((x - 1)*(x - 3)))<jupyter_output><empty_output><jupyter_text>The `.factor()` method will accomplish the same thing, other than also factoring the polynomials.<jupyter_code>(((x - 3) * (x + 2) * (x - 4)) / ((x - 1)*(x - 3))).factor()<jupyter_output><empty_output><jupyter_text>Apart The `apart()` function is especially helpful.  It calculates partial fraction decomposition.  This can take a long time if done by hand.  Take a look at expression (4) and then its (one possible) partial fraction decomposition. $$\frac{3x^4 + 5x^3 - 3x^2 + 3x}{x^5 + 3x^4 - 9x - 27} \tag{4}$$<jupyter_code>from sympy import apart
apart((1 / (x + 3)) + (x / (x**2 - 3)) + ((x - 1) / (x**2 + 3)))<jupyter_output><empty_output><jupyter_text>Trigonometric functions The `sympy` package can both simplify and expand trigonometric function.  One of the most common trigonometric identity is shown in equation (5). $${\left( \sin{\theta} \right)}^2 + {\left( \cos{\theta} \right)}^2 = 1 \tag{5}$$ First we have to learn how to create the mathematical variable $\theta$.  This is done using the same `symbol()` function that we are now familiar with.  First we have to create a computer variable to hold the symbol, just as we did with the mathematical symbol $x$ being held in the computer variable `x`, i.e. `x = symbols('x')`.  It is common just to use the written word for the Greek symbols. The `symbols()` function recognizes special characters.  The Greek alphabet is included and hence we use the string `'theta'` as argument.<jupyter_code>theta = symbols('theta')
theta<jupyter_output><empty_output><jupyter_text>To show this identity in action, we use the `trigsimp()` function.<jupyter_code>from sympy import trigsimp, sin, cos
# Expression
(sin(theta))**2 + (cos(theta))**2
# Trigonometric simplification
trigsimp((sin(theta))**2 + (cos(theta))**2)<jupyter_output><empty_output><jupyter_text>Trigonometric expansion is just as easy with `expand_trig()`.  We will do this for the trigonometric identity in (6). $$\sin( \theta + \phi ) = \sin(\phi) \cos(\theta) + \sin(\theta) \cos(\phi) \tag{6}$$<jupyter_code># Creating phi
phi = symbols('phi')
phi
from sympy import expand_trig
expand_trig(sin(theta + phi))<jupyter_output><empty_output><jupyter_text>Powers The common properties of powers do not always hold.  In (7) below, are the common (non-exclusive) conditions for the properties of powers. $$\begin{align}&x^a x^b = x^{a+b}, \forall a, b, x \in \mathbb{C} \\&x^a y^a = \left( xy \right)^a, \forall x, y > 0, \forall a \in \mathbb{R} \\&\left( x^a \right)^b = x^{ab}, \forall b \in \mathbb{Z} \end{align}  \tag{7}$$ The `sympy` package will only calculate powers when they are true in general.  By default all mathematical symbols in `sympy` are complex.  When mathematical symbols are declared with the `symbols()` function, we can specify their type, i.e. real or positive.<jupyter_code># Specifying the type of values that a mathematical symbol can hold
x, y = symbols('x y',
              positive = True)
a, b = symbols('a b',
              real = True)<jupyter_output><empty_output><jupyter_text>Now we can use these mathematical symbols with the `powersimp()` function.  Below we import the function and look at the power properties.<jupyter_code>from sympy import powsimp
expr = x**a * x**b  # Overwriting the computer variable expr
expr
powsimp(expr)
expr = x**a * y**a  # Overwriting the computer variable expr
expr
powsimp(expr)
expr = (x**a)**b  # Overwriting the computer variable expr
expr  # Automatically simplified
powsimp(expr)<jupyter_output><empty_output><jupyter_text>When the the complex number values that variables can hold does not allow for the use of the properties of powers, the simplification is not performed.<jupyter_code>m, n, c = symbols(' m n c')
expr = m**c * n**c  # Overwriting the computer variable expr
expr
powsimp(expr)<jupyter_output><empty_output><jupyter_text>An example of this might be $m=n=-1, a = \frac{1}{2}$.$$\begin{align} \left( -1 \right)^{\frac{1}{2}}\left( -1 \right)^{\frac{1}{2}} &= \left( -1 \times -1 \right)^{\frac{1}{1}} \\ i \cdot i &= 1^{\frac{1}{2}} \\ -1 &\ne \pm 1 \end{align}$$ The argument `force = True` can be added to the `powsimp()` function irrespective of having defined the type of mathematical variable.  This is useful if you know that you want to make the simplification an understand the mathematical constraints.<jupyter_code>powsimp(expr,
       force = True)<jupyter_output><empty_output><jupyter_text>Just as an aside, the square root function, `sqrt(x)` in `sympy` is actuall shorthand syntax for `x**Rational(1,2)`.<jupyter_code>from sympy import sqrt, Rational
sqrt(x) == x**Rational(1, 2)<jupyter_output><empty_output><jupyter_text>The `expand_power_exp()` and the `expand_power_base()` functions expand power expressions.<jupyter_code>from sympy import expand_power_exp, expand_power_base<jupyter_output><empty_output><jupyter_text>In expression (8) and (9) we expand using the properties of powers.  If these functions fail to expand the expression that you are working on, remember the mathematical constraints  or use the `force = True` argument. $$x^{ a + b} \tag{8}$$<jupyter_code>expand_power_exp(x**(a + b))<jupyter_output><empty_output><jupyter_text>$$ \left( xy \right)^{a} \tag{9}$$<jupyter_code>expand_power_base((x*y)**a)<jupyter_output><empty_output><jupyter_text>Logarithms The `log()` function in `sympy` calculates the natural logarithm by default.  The latter uses Euler's number as base.  In general the properties shown in (10) below will hold for all positive real numbers and real exponents. $$\begin{align} &\log{ x y } = \log{x} + \log{y} \\ &\log{x^n} = n \log{x}, \forall x, y \in \mathbb{R}, > 0 \end{align} \tag{10}$$<jupyter_code># Ovewrtiting the computer variable that hold the mathematical variables
# Specifying the values that each can hold
x, y = symbols('x y',
               positive = True)
n = symbols('n',
           real = True)<jupyter_output><empty_output><jupyter_text>The `expand_log()` will expand expression according to the properties of logarithms given in (10) above.<jupyter_code>from sympy import expand_log, log
expand_log(log(x * y))
expand_log(x**2)<jupyter_output><empty_output><jupyter_text>Division of two positive real numbers, $\frac{x}{y}$, is simply a special case. $$\begin{align} \log{\frac{x}{y}} &= \log{x \frac{1}{y}} \\ &= \log{x y^{-1}} \\ &= \log{x} + \log{y^{-1}} \\ &= \log{x} - \log{y} \end{align}$$<jupyter_code>expand_log(log(x / y))<jupyter_output><empty_output><jupyter_text>The `logcombine()` function will simplify our expression according to the properties in (10).<jupyter_code>from sympy import logcombine
logcombine(log(x) + log(y))
logcombine(n * log(x))<jupyter_output><empty_output><jupyter_text>Binomial The binomial coefficient function, `binomial()` calculates the number of ways to choose $k$ items from a set of $n$ distinct items, where order does not matter.<jupyter_code>from sympy import binomial<jupyter_output><empty_output><jupyter_text>We choose natural numbers as the binomial function pertains to discrete objects.<jupyter_code>n, k = symbols('n k', positive = True, integer = True)
binomial(n, k)<jupyter_output><empty_output><jupyter_text>We can substitute values using the `.evalf()` method.<jupyter_code>binomial(n, k).evalf(subs = {n:4, k:2})<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13341078516797367, 'fraction_numerical': 0.013457256268757866, 'mean_word_length': 5.337423312883436, 'pattern_counts': {'":': 0, '<': 121, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '30', 'dir': 'jupyter-structured-clean-dedup', 'id': '250249', 'license': 'MIT', 'n_tokens_mistral': 3475, 'n_tokens_neox': 3294, 'n_words': 1159, 'path': 'Python/3. Computational Sciences and Mathematics/Linear Algebra/0.0 Start Here/0.2 Algebraic Operations Activity.ipynb', 'repo_name': 'okara83/Becoming-a-Data-Scientist', 'size': '10329'}