A. Parallel projection class

In the second TA session, you saw how to project a 3D curve onto some arbitrary 2D subspace of $\mathbb{R}^3$ (i.e. a plane). As a sidenote, the particular type of projection employed is called parallel projection in the computer graphics community.

Please implement a class that can do this for the general $\mathbb{R}^n$ case. I.e. complete the prototype given below:


In [1]:
import numpy as np
class plane_projector:
    # this class is supposed to have 3 member variables:
    #   n  --- dimension of the space in which the plane resides
    #   e0 --- first unit vector
    #   e1 --- second unit vector, perpendicular to the first
    
    def __init__(self, a, b):
        self.n = a.shape[0]
        a /= np.sqrt(np.sum(a**2))
        b /= np.sqrt(np.sum(b**2))
        if (np.abs(np.sum(a*b)) > 1-0.00001 ):
            print 'Input vectors are colinear: Cannot procede.'
            return None
        e0 = a
        e1 = b - np.sum(e0*b)*e0
        e1 /= np.sqrt(np.sum(e1**2))
        self.e0 = e0
        self.e1 = e1
        print np.linalg.norm(e0), np.linalg.norm(e1), np.dot(e0, e1)
        # extract the dimension of the space from a and b
        # use the Gram-Schmidt procedure
        # (described , for instance, at
        #  https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process)
        # to generate an orthonormal basis in the plane spanned by a and b
        # tell the user if they mess up and a is parallel to b
        return None
    
    def project(self, points):
        assert(len(points.shape) == 2)
        npoints = points.shape[0]
        new_points = np.zeros((npoints, 2))
        new_points[:, 0] = np.sum(self.e0[np.newaxis, :]*points, axis = 1)
        new_points[:, 1] = np.sum(self.e1[np.newaxis, :]*points, axis = 1)
        # points is supposed to be a numpy array of shape (npoints, self.n)
        # new_points is a numpy array of shape (npoints, 2), such that
        # new_points[:, 0] is the array of projections onto self.e0 etc
        return new_points
    
# 10 points for correctly implementing Gram Schmidt
# 10 points if the "project" function actually works as requested
# -5 points if the class doesn't have the member variables
#described in the problem statement

In [3]:
x = np.array([-0.10140993, -0.66638705, -0.69692361, -0.17490933,  0.52686482])
y = np.array([-0.26944688, -0.98211864,  0.71770293,  0.84139943,  0.01431163])
z = np.array([[-0.96761895, -0.78043939, -0.47538903, -0.89429321,  0.47477066],
              [ 0.90838771, -0.29673004, -0.98057486, -0.30778446,  0.16239403],
              [-0.63983888, -0.68272871, -0.02770067, -0.15951433, -0.21897474],
              [ 0.29507606, -0.47014532, -0.73760272, -0.71005766, -0.4911878 ],
              [ 0.61851281,  0.3896185 ,  0.80825637, -0.28661449,  0.61911898],
              [ 0.79736948, -0.1920221 ,  0.43433914,  0.60265086, -0.62849677],
              [-0.81026369,  0.73588741, -0.37720727,  0.60650658, -0.03858237],
              [ 0.08648482, -0.40935854, -0.30262387, -0.15478324, -0.15636942],
              [ 0.75922149,  0.81050563, -0.72987108, -0.24939442,  0.77175411],
              [ 0.28081889, -0.95049644, -0.84901844, -0.70014589, -0.39803296],
              [ 0.93249586,  0.83767033, -0.78844573,  0.91398471,  0.87595811]])

c = plane_projector(x, y)
print c.project(z)


1.0 1.0 0.0
[[ 1.21376244 -0.07003027]
 [ 0.83096972 -0.62872414]
 [ 0.40428286  0.44610931]
 [ 0.59327504 -0.5149137 ]
 [-0.45587953 -0.12267872]
 [-0.61950869  0.53680796]
 [-0.24322649 -0.170477  ]
 [ 0.37557899  0.00995065]
 [ 0.3059245  -1.15416303]
 [ 0.99295451 -0.25523668]
 [ 0.17754062 -0.57546896]]

B. Cube and hypercube

Similarly to the figure of the spring that was generated in the TA session, generate 4 2D projections of a cube. The cube should be centered at the origin, with edges of size $0.9$.

Also generate 4 2D projections of a hypercube centered at the origin with edges of size $0.9$.

small hints: use 4 instances of the class that you generated in A; once you've generated the vertices, you can check whether there's an edge between them by looking at the Euclidian $\mathbb{R}^n$ distance between them; it should not be $0.9 \times \sqrt{2}$ or $0.9 \times \sqrt{3}$ etc.

  • $ 10$ points for getting the coordinates of the vertices correctly
  • $ 15$ points for constructing connections correctly
  • $ 5$ points for generating figure correctly
  • $- 1$ points if 4 separate figures are generated
  • $- 2$ points if only one projection is computed
  • $-10$ points if the connections are computed after projection

Big hint

Problem statement: given a bunch of points in $\mathbb{R}^2$, randomly distributed, find out which are closer than some threshold distance d, and plot segments between these "neighbours".


In [8]:
import numpy as np
%matplotlib nbagg
import matplotlib
matplotlib.rcParams['text.usetex'] = 1
import matplotlib.pyplot as plt

d = 0.2
x = np.random.random((32, 2))

def get_neighbours(points, distance):
    c = set()
    for i in range(points.shape[0]):
        for j in range(points.shape[0]):
            dist = np.sqrt(np.sum((points[i] - points[j])**2))
            if (dist <= distance) and (i != j):
                c.add(frozenset([i, j]))
    return c

c = get_neighbours(x, d)

def plot_segments(a, points, connections):
    for c in connections:
        a.plot(points[list(c), 0], points[list(c), 1], marker = '.')
    return None

ax = plt.figure().add_subplot(111)
plot_segments(ax, x, c)



In [9]:
vertices = np.array([[.45, .45, .45, .45],
                      [-.45, .45, .45, .45],
                      [.45, -.45, .45, .45],
                      [-.45, -.45, .45, .45],
                      [.45, .45, -.45, .45],
                      [-.45, .45, -.45, .45],
                      [.45, -.45, -.45, .45],
                      [-.45, -.45, -.45, .45],
                      [.45, .45, .45, -.45],
                      [-.45, .45, .45, -.45],
                      [.45, -.45, .45, -.45],
                      [-.45, -.45, .45, -.45],
                      [.45, .45, -.45, -.45],
                      [-.45, .45, -.45, -.45],
                      [.45, -.45, -.45, -.45],
                      [-.45, -.45, -.45, -.45]])

fig = plt.figure(figsize=(8,8))

ax = fig.add_subplot(221)
a1 = np.random.uniform(-1,1,size=(4))
b1 = np.random.uniform(-1,1,size=(4))
c1 = plane_projector(a1,b1)
X1 = c1.project(vertices)
neighbors = get_neighbours(vertices, 0.9)
plot_segments(ax, X1, neighbors)
    
ax = fig.add_subplot(222)
a2 = np.random.uniform(-1,1,size=(4))
b2 = np.random.uniform(-1,1,size=(4))
c2 = plane_projector(a2,b2)
X2 = c2.project(vertices)
neighbors = get_neighbours(vertices, 0.9)
plot_segments(ax, X2, neighbors)
    
ax = fig.add_subplot(223)
a3 = np.random.uniform(-1,1,size=(4))
b3 = np.random.uniform(-1,1,size=(4))
c3 = plane_projector(a3,b3)
X3 = c3.project(vertices)
neighbors = get_neighbours(vertices, 0.9)
plot_segments(ax, X3, neighbors)

ax = fig.add_subplot(224)
a4 = np.random.uniform(-1,1,size=(4))
b4 = np.random.uniform(-1,1,size=(4))
c4 = plane_projector(a4,b4)
X4 = c4.project(vertices)
neighbors = get_neighbours(vertices, 0.9)
plot_segments(ax, X4, neighbors)


1.0 1.0 1.11022302463e-16
1.0 1.0 -1.11022302463e-16
1.0 1.0 -5.55111512313e-17
1.0 1.0 -1.38777878078e-16

In [10]:
vertices_cube = np.array([[.45, .45, .45],
              [-.45, .45, .45],
              [.45, -.45, .45],
              [-.45, -.45, .45],
              [.45, .45, -.45],
              [-.45, .45, -.45],
              [.45, -.45, -.45],
              [-.45, -.45, -.45]])              

fig = plt.figure(figsize=(8,8))

ax = fig.add_subplot(221)
a1 = np.random.uniform(-1,1,size=(3))
b1 = np.random.uniform(-1,1,size=(3))
c1 = plane_projector(a1,b1)
X1 = c1.project(vertices_cube)
neighbors = get_neighbours(vertices_cube, 0.9)
plot_segments(ax, X1, neighbors)
    
ax = fig.add_subplot(222)
a2 = np.random.uniform(-1,1,size=(3))
b2 = np.random.uniform(-1,1,size=(3))
c2 = plane_projector(a2,b2)
X2 = c2.project(vertices_cube)
neighbors = get_neighbours(vertices_cube, 0.9)
plot_segments(ax, X2, neighbors)
    
ax = fig.add_subplot(223)
a3 = np.random.uniform(-1,1,size=(3))
b3 = np.random.uniform(-1,1,size=(3))
c3 = plane_projector(a3,b3)
X3 = c3.project(vertices_cube)
neighbors = get_neighbours(vertices_cube, 0.9)
plot_segments(ax, X3, neighbors)

ax = fig.add_subplot(224)
a4 = np.random.uniform(-1,1,size=(3))
b4 = np.random.uniform(-1,1,size=(3))
c4 = plane_projector(a4,b4)
X4 = c4.project(vertices_cube)
neighbors = get_neighbours(vertices_cube, 0.9)
plot_segments(ax, X4, neighbors)


1.0 1.0 1.19695919842e-16
1.0 1.0 3.05311331772e-16
1.0 1.0 9.61036805691e-16
1.0 1.0 5.55111512313e-17

In [ ]: