Thursday, February 23, 2017

Visualizing the patterns in the sets of complex and real roots of quadratic and cubic equations (II)

A former post two years ago  described some methods to visualize the roots of quadratic and cubic equations. In the present post I will show a new method that can be applied to the real roots of quadratic equations based in my recent previous post regarding the use of projections of $4$-dimensional points into $3D$.

Basically, we allocate the points inside a $4D$ tesseract whose center will be the origin of coordinates in the $4$-dimensional space and the reference of the position of the points in the internal space of the tesseract, and then the tesseract and its content is projected into $3D$. The example below shows every real root for $a,b,c \in [-10,10] (a,b,c \in \Bbb Z$):



And below there is a zoom showing only the root tuples. As it can be seen, the pattern generated by the set of tuples shows interesting symmetries in the $4D$ space that can be observed in the $3D$ projection of the set of points (kind of mesmerizing!). The movement is required because to be able to visualize a complete $4D$ object through its projection, we need to rotate around one of the $4D$ axes. By doing so, it is possible to see the $3D$ "shadow" of the positions of the points in the original $4$-dimensional space:


Wednesday, February 8, 2017

Visualizing the four-dimensional symmetries of the gcd and lcm.

This is a variation of my previous post. The following combination of $4$-tuples provides a better insight of the symmetries regarding the gcd:

$$(a,b,gcd(a,b),sgn(gcd(a,b)) \cdot \lfloor \sqrt{\frac{a \cdot b}{|gcd(a,b)|} \rfloor})$$

where $gcd(a,b) \in \Bbb Z$ (instead of using the definition of the gcd as the absolute value, we are using the alternative definition that lets the gcd to be negative when the sign of the elements $(a,b)$ is not positive) and

$$sgn(gcd(a,b)) \cdot \lfloor \sqrt{\frac{a \cdot b}{|gcd(a,b)|} \rfloor}$$

is the floor of the square root of the least common multiple, letting it be negative (sgn is the sign function) if gcd is negative and positive if the sign of the gcd is positive. I am making the square root of the LCM because it is an approximation to the expected values of its biggest possible pair of multiples $x$ and $y$:

$$\{x,y\ /\ x \cdot y = LCM \} \land \{\not\exists\ x',y',\ x' \cdot y' = LCM , x'+y' \gt x+y \}$$

$x$ and $y$ will be located around the square root of the LCM, so is is a good approximation, and they are located inside the range of the tesseract, so they are very useful in terms of representing the relationship of $(a,b)$ and the gcd using $4$-tuples.

It leads to the following results, first an overview of the projection:


And zoomed:


So it seems better to use directly the gcd and a manipulation of the LCM to observe the symmetries of the tuples.

Monday, February 6, 2017

Visualizing the possible symmetries of tuples $(a,b,x,y)$ of the extended Euclidean algorithm in a four-dimensional tesseract.

I am trying to visualize the possible symmetries in the Euclidean four-dimensional space of the $4$-tuples of points $(a,b,x,y)$ generated by the extended Euclidean algorithm, where $ax+by=gcd(a,b)$. There is more than one solution of $(x,y)$ pairs, so I am focusing on visualizing one of the two minimal pairs generated by the algorithm, as it is explained in the Wiki page of the Bézout's identity. This is a mirror of my question at MSE.

1. In the example below, basically what I am doing is generating a single $4$-tuple $(a,b,x,y)$, where $(x,y)$ is one of the two possible minimal pairs that the extended Euclidean algorithm generates for every possible combination of integer pairs $(a,b)$ where in this case $a,b \in [0,50]$.

2. For this reason, $50 \cdot 50=2500$ $4$-tuples are generated. Then the rest of combinations of positive and negative values of $a$ and $b$ were also included $(-a,b)$$(a,-b)$$(-a,-b)$ and the rest of $4$-tuples are calculated in the same way, obtaining the associated $(x,y)$ values as before.

3. Finally $4 \cdot 2500 = 10^4$ $4$-tuples have been generated. This set of $10^4$ four-dimensional tuples then is visualized inside a tesseract, following the methodology of my previous post, having the reference axes at $(0,0,0,0)$, the center of the tesseract, and representing them as points of the Euclidean four-dimensional space.

4. The result is shown as a $3D$ projection of the tesseract (a Schlegel diagram). So basically we are able to view the four-dimensional set of $4$-tuples $(a,b,x,y)$ representing each possible combination of $(a,b)$ as the first two dimensions, and their associated pair $(x,y)$, one of the minimal pairs generated by the Euclidean algorithm, as the last two dimensions. In other words, one single four-dimensional point $(a,b,x,y)$ represents a given pair $(a,b)$ and one of its minimal results of the extended Euclidean algorithm, $(x,y)$ .

5. So here are the results:



This is a zoom showing only the $4$-tuples associated with the **positive** combinations of $a$ and $b$:


And finally, this is the same zoom showing the whole set of results, for any combination of positive and negative values of $a$ and $b$:


Well, in this constellation of points, apart from being kind of "mesmerizing", it is possible to distinguish some basic already known symmetries, due to the inclusion of the tuples $(a,b,x_0,y_0)$ , $(-a,b,x_1,y_1)$ , $(a,-b,x_2,y_2)$ and $(-a,-b,x_3,y_3)$. But, in other hand, as we are visualizing a rotating set of four-dimensional points, it seems that other possible symmetries are there.

In general the methodology of projecting $4D$ into $3D$ is quite interesting: it is one of the closest ways to visualize the four-dimensional problem directly. Sometimes it is not easy to find symmetries when the problem is represented in lower dimensions, so seeing directly the $4$-dimensional system can provide new insights about the set of points being studied.

Thursday, February 2, 2017

How to build a projection (Schlegel diagram) of a tesseract, and show a four-dimensional point inside it

This is an explanation about how to build a projection (Schlegel diagram) of a tesseract, and to show a point inside it (this is a mirror of my question at MSE). Regarding the methodology applied to visualize the 4D point, basically, if we want to show a point inside the tesseract, we need to project the tesseract first, and then project the desired point as well, following the same projection rules.

1. The definition of the tesseract is as follows (please see my post at MSE for the credits):

The tesseract is a four dimensional cube. It has 16 edge points $v=(a,b,c,d)$, with $a,b,c,d$ either equal to $+1$ or $-1$. Two points are connected, if their distance is $2$. Given a projection $P(x,y,z,w)=(x,y,z)$ from four dimensional space to three dimensional space, we can visualize the cube as an object in familiar space. The effect of a linear transformation like a rotation
$$
R(t)=\pmatrix{1&0&0&0\\0&1&0&0&\\0&0&\cos(t)&\sin(t)\\0&0&-\sin(t)&\cos(t)}
$$
in $4D$ space can be visualized in $3D$ by viewing the points $v(t) = P R(t) v$ in $\mathbb R^3$.

2. The definition of the projection (please see my post at MSE for the credits):

$$
P(x, y, z, w) = \frac{h}{h - w}(x, y, z).
$$

3. And finally, the definition of the distance between two four-dimensional points is calculated as follows (this is used to show the edges of the tesseract properly, making lines between the correct projected vertices):

$$d=\sqrt{(x_0-x'_0)^2+(x_1-x'_1)^2+(x_2-x'_2)^2+(x_3-x'_3)^2}$$

4. I have prepared a Python code snippet that creates the frames (jpg) of an animation of a tesseract including an internal point. In this case, the length of the edge is $1000$ so the distance between the vertices is not $2$, but $1000$. For the projection I have used a light source located at three times the length of the edge, this is $h=3000$. Finally, I have applied a rotation as defined above. The star is marking the location of the point $(\frac{3}{4} \cdot \frac{1000}{2}, \frac{3}{4} \cdot \frac{1000}{2}, \frac{3}{4} \cdot \frac{1000}{2}, \frac{3}{4} \cdot \frac{1000}{2})$ while we rotate the tesseract. Be aware that the position of the camera in the animation is lateral. The typical location of the camera is from above, which is the usual view of a square inside a square. But for visualization purposes (we want to see clearly the movement of the projection of the point due to the rotation of the tesseract) the camera in this case was located in a lateral position. Please use and modify it freely (for instance instead of one point it is possible to show a set of points and verify if there is symmetry, etc.):






from math import pi, sin , cos, sqrt
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D

edge_length=1000
edge_half_length= int(edge_length/2)

lotuples=[]
list_of_loxt_lists=[]
list_of_loyt_lists=[]
list_of_lozt_lists=[]

rotation_accuracy=100
filled_once=False
for ratio in range(0,rotation_accuracy):
    angle= ((2*pi)*ratio)/rotation_accuracy
    loxt=[]
    loyt=[]
    lozt=[]
  
    #t=edge_half_length (positive)
    a=-edge_half_length
    b=edge_half_length
    ret0=-edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))
  
    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])
      
    a=-edge_half_length
    b=-edge_half_length
    ret0=-edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=edge_half_length
    ret0=-edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=-edge_half_length
    ret0=-edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=edge_half_length
    ret0=edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=-edge_half_length
    ret0=edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=-edge_half_length
    b=edge_half_length
    ret0=edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=-edge_half_length
    b=-edge_half_length
    ret0=edge_half_length
    ret1=edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    #t=-edge_half_length (negative)
    a=-edge_half_length
    b=edge_half_length
    ret0=-edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=-edge_half_length
    b=-edge_half_length
    ret0=-edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=edge_half_length
    ret0=-edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=-edge_half_length
    ret0=-edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=edge_half_length
    ret0=edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=edge_half_length
    b=-edge_half_length
    ret0=edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=-edge_half_length
    b=edge_half_length
    ret0=edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])

    a=-edge_half_length
    b=-edge_half_length
    ret0=edge_half_length
    ret1=-edge_half_length
    finala=a
    finalb=b
    finalret0=(ret0*cos(angle))+(ret1*sin(angle))
    finalret1=(ret0*(-sin(angle)))+(ret1*cos(angle))  

    light_projection_factor = ((edge_length*3)/((edge_length*3)-(finalret1)))
    loxt.append(light_projection_factor*finala)
    loyt.append(light_projection_factor*finalb)
    lozt.append(light_projection_factor*finalret0)
    if filled_once==False:
        lotuples.append([a,b,ret0,ret1])
        filled_once=True

    list_of_loxt_lists.append(loxt)
    list_of_loyt_lists.append(loyt)
    list_of_lozt_lists.append(lozt)

list_of_loxi_lists=[]
list_of_loyi_lists=[]
list_of_lozi_lists=[]

list_of_loxi_lists_axis=[]
list_of_loyi_lists_axis=[]
list_of_lozi_lists_axis=[]

for ratio in range(0,rotation_accuracy):
    angle= ((2*pi)*ratio)/rotation_accuracy
    loxi=[]
    loyi=[]
    lozi=[]

    finala=int((3/4)*edge_half_length)
    finalb=int((3/4)*edge_half_length)
    finalret0=(int((3/4)*edge_half_length)*cos(angle))+(int((3/4)*edge_half_length)*sin(angle))
    finalret1=(int((3/4)*edge_half_length)*(-sin(angle)))+(int((3/4)*edge_half_length)*cos(angle))

    light_projection_factor = ((edge_length*3)/((edge_length*3)-finalret1))
    loxi.append(light_projection_factor*finala)
    loyi.append(light_projection_factor*finalb)
    lozi.append(light_projection_factor*finalret0)

    list_of_loxi_lists.append(loxi)
    list_of_loyi_lists.append(loyi)
    list_of_lozi_lists.append(lozi)
  
    # Show projection of refence axes BEGIN
    loxi=[]
    loyi=[]
    lozi=[]

    finala_axis=finala
    finalb_axis=0
    finalret0_axis=0
    finalret1_axis=0
  
    light_projection_factor = ((edge_length*3)/((edge_length*3)-finalret1_axis))
    loxi.append(light_projection_factor*finala_axis)
    loyi.append(light_projection_factor*finalb_axis)
    lozi.append(light_projection_factor*finalret0_axis)
  
    finala_axis=0
    finalb_axis=finalb
    finalret0_axis=0
    finalret1_axis=0
  
    light_projection_factor = ((edge_length*3)/((edge_length*3)-finalret1_axis))
    loxi.append(light_projection_factor*finala_axis)
    loyi.append(light_projection_factor*finalb_axis)
    lozi.append(light_projection_factor*finalret0_axis)
  
    finala_axis=0
    finalb_axis=0
    finalret0_axis=finalret0
    finalret1_axis=0
  
    light_projection_factor = ((edge_length*3)/((edge_length*3)-finalret1_axis))
    loxi.append(light_projection_factor*finala_axis)
    loyi.append(light_projection_factor*finalb_axis)
    lozi.append(light_projection_factor*finalret0_axis)
  
    finala_axis=0
    finalb_axis=0
    finalret0_axis=0
    finalret1_axis=finalret1
  
    light_projection_factor = ((edge_length*3)/((edge_length*3)-finalret1_axis))
    loxi.append(light_projection_factor*finala_axis)
    loyi.append(light_projection_factor*finalb_axis)
    lozi.append(light_projection_factor*finalret0_axis)
  
    list_of_loxi_lists_axis.append(loxi)
    list_of_loyi_lists_axis.append(loyi)
    list_of_lozi_lists_axis.append(lozi)
    # Show projection of refence axes END

for ratio in range(0,rotation_accuracy):  
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.view_init(elev=17., azim=-152) 

    for i in range(0,len(lotuples)):
        for j in range(i+1,len(lotuples)):
            distance = int(sqrt(((lotuples[i][0]-lotuples[j][0])**2)+((lotuples[i][1]-lotuples[j][1])**2)+((lotuples[i][2]-lotuples[j][2])**2)+((lotuples[i][3]-lotuples[j][3])**2)))
            if distance<=edge_length:
                ax.plot([list_of_loxt_lists[ratio][i],list_of_loxt_lists[ratio][j]],[list_of_loyt_lists[ratio][i],list_of_loyt_lists[ratio][j]],[list_of_lozt_lists[ratio][i],list_of_lozt_lists[ratio][j]],"r")


        ax.plot([-edge_length],[edge_length],[-edge_length],"w")
        ax.plot([-edge_length],[-edge_length],[-edge_length],"w")
        ax.plot([edge_length],[edge_length],[-edge_length],"w")
        ax.plot([edge_length],[-edge_length],[-edge_length],"w")
        ax.plot([edge_length],[edge_length],[edge_length],"w")
        ax.plot([edge_length],[-edge_length],[edge_length],"w")
        ax.plot([-edge_length],[edge_length],[edge_length],"w")
        ax.plot([-edge_length],[-edge_length],[edge_length],"w")

    ax.plot([list_of_loxi_lists[ratio][0]], [list_of_loyi_lists[ratio][0]], [list_of_lozi_lists[ratio][0]], "r*")

    #projection of refence axes around the point  
    ax.plot([0,list_of_loxi_lists_axis[ratio][0]],[0,list_of_loyi_lists_axis[ratio][0]],[0,list_of_lozi_lists[ratio][0]],"b")
    ax.plot([0,list_of_loxi_lists_axis[ratio][1]],[0,list_of_loyi_lists_axis[ratio][1]],[0,list_of_lozi_lists_axis[ratio][1]],"b")
    ax.plot([0,list_of_loxi_lists_axis[ratio][2]],[0,list_of_loyi_lists_axis[ratio][2]],[0,list_of_lozi_lists_axis[ratio][2]],"b")
    ax.plot([0,list_of_loxi_lists_axis[ratio][3]],[0,list_of_loyi_lists_axis[ratio][3]],[0,list_of_lozi_lists_axis[ratio][3]],"b")
  
    ax.dist=8
    mpl.pyplot.savefig("tesseract_movie_"+str(ratio)+".png")
    print("End ratio "+str(ratio))


The animated gif was generated joining the jpg files with VirtualDub.