↓ Skip to main content

Rational Expressions, Exponentials, and Logarithms with SymPy

·1118 words·6 mins·
 Author
Author
Francisco Bustamante
Chemist, data scientist and Python programmer.
Table of Contents
SymPy - This article is part of a series.
Part 3: This Article

In this article we will see how to work with rational expressions, exponentials, and logarithms using Python’s SymPy library.

Importing SymPy and creating some symbols
#

Let’s start by importing SymPy and creating some symbols that we will use throughout the article. If you have any trouble with the library’s concept of symbols, see the first article in the series about SymPy:

import sympy

# setup for better outputs in the article, can be ignored
sympy.init_printing(use_latex='png', scale=1.0, order='grlex',
                    forecolor='Black', backcolor='White',)

x, y, a, b, c, d, n = sympy.symbols('x y a b c d n')

Rational expressions
#

A rational function is any function that can be expressed as a ratio (quotient) of polynomials:

$$ f(x) = \frac{P(x)}{Q(x)} $$

By default, SymPy does not combine or split rational expressions. For example:

a/b + c/d

\(\displaystyle \frac{a}{b} + \frac{c}{d}\)

If we want to “combine” the fractions, which by hand we would do with the least common multiple (LCM) procedure, we use the together method:

sympy.together(_)

\(\displaystyle \frac{a d + b c}{b d}\)

Remember, the underscore (_) is just a shorthand for reusing a result from a previous cell.

Now let’s see the opposite: a situation where we have a rational expression and would like to write it as simpler fractions, called partial fractions. A situation where we usually do this operation is when solving integrals of rational functions. In this case, we use the apart method:

(x**2 + x + 4)/(x + 2)

\(\displaystyle \frac{x^{2} + x + 4}{x + 2}\)

sympy.apart(_)

\(\displaystyle x - 1 + \frac{6}{x + 2}\)

Exponentials and logarithms
#

Logarithms and exponentials show up in the most diverse mathematical problem contexts. So it is important to know how to use them with SymPy, especially the implicit assumptions the package makes, which can give rise to results that are not always what we expect at first.

Starting with the notation, in most programming languages and their libraries, log stands for the natural logarithm, which in written work we usually write as ln. Python and SymPy are no different, but SymPy tries to be friendlier and treats ln = log:

sympy.ln(x)

\(\displaystyle \log{\left(x \right)}\)

Notice that the result shows log(x) even though we wrote ln(x) in the cell. And remember that this is a “courtesy” of SymPy and not of Python itself. For example, don’t try it with the standard math library:

import math

math.ln(10)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

/tmp/ipykernel_16553/2001684872.py in <module>
      1 import math
      2 
----> 3 math.ln(10)


AttributeError: module 'math' has no attribute 'ln'
math.log(10)  # this works, log being the representation of ln. Not to be confused with log10

\(\displaystyle 2.30258509299405\)

math.log10(10)

\(\displaystyle 1.0\)

In more basic math courses, the following identities are taught for logarithms:

$$ \begin{align*} \log (xy) &= \log(x) + \log(y) \\ \log (x^n) &= n \log(x) \end{align*} $$

However, such identities are not valid if x and y are arbitrary complex numbers, because of the branch point (discontinuity) that a logarithm has in the complex plane. Let’s see how SymPy handles such expressions:

sympy.log(x * y)

\(\displaystyle \log{\left(x y \right)}\)

sympy.log(x**n)

\(\displaystyle \log{\left(x^{n} \right)}\)

Notice that SymPy kept the multiplication and the exponentiation. Let’s try to force the expansion with the expand_log method:

sympy.expand_log(sympy.log(x * y))

\(\displaystyle \log{\left(x y \right)}\)

sympy.expand_log(sympy.log(x**n))

\(\displaystyle \log{\left(x^{n} \right)}\)

Notice that it “didn’t work”. Or rather, it worked, because we did not specify any assumptions about x, y, and n. So SymPy makes no implicit assumptions and considers that, in the worst case, they are complex and the expansion cannot be carried out.

Now, expand_log, like other SymPy methods that we will see in other articles of this series, has a parameter called force that, when True, forces the expansion to be carried out:

sympy.expand_log(sympy.log(x * y), force=True)

\(\displaystyle \log{\left(x \right)} + \log{\left(y \right)}\)

sympy.expand_log(sympy.log(x**n), force=True)

\(\displaystyle n \log{\left(x \right)}\)

But in real life, good ideas don’t need to be forced. Or, bringing it to a context closer to programming, let’s remember the Zen of Python:

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Look at the second line: explicit is better than implicit. So, rather than forcing the expansion, it would be better to state the assumptions for each symbol explicitly and, if those assumptions are adequate, the expansion happens naturally.

The identities above are valid as long as x and y are positive and n is a real number. So let’s redefine those symbols, stating those assumptions explicitly:

x, y = sympy.symbols('x y', positive=True)
n = sympy.symbols('n', real=True)

Let’s check whether the expansions are now possible:

sympy.expand_log(sympy.log(x * y))

\(\displaystyle \log{\left(x \right)} + \log{\left(y \right)}\)

sympy.expand_log(sympy.log(x**n))

\(\displaystyle n \log{\left(x \right)}\)

In my opinion, the second form is much better. Whenever you know the assumptions about each symbol (whether it is always positive, negative, real…) it is good practice to state them when creating the symbol.

If the opposite operation, combining, is desired, there is logcombine:

sympy.logcombine(sympy.log(x) + sympy.log(y))

\(\displaystyle \log{\left(x y \right)}\)

sympy.logcombine(n * sympy.log(x))

\(\displaystyle \log{\left(x^{n} \right)}\)

Finally, let’s talk about Euler’s number, the base of the natural logarithm. This number has several definitions:

$$ e \equiv \lim_{n \to \infty} \left(1 + \frac{1}{n}\right)^n \equiv \lim_{x \to 0} \left(1 + x\right)^{\frac{1}{x}} \equiv \sum_{n=0}^{\infty} \frac{1}{n!} $$

In SymPy it is represented by E. Thus, exp(x) is equivalent to E**x:

sympy.E

\(\displaystyle e\)

sympy.E**x

\(\displaystyle e^{x}\)

sympy.exp(x)

\(\displaystyle e^{x}\)

We can get a numerical approximation for E using methods already seen in other articles:

sympy.E.n()

\(\displaystyle 2.71828182845905\)

Let’s check with E that log really represents the natural logarithm:

sympy.log(sympy.E**2)

\(\displaystyle 2\)

But what if we want a logarithm in some other base? Just pass the base as the second argument of the log method:

sympy.log(10**2, 10)

\(\displaystyle 2\)

Conclusion
#

Another article about SymPy. Rational expressions, logarithms, and exponentials show up many times in many concepts, so what we saw in this article will be useful many times in the upcoming articles.

See you next time.

SymPy - This article is part of a series.
Part 3: This Article

Related

Plots with SymPy and Matplotlib

·3611 words·18 mins
In this article we will see how to make plots with SymPy and Matplotlib. When people talk about SymPy, we usually focus on solving mathematical problems. But many forget that the library has some basic features for building plots, which we will see in this article.

Infinite Series - Calculus with Python and SymPy

·8151 words·39 mins
A good part of the study of calculus is devoted to what are called infinite series. But what, exactly, are infinite series? And how are they related to the paradox of Achilles and the tortoise, one of the so-called paradoxes of Zeno of Elea, the Greek philosopher? What is the connection with quantum mechanics and the uncertainty principle? And where does Python come in? That is what we will find out in this article.

Integrals - Calculus with Python and SymPy

We have already written here on the site about the concepts of limit and derivative and how we can do the related computations with Python. Continuing our journey through calculus, in this article we will see how to work with integrals using SymPy and SciPy, Python packages. Along the way, we will pick up some notions of numerical methods.