How to plot Mappings in python

Viewed 233

I don't know how to plot mappings in python, and by mappings I mean nonlinear mappings, for linear ones there is already GeoGebra that you can use it to visualize them, but I can't find a site or even a python script to visualize nonlinear maps, to understand what I mean by nonlinear maps here is this picture

It is from 3blue1brown, and actually it is a plot of the complex function f(z)=z**2 but you can easily transform it to a map, which is F(x,y)=(x**2-y**2,2*x*y)

So my question is, what libraries should I use to plot these things in python?

2 Answers

You might want to look at Sympy's plotting capabilities. It uses Matplotlib as a backend. It still takes a bit of work since you need explicitly specify each line:

 import sympy as sy

# Create symbolic function Fz = (x + i*y)**2:
x, y, t = sy.symbols("x, y, t", real=True)
z = x + sy.I * y
Fz = sy.expand(z**2)

# Create expressions for the grid lines:
x_lines, y_lines = [], []
for g_ in range(-5, 6):
    Fz_y, Fz_x = Fz.subs({x: g_/2, y: t}), Fz.subs({x: t, y: g_/2})
    x_lines.append((sy.re(Fz_x), sy.im(Fz_x)))
    y_lines.append((sy.re(Fz_y), sy.im(Fz_y)))

# Make the plot:
plt0 = sy.plot_parametric(*(x_lines+y_lines), (t, -5, 5), show=False,
                          xlim=(-3, 3), ylim=(-4, 4), axis_center=(-3, -4),
                          xlabel="Re", ylabel="Im")

# Adapt color for each line separately:
for c_, p_ in enumerate(plt0):
    p_.line_color = 'blue' if c_ < len(x_lines) else 'red'

plt0.show()  # do the drawing

# Access matplotlib backend (works only in interactive IPython shell):
ax = plt0._backend.ax[0]  # get matplotlib axis
ax.grid(True)

It gives:

Plot of F(z) ) z**2

Alternatively, you can use lambify() to convert a Sympy expression to a Numpy function, e.g.,:

Fz_np = sy.lambdify((x, y), Fz)
print(f"Fz(1, 2) = {Fz_np(1, 2)}")

Then you can draw the lines in Matplotlib the traditional way.

NOTE: this is by no means a good solution, I am writing this answer for the purpose of you having an answer at all, no matter how good or bad

My approach is to create an image and treat each pixel as a dot on a graph by plugging in the x coordinate of the pixel into a function that returns a number that is checked against the y coordinate of the pixel. If the result of the function is equal to the y coordinate, the pixel's color is changed. I added a few options to customize the look of the graph. Here is the function (it is only a prototype mean and is not at its potential efficiency, if you like this approach I can improve the code):

from numbers import Number

def draw_graph(image, math_func, *,
               error=10,
               x_scale=1, y_scale=1,
               x_offset=None, y_offset=None,
               color=(0, 255, 0)):
    if x_offset is None:
        x_offset = image.width // 2
    if y_offset is None:
        y_offset = image.height // 2
    pxls = image.load()
    for x in range(image.width):
        for y in range(image.height):
            output = math_func((x - x_offset) / x_scale)
            if isinstance(output, Number):
                if output - error <= (image.height - y - y_offset) / y_scale <= output + error:
                    pxls[x, y] = color
            elif isinstance(output, tuple):
                for out in output:
                    if out - error <= (image.height - y - y_offset) / y_scale <= out + error:
                        pxls[x, y] = color

image is a pillow Image object, math_func is a function that takes in an integer (x) and returns a number or a tuple of numbers (multiple values), error is how close the output of the function has to be to the y coordinate of the pixel in order for it to be colored, and the rest of the arguments are self explanatory. If match_func returns None, the function will ignore it.

Here is sample usage of the function (the images should show themselves at the end of the program):

from PIL import Image
from numbers import Number
from math import sqrt

def third_power(x):
    return 0.01 * x**3

def one_over_x(x):
    return 1 / x if x != 0 else None

def idk(x):
    return sqrt(x*x - 3) if x*x > 3 else None

def draw_graph(image, math_func, *,
               error=10,
               x_scale=1, y_scale=1,
               x_offset=None, y_offset=None,
               color=(0, 255, 0)):
    if x_offset is None:
        x_offset = image.width // 2
    if y_offset is None:
        y_offset = image.height // 2
    pxls = image.load()
    for x in range(image.width):
        for y in range(image.height):
            output = math_func((x - x_offset) / x_scale)
            if isinstance(output, Number):
                if output - error <= (image.height - y - y_offset) / y_scale <= output + error:
                    pxls[x, y] = color
            elif isinstance(output, tuple):
                for out in output:
                    if out - error <= (image.height - y - y_offset) / y_scale <= out + error:
                        pxls[x, y] = color

img1 = Image.new("RGB", (200, 200), "black")

draw_graph(img1, third_power, error=5, x_scale=3)
draw_graph(img1, one_over_x, error=3, x_scale=100, color=(255, 0, 0))
draw_graph(img1, idk, error=1, x_scale=3, y_scale=10, color=(0, 0, 255))
img1.show()

img2 = Image.new("RGB", (2000, 2000), "black")

draw_graph(img2, lambda x: x*x, x_scale=10, y_scale=2)

img2.show()

Please comment if I made a mistake on this answer, it's 3 a.m. and I can't resist a good challenge. I hope you find this answer useful or creative.

Related