Computing Assignment 1

Question 1

We found in Lecture 1 that the trajectory connecting \(A(x_A,0)\) to \(B(0,h)\) with launch speed/angle \((v_0,\alpha)\) must satisfy a quadratic equation in \(X=\tan\alpha\): \[ C X^2 +B X +A = 0, \qquad A= 2h + \frac{gx_A^2}{v_0^2}, \quad B= 2x_A , \quad C= \frac{gx_A^2}{v_0^2} \]
This quadratic equation has in general two roots unless \(B^2 -4AC <0\). This last condition shows that a path can be found only if \(v_0\) exceeds a minimum value: \[ v_0 > v_{0min} = \sqrt{gh} (1+ (1+x_A^2/h^2)^{1/2}) \] Given values of parameters \(x_A\) and \(h\), no path can be found for \(v_0 < v_{0min}\).

Python code: For given values of \((x_A,v_0,h)\) we compute the values of angle \(\alpha\) by solving the quadratic equation in \(X=\tan\alpha\) which guarantees that \(A\) and B are connected. Since 3 values of \(v_0\) are given, we set up an array for \(v_0\). We add a fourth value which corresponds to \(v_{0min}\), below which there is no solution. The fifth value of \(v_0\) is the optimal path. On a graph, we will plot the 5 trajectories and the rectangular obstacle. It is convenient to set up a function which computes the path for given values of \((x_A,v_0,\alpha)\): this functions returns 2 arrays of \((x,y)\) values associated with this path.

def Projectile(X, v, alpha):
    # returns an array of [x, y] values from t=0 to tfinal
    # starting at A(X,0) with initial speed v and launch angle
    # alpha
    t0 = 0
    tfinal = -X/(v*np.cos(alpha))  # final time to reach point B(0,h) starting from
                                    # point A(xA,0)
    t = np.linspace(t0, tfinal, Npt)    
    x =  X + v*t*np.cos(alpha)
    y =  v*t*np.sin(alpha)-0.5*g*t**2
    return [x, y]

For each value of \(v_0\) we solve the quadratic equation for angle \(\alpha\), call function Projectile and use the values returned by this function to plot the path.

We obtain this plot (the yellow path is the optimal path found in Q.2):

Question 2: Q.1 shows that, for given values of \((x_A,h)\), there is a minimum launch speed v0min necessary to reach point \(B\). Also we note that v0min decreases as a function of xA. There is good reason to believe that there will be a minimum value of xA below which the paths requires higher initial speed v0.

Based on these observations, we can build a scheme with two nested loops:

Eventually, as the computations proceed along these 2 loops, there are no longer smaller values of v0. We can get closer to the obstacle, but it takes a larger speed v0 to reach \(B\). The scheme converges very fast to the solution. You can refine your solution by adding more values to the loops.

This gives the following script:

for xA in X:
    v0min=  np.sqrt(g*h*(1+np.sqrt(1.0+xA**2/h**2)))
    v0max= 1.2*v0min
    Vvalues= np.linspace(-v0max, -v0min, Nvvalues, endpoint=False)# array of v0 values
    Vvalues = -Vvalues

    for V0 in reversed(Vvalues):    # v0 loop from v0max to v0min, excluding v0min
                                    # (at v0min, discrim is a very small number, which can
                                    # be <0 due to machine precision)

        A =2*h + g*xA**2/V0**2
        B = 2*xA
        C= g*xA**2/V0**2
        discrim=B**2-4*A*C

        alpha1 = np.arctan(0.5*(-B+np.sqrt(discrim))/C)  # launch angle 1 
        t1_hit= -(d+xA)/np.cos(alpha1)/V0         # corresponding time to reach x=-d
        y1_hit= -0.5*g*t1_hit**2 + V0*t1_hit*np.sin(alpha1)  # height at x=-d

        alpha2 = np.arctan(0.5*(-B-np.sqrt(discrim))/C) # launch angle 2
        t2_hit= -(d+xA)/np.cos(alpha2)/V0    # time to reach x=-d
        y2_hit= -0.5*g*t2_hit**2 + V0*t2_hit*np.sin(alpha2) # height at x=-d
        
        if (y1_hit > h): # test if path #1 is acceptable
            if(V0 < Vmin):  # if acceptable, test if V0 is smaller than Vmin
                Vmin=V0     # if smaller, then this is the new minimum
                Xmin=xA      # record the corresponding value of xA
                AlphaMin= alpha1 # record the corresponding value of alpha
                print('Rectangle Case: xA=',xA,'[m], v0=',V0,'[m/s], alpha=',alpha1*180/np.pi,'degrees')

        if (y2_hit > h):  # repeat for the second path
            if(Vmin>V0):
                Vmin=V0
                Xmin=xA
                AlphaMin=alpha2
                print('Rectangle Case: xA=',xA,'[m], v0=',V0,'[m/s], alpha=',alpha2*180/np.pi,'degrees')

Numerically we find the optimal path is given by \[ \boxed{ x_A = -23.95 \text{ [m]}, \quad v_0= 27.29 \text{ [m/s]}, \quad \alpha= 75.04 \text{ degrees} } \]

The second case proceeds the same way, except for the testing of the paths. To test if a path is acceptable, we need to guarantee that no point \((x,y)\) of the path joining \(A\) to \(B\) satisfies the condition \[ x^2 + (y-h/2)^2 < (h/2)^2 \] In this case, for each value of \((x_A, v_0)\) in the nested loops, we need to make a call to function Projectile and test that the returned \((x,y)\) values are acceptable or not. To do this, we use a function check:

def check(DistanceArray, value): 
    return(all(x > value for x in DistanceArray)) 

where the array DistanceArray contains the values of \(x^2+ (y-h/2)^2\) and the call to check is made with value = (h/2)**2. If the returned value is True, the path is acceptable. This case is more costly to run than the first case. We find the following values for the optimal path \[ \boxed{ x_A = -24.30 \text{ [m]}, \quad v_0= 26.98 \text{ [m/s]}, \quad \alpha= 72.94 \text{ degrees} } \] and the optimal path given by:

The full script is available here.