Search code examples
pythonpython-3.xdifferential-equationsscientific-computing

Implementing initial conditions for a numerically solved differential equation


Imagine someone jumping off a balcony under a certain angle theta and velocity v0 (the height of the balcony is denoted as ystar). Looking at this problem in 2D and considering drag you get a system of differential equations which can be solved with a Runge-Kutta method (I choose explicit-midpoint, not sure what the butcher tableu for this one is). I implemented this and it works perfectly fine, for some given initial conditions I get the trajectory of the moving particle.

My problem is that I want to fix two of the initial conditions (starting point on the x-axis is zero and on the y-axis is ystar) and make sure that the trajectory goes trough a certain point on the x-axis (let's call it xstar). For this of course exist multiple combinations of the other two initial conditions, which in this case are the velocities in the x- and y-direction. The problem is that I don't know how to implement that.

The code that I used to solve the problem up to this point:

1) Implementation of the Runge-Kutta method

import numpy as np 
import matplotlib.pyplot as plt

def integrate(methode_step, rhs, y0, T, N):
    star = (int(N+1),y0.size)
    y= np.empty(star)
    t0, dt = 0, 1.* T/N
    y[0,...] = y0
    for i in range(0,int(N)):
        y[i+1,...]=methode_step(rhs,y[i,...], t0+i*dt, dt)
    t = np.arange(N+1) * dt
    return t,y

def explicit_midpoint_step(rhs, y0, t0, dt):
    return y0 + dt * rhs(t0+0.5*dt,y0+0.5*dt*rhs(t0,y0))

def explicit_midpoint(rhs,y0,T,N):
    return integrate(explicit_midpoint_step,rhs,y0,T,N)

2) Implementation of the right-hand-side of the differential equation and the nessecery parameters

A = 1.9/2.
cw = 0.78
rho = 1.293
g = 9.81

# Mass and referece length
l = 1.95
m = 118

# Position
xstar = 8*l
ystar = 4*l

def rhs(t,y):
    lam = cw * A * rho /(2 * m)
    return np.array([y[1],-lam*y[1]*np.sqrt(y[1]**2+y[3]**2),y[3],-lam*y[3]*np.sqrt(y[1]**2+y[3]**2)-g])

3) solving the problem with it

# Parametrize the two dimensional velocity with an angle theta and speed v0

v0 = 30
theta = np.pi/6

v0x = v0 * np.cos(theta)
v0y = v0 * np.sin(theta)

# Initial condintions
z0 = np.array([0, v0x, ystar, v0y])

# Calculate solution
t, z = explicit_midpoint(rhs, z0, 5, 1000)

4) Visualization

plt.figure()
plt.plot(0,ystar,"ro")
plt.plot(x,0,"ro")
plt.plot(z[:,0],z[:,1])
plt.grid(True)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()

To make the question concrete: With this set up in mind, how do I find all possible combinations of v0 and theta such that z[some_element,0]==xstar

I tried of course some things, mainly the brute force method of fixing theta and then trying out all the possible velocities (in an intervall that makes sense) but finally didn't know how to compare the resulting arrays with the desired result...

Since this is mainly a coding issue I hope stack overflow is the right place to ask for help...

EDIT: As requested here is my try to solve the problem (replacing 3) and 4) from above)..

theta = np.pi/4.
xy = np.zeros((50,1001,2))
z1 = np.zeros((1001,2))
count=0

for v0 in range(0,50):
    v0x = v0 * np.cos(theta)
    v0y = v0 * np.sin(theta)
    z0 = np.array([0, v0x, ystar, v0y])

    # Calculate solution
    t, z = explicit_midpoint(rhs, z0, 5, 1000)    

    if np.around(z[:,0],3).any() == round(xstar,3):
        z1[:,0] = z[:,0]
        z1[:,1] = z[:,2]
        break
    else:
        xy[count,:,0] = z[:,0]
        xy[count,:,1] = z[:,2]
        count+=1


plt.figure()
plt.plot(0,ystar,"ro")
plt.plot(xstar,0,"ro")

for k in range(0,50):    
    plt.plot(xy[k,:,0],xy[k,:,1])

plt.plot(z[:,0],z[:,1])
plt.grid(True)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()

I'm sure that I'm using the .any() function wrong, the idea there is to round the values of z[:,0] to three digits and than compare them to xstar, if it matches the loop should terminate and retrun the current z, if not it should save it in another array and then increase v0.


Solution

  • Edit 2018-07-16

    Here I post a corrected answer taking into account the drag by air.

    Below is a python script to compute the set of (v0,theta) values so that the air-dragged trajectory passes through (x,y) = (xstar,0) at some time t=tstar. I used the trajectory without air-drag as the initial guess and also to guess the dependence of x(tstar) on v0 for the first refinement. The number of iterations needed to arrive at the correct v0 was typically 3 to 4. The script finished in 0.99 seconds on my laptop, including the time for generating figures.

    The script generates two figures and one text file.

    • fig_xdrop_v0_theta.png
      • The black dots indicates the solution set (v0,theta)
      • The yellow line indicates the reference (v0,theta) which would be a solution if there were no air drag.
    • fig_traj_sample.png
      • Checking that the trajectory (blue solid line) passes through (x,y)=(xstar,0) when (v0,theta) is sampled from the solution set.
      • The black dashed line shows a trajectory without drag by air as a reference.
    • output.dat
      • contains the numerical data of (v0,theta) as well as the landing time tstar and number of iteration needed to find v0.

    Here begins script.

    #!/usr/bin/env python3
    
    import numpy as np
    import scipy.integrate
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import matplotlib.image as img
    
    mpl.rcParams['lines.linewidth'] = 2
    mpl.rcParams['lines.markeredgewidth'] = 1.0
    mpl.rcParams['axes.formatter.limits'] = (-4,4)
    #mpl.rcParams['axes.formatter.limits'] = (-2,2)
    mpl.rcParams['axes.labelsize'] = 'large'
    mpl.rcParams['xtick.labelsize'] = 'large'
    mpl.rcParams['ytick.labelsize'] = 'large'
    mpl.rcParams['xtick.direction'] = 'out'
    mpl.rcParams['ytick.direction'] = 'out'
    
    
    
    ############################################
    len_ref = 1.95
    xstar = 8.0*len_ref
    ystar = 4.0*len_ref
    g_earth = 9.81
    #
    mass = 118
    area = 1.9/2.
    cw = 0.78
    rho = 1.293
    lam = cw * area * rho /(2.0 * mass)
    ############################################
    ngtheta=51
    theta_min = -0.1*np.pi
    theta_max =  0.4*np.pi
    theta_grid = np.linspace(theta_min, theta_max, ngtheta)
    #
    ngv0=100
    v0min =6.0
    v0max =18.0
    v0_grid=np.linspace(v0min, v0max, ngv0)
    # .. this grid is used for the initial coarse scan by reference trajecotry
    ############################################
    outf=open('output.dat','w')
    print('data file generated: output.dat')
    ###########################################
    
    
    def calc_tstar_ref_and_x_ref_at_tstar_ref(v0, theta, ystar, g_earth):
        '''return the drop time t* and drop point x(t*) of a reference trajectory
        without air drag.
        '''
    
        vx = v0*np.cos(theta)
        vy = v0*np.sin(theta)
        ts_ref = (vy+np.sqrt(vy**2+2.0*g_earth*ystar))/g_earth
        x_ref  = vx*ts_ref 
        return (ts_ref, x_ref)
    
    
    def rhs_drag(yvec, time, g_eath, lamb):
        '''
        dx/dt = v_x
        dy/dt = v_y
        du_x/dt = -lambda v_x sqrt(u_x^2 + u_y^2)
        du_y/dt = -lambda v_y sqrt(u_x^2 + u_y^2) -g
    
        yvec[0] .. x
        yvec[1] .. y
        yvec[2] .. v_x
        yvec[3] .. v_y
        '''
    
        vnorm = (yvec[2]**2+yvec[3]**2)**0.5
        return  [ yvec[2], yvec[3], -lamb*yvec[2]*vnorm, -lamb*yvec[3]*vnorm -g_earth]
    
    
    def try_tstar_drag(v0, theta, ystar, g_earth, lamb, tstar_search_grid):
        '''one trial run to find the drop point x(t*), y(t*) of a trajectory 
        under the air drag.
        '''
    
        tinit=0.0
        tgrid = [tinit]+list(tstar_search_grid)
        yvec_list = scipy.integrate.odeint(rhs_drag,
                                           [0.0, ystar, v0*np.cos(theta), v0*np.sin(theta)],
                                           tgrid, args=(g_earth, lam))
        y_drag = [yvec[1] for yvec in yvec_list]
        x_drag = [yvec[0] for yvec in yvec_list]
    
        if y_drag[0]<0.0:
            ierr=-1
            jtstar=0
            tstar_braket=None
        elif y_drag[-1]>0.0:
            ierr=1
            jtstar=len(y_drag)-1
            tstar_braket=None
        else:
            ierr=0
            for jt in range(len(y_drag)-1):
                if y_drag[jt+1]*y_drag[jt]<=0.0:
                    tstar_braket=[tgrid[jt],tgrid[jt+1]]
                    if abs(y_drag[jt+1])<abs(y_drag[jt]):
                        jtstar = jt+1
                    else:
                        jtstar = jt
                    break
    
        tstar_est = tgrid[jtstar]
        x_drag_at_tstar_est = x_drag[jtstar]
        y_drag_at_tstar_est = y_drag[jtstar]
        return (tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, ierr, tstar_braket)
    
    
    def calc_x_drag_at_tstar(v0, theta, ystar, g_earth, lamb, tstar_est,
                             eps_y=1.0e-3, ngt_search=20,
                             rel_range_lower=0.8, rel_range_upper=1.2,
                             num_try=5):
        '''compute the dop point x(t*) of a trajectory under the air drag.
        '''
    
        flg_success=False
        tstar_est_lower=tstar_est*rel_range_lower
        tstar_est_upper=tstar_est*rel_range_upper
        for jtry in range(num_try):
            tstar_search_grid = np.linspace(tstar_est_lower, tstar_est_upper, ngt_search)
            tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, ierr, tstar_braket \
                = try_tstar_drag(v0, theta, ystar, g_earth, lamb, tstar_search_grid)
            if ierr==-1:
                tstar_est_upper = tstar_est_lower
                tstar_est_lower = tstar_est_lower*rel_range_lower
            elif ierr==1:
                tstar_est_lower = tstar_est_upper
                tstar_est_upper = tstar_est_upper*rel_range_upper
            else:
                if abs(y_drag_at_tstar_est)<eps_y:
                    flg_success=True
                    break
                else:
                    tstar_est_lower=tstar_braket[0]
                    tstar_est_upper=tstar_braket[1]
    
        return (tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, flg_success)
    
    
    def find_v0(xstar, v0_est, theta, ystar, g_earth, lamb, tstar_est,
                eps_x=1.0e-3, num_try=6):
       '''solve for v0 so that x(t*)==x*.
       '''
    
       flg_success=False
       v0_hist=[]
       x_drag_at_tstar_hist=[]
       jtry_end=None
       for jtry in range(num_try):
           tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, flg_success_x_drag \
               = calc_x_drag_at_tstar(v0_est, theta, ystar, g_earth, lamb, tstar_est)
           v0_hist.append(v0_est)
           x_drag_at_tstar_hist.append(x_drag_at_tstar_est)
           if not flg_success_x_drag:
               break
           elif abs(x_drag_at_tstar_est-xstar)<eps_x: 
               flg_success=True
               jtry_end=jtry
               break
           else:
               # adjust v0
               # better if tstar_est is also adjusted, but maybe that is too much.
               if len(v0_hist)<2:
                   # This is the first run. Use the analytical expression of
                   # dx(tstar)/dv0 of  the refernece trajectory
                   dx = xstar - x_drag_at_tstar_est
                   dv0 = dx/(tstar_est*np.cos(theta))
                   v0_est += dv0
               else:
                   # use linear interpolation
                   v0_est = v0_hist[-2] \
                            + (v0_hist[-1]-v0_hist[-2]) \
                            *(xstar                 -x_drag_at_tstar_hist[-2])\
                            /(x_drag_at_tstar_hist[-1]-x_drag_at_tstar_hist[-2])
    
       return (v0_est, tstar_est, flg_success, jtry_end)        
    
    
    # make a reference table of t* and x(t*) of a trajectory without air drag
    # as a function of v0 and theta.
    tstar_ref=np.empty((ngtheta,ngv0))
    xdrop_ref=np.empty((ngtheta,ngv0))
    for j1 in range(ngtheta):
        for j2 in range(ngv0):
            tt, xx = calc_tstar_ref_and_x_ref_at_tstar_ref(v0_grid[j2], theta_grid[j1], ystar, g_earth)
            tstar_ref[j1,j2] = tt
            xdrop_ref[j1,j2] = xx
    
    # make an estimate of v0 and t* of a dragged trajectory for each theta 
    # based on the reference trajectroy's landing position xdrop_ref.
    tstar_est=np.empty((ngtheta,))
    v0_est=np.empty((ngtheta,))
    v0_est[:]=-1.0
    # .. null value
    for j1 in range(ngtheta):
        for j2 in range(ngv0-1):
            if (xdrop_ref[j1,j2+1]-xstar)*(xdrop_ref[j1,j2]-xstar)<=0.0:
                tstar_est[j1] = tstar_ref[j1,j2]
                # .. lazy
                v0_est[j1] \
                    = v0_grid[j2] \
                    + (v0_grid[j2+1]-v0_grid[j2])\
                    *(xstar-xdrop_ref[j1,j2])/(xdrop_ref[j1,j2+1]-xdrop_ref[j1,j2])
                # .. linear interpolation
                break
    
    print('compute v0 for each theta under air drag..')
    # compute v0 for each theta under air drag
    theta_sol_list=[]
    tstar_sol_list=[]
    v0_sol_list=[]
    outf.write('#  theta  v0  tstar  numiter_v0\n')
    for j1 in range(ngtheta):
        if v0_est[j1]>0.0:
            v0, tstar, flg_success, jtry_end \
                = find_v0(xstar, v0_est[j1], theta_grid[j1], ystar, g_earth, lam, tstar_est[j1])
            if flg_success:
                theta_sol_list.append(theta_grid[j1])
                v0_sol_list.append(v0)
                tstar_sol_list.append(tstar)
                outf.write('%26.16e  %26.16e %26.16e %10i\n'
                           %(theta_grid[j1], v0, tstar, jtry_end+1))
    
    theta_sol = np.array(theta_sol_list)            
    v0_sol    = np.array(v0_sol_list)            
    tstar_sol = np.array(tstar_sol_list)            
    
    
    
    ### Check a sample 
    jsample=np.size(v0_sol)//3
    theta_sol_sample= theta_sol[jsample]
    v0_sol_sample   = v0_sol[jsample]
    tstar_sol_sample= tstar_sol[jsample]
    
    ngt_chk = 50
    tgrid  = np.linspace(0.0, tstar_sol_sample, ngt_chk)
    
    yvec_list = scipy.integrate.odeint(rhs_drag,
                                       [0.0, ystar,
                                        v0_sol_sample*np.cos(theta_sol_sample),
                                        v0_sol_sample*np.sin(theta_sol_sample)],
                                       tgrid, args=(g_earth, lam))
    y_drag_sol_sample = [yvec[1] for yvec in yvec_list]
    x_drag_sol_sample = [yvec[0] for yvec in yvec_list]
    
    # compute also the trajectory without drag starting form the same initial
    # condiiton by setting lambda=0.
    yvec_list = scipy.integrate.odeint(rhs_drag,
                                       [0.0, ystar,
                                        v0_sol_sample*np.cos(theta_sol_sample),
                                        v0_sol_sample*np.sin(theta_sol_sample)],
                                       tgrid, args=(g_earth, 0.0))
    y_ref_sample = [yvec[1] for yvec in yvec_list]
    x_ref_sample = [yvec[0] for yvec in yvec_list]
    
    
    #######################################################################
    # canvas setting 
    #######################################################################
    f_size   = (8,5)
    #
    a1_left   = 0.15
    a1_bottom  = 0.15
    a1_width  = 0.65
    a1_height = 0.80
    #
    hspace=0.02
    #
    ac_left   = a1_left+a1_width+hspace
    ac_bottom = a1_bottom
    ac_width  = 0.03
    ac_height = a1_height
    ###########################################
    
    
    
    ############################################
    # plot 
    ############################################
    
    #------------------------------------------------
    print('plotting the solution..')
    fig1=plt.figure(figsize=f_size)
    ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
    
    im1=img.NonUniformImage(ax1,
                                interpolation='bilinear', \
                                cmap=mpl.cm.Blues, \
                                norm=mpl.colors.Normalize(vmin=0.0,
                                                              vmax=np.max(xdrop_ref), clip=True)) 
    im1.set_data(v0_grid, theta_grid/np.pi, xdrop_ref )
    ax1.images.append(im1)
    plt.contour(v0_grid, theta_grid/np.pi, xdrop_ref, [xstar], colors='y')
    plt.plot(v0_sol, theta_sol/np.pi, 'ok', lw=4, label='Init Cond with Drag')
    plt.legend(loc='lower left')
    
    plt.xlabel(r'Initial Velocity $v_0$', fontsize=18)
    plt.ylabel(r'Angle of Projection $\theta/\pi$', fontsize=18)
    plt.yticks([-0.50, -0.25, 0.0, 0.25, 0.50])
    ax1.set_xlim([v0min, v0max])
    ax1.set_ylim([theta_min/np.pi, theta_max/np.pi])
    
    axc =plt.axes([ac_left, ac_bottom, ac_width, ac_height], axisbg='w')
    mpl.colorbar.Colorbar(axc,im1)
    axc.set_ylabel('Distance from Blacony without Drag')
    # 'Distance from Blacony $x(t^*)$'
    
    plt.savefig('fig_xdrop_v0_theta.png')
    print('figure file genereated: fig_xdrop_v0_theta.png')
    plt.close()
    
    
    #------------------------------------------------
    print('plotting a sample trajectory..')
    fig1=plt.figure(figsize=f_size)
    ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
    plt.plot(x_drag_sol_sample, y_drag_sol_sample, '-b', lw=2, label='with drag')
    plt.plot(x_ref_sample, y_ref_sample, '--k', lw=2, label='without drag')
    plt.axvline(x=xstar, color=[0.3, 0.3, 0.3], lw=1.0)
    plt.axhline(y=0.0, color=[0.3, 0.3, 0.3], lw=1.0)
    plt.legend()
    plt.text(0.1*xstar, 0.6*ystar,
             r'$v_0=%5.2f$'%(v0_sol_sample)+'\n'+r'$\theta=%5.2f \pi$'%(theta_sol_sample/np.pi),
             fontsize=18)
    plt.text(xstar, 0.5*ystar, 'xstar', fontsize=18)
    
    plt.xlabel(r'Horizontal Distance $x$', fontsize=18)
    plt.ylabel(r'Height $y$', fontsize=18)
    ax1.set_xlim([0.0,        1.5*xstar])
    ax1.set_ylim([-0.1*ystar, 1.5*ystar])
    
    plt.savefig('fig_traj_sample.png')
    print('figure file genereated: fig_traj_sample.png')
    plt.close()
    
    outf.close()
    

    Here is the figure fig_xdrop_v0_theta.png.

    fig_xdrop_v0_theta.png

    Here is the figure fig_traj_sample.png.

    fig_traj_sample.png


    Edit 2018-07-15

    I realized that I overlooked that the question considers the drag by air. What a shame on me. So, my answer below is not correct. I'm afraid that deleting my answer by myself looks like hiding a mistake, and I leave it below for now. If people think it's annoying that an incorrect answer hanging around, I'm O.K. someone delete it.


    The differential equation can actually be solved by hand, and it does not require much computational resource to map out how far the person reach from the balcony on the ground as a function of the initial velocity v0 and the angle theta. Then, you can select the condition (v0,theta) such that distance_from_balcony_on_the_ground(v0,theta) = xstar from this data table.

    Let's write the horizontal and vertical coordinates of the person at time t is x(t) and y(t), respectively. I think you took x=0 at the wall of the building and y=0 as the ground level, and I do so here, too. Let's say the horizontal and vertical velocity of the person at time t are v_x(t) and v_y(t), respectively. The initial conditions at t=0 are given as

    x(0) = 0
    y(0) = ystar
    v_x(0) = v0 cos theta
    v_y(0) = v0 sin theta
    

    The Newton eqution you are solving is,

    dx/dt = v_x  .. (1)
    dy/dt = v_y  .. (2)
    m d v_x /dt = 0  .. (3)
    m d v_y /dt = -m g  .. (4)
    

    where m is the mass of the person, and g is the constant which I don't know the English name of, but we all know what it is.

    From eq. (3),

    v_x(t) = v_x(0) = v0 cos theta.
    

    Using this with eq. (1),

    x(t) = x(0) + \int_0^t dt' v_x(t') = t v0 cos theta,
    

    where we also used the initial condition. \int_0^t means integral from 0 to t.

    From eq. (4),

    v_y(t)
    = v_y (0) + \int_0^t dt' (-g) 
    = v0 sin theta -g t,
    

    where we used the initial condition. Using this with eq. (3) and also using the initial condition,

    y(t)
    = y(0) + \int_0^t dt' v_y(t')
    = ystar + t v0 sin theta -t^2 (g/2).
    

    where t^2 means t squared. From the expression for y(t), we can get the time tstar at which the person hits the ground. That is, y(tstar) =0. This equation can be solved by quadratic formula (or something similar name) as

    tstar = (v0 sin theta + sqrt((v0 sin theta)^2 + 2g ystar)/g,
    

    where I used a condition tstar>0. Now we know the distance from the balcony the person reached when he hit the ground as x(tstar). Using the expression for x(t) above,

    x(tstar) = (v0 cos theta) (v0 sin theta + sqrt((v0 sin theta)^2 + 2g ystar))/g.
      .. (5)
    

    Actually x(tstar) depends on v0 and theta as well as g and ystar. You hold g and ystar as constants, and you want to find all (v0,theta) such that x(tstar) = xstar for a given xstar value.

    Since the right hand side of eq. (5) can be computed cheaply, you can set up grids for v0 and theta and compute xstar on this 2D grid. Then, you can see where roughly is the solution set of (v0,theta) lies. If you need precise solution, you can pick up a segment which encloses the solution from this data table.

    Below is a python script that demonstrates this idea.

    I also attach here a figure generated by this script. The yellow curve is the solution set (v0,theta) such that the person hit the ground at xstar from the wall when xstar = 8.0*1.95 and ystar=4.0*1.95 as you set. The blue color coordinate indicates x(tstar), i.e., how far the person jumped from the balcony horizontally. Note that at a given v0 (higher than a threshold value aruond v0=9.9), the there are two theta values (two directions for the person to project himself) to reach the aimed point (x,y) = (xstar,0). The smaller branch of the theta value can be negative, meaning that the person can jump downward to reach the aimed point, as long as the initial velocity is sufficiently high.

    The script also generates a data file output.dat, which has the solution-enclosing segments.

    #!/usr/bin/python3
    
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import matplotlib.image as img
    
    mpl.rcParams['lines.linewidth'] = 2
    mpl.rcParams['lines.markeredgewidth'] = 1.0
    mpl.rcParams['axes.formatter.limits'] = (-4,4)
    #mpl.rcParams['axes.formatter.limits'] = (-2,2)
    mpl.rcParams['axes.labelsize'] = 'large'
    mpl.rcParams['xtick.labelsize'] = 'large'
    mpl.rcParams['ytick.labelsize'] = 'large'
    mpl.rcParams['xtick.direction'] = 'out'
    mpl.rcParams['ytick.direction'] = 'out'
    
    
    ############################################
    len_ref = 1.95
    xstar = 8.0*len_ref
    ystar = 4.0*len_ref
    g_earth = 9.81
    ############################################
    ngv0=100
    v0min =0.0
    v0max =20.0
    v0_grid=np.linspace(v0min, v0max, ngv0)
    ############################################
    outf=open('output.dat','w')
    print('data file generated: output.dat')
    ###########################################
    
    def x_at_tstar(v0, theta, ystar, g_earth):
        vx = v0*np.cos(theta)
        vy = v0*np.sin(theta)
        return (vy+np.sqrt(vy**2+2.0*g_earth*ystar))*vx/g_earth
    
    ngtheta=100
    theta_min = -0.5*np.pi
    theta_max =  0.5*np.pi
    theta_grid = np.linspace(theta_min, theta_max, ngtheta)
    xdrop=np.empty((ngv0,ngtheta))
    # x(t*) as a function of v0 and theta.
    for j1 in range(ngv0):
        for j2 in range(ngtheta):
            xdrop[j1,j2] = x_at_tstar(v0_grid[j1], theta_grid[j2], ystar, g_earth)
    
    outf.write('# domain [theta_lower, theta_upper] that encloses the solution\n')        
    outf.write('# theta such that x_at_tstart(v0,theta, ystart, g_earth)=xstar\n')        
    outf.write('# v0  theta_lower  theta_upper  x_lower  x_upper\n')
    for j1 in range(ngv0):
        for j2 in range(ngtheta-1):
           if (xdrop[j1,j2+1]-xstar)*(xdrop[j1,j2]-xstar)<=0.0:
               outf.write('%26.16e %26.16e %26.16e %26.16e %26.16e\n'
                              %(v0_grid[j1], theta_grid[j2], theta_grid[j2+1],
                                    xdrop[j1,j2], xdrop[j1,j2+1]))
    print('See output.dat for the segments enclosing solutions.')
    print('You can hunt further for precise solutions using this data.')
    
    
    
    #######################################################################
    # canvas setting 
    #######################################################################
    f_size   = (8,5)
    #
    a1_left   = 0.15
    a1_bottom  = 0.15
    a1_width  = 0.65
    a1_height = 0.80
    #
    hspace=0.02
    #
    ac_left   = a1_left+a1_width+hspace
    ac_bottom = a1_bottom
    ac_width  = 0.03
    ac_height = a1_height
    ###########################################
    
    
    
    ############################################
    # plot 
    ############################################
    
    print('plotting..')
    fig1=plt.figure(figsize=f_size)
    ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
    
    im1=img.NonUniformImage(ax1,
                                interpolation='bilinear', \
                                cmap=mpl.cm.Blues, \
                                norm=mpl.colors.Normalize(vmin=0.0,
                                                              vmax=np.max(xdrop), clip=True)) 
    im1.set_data(v0_grid, theta_grid/np.pi, np.transpose(xdrop))
    ax1.images.append(im1)
    plt.contour(v0_grid, theta_grid/np.pi, np.transpose(xdrop), [xstar], colors='y')
    
    plt.xlabel(r'Initial Velocity $v_0$', fontsize=18)
    plt.ylabel(r'Angle of Projection $\theta/\pi$', fontsize=18)
    plt.yticks([-0.50, -0.25, 0.0, 0.25, 0.50])
    ax1.set_xlim([v0min, v0max])
    ax1.set_ylim([theta_min/np.pi, theta_max/np.pi])
    
    axc =plt.axes([ac_left, ac_bottom, ac_width, ac_height], axisbg='w')
    mpl.colorbar.Colorbar(axc,im1)
    # 'Distance from Blacony $x(t^*)$'
    
    plt.savefig('fig_xdrop_v0_theta.png')
    print('figure file genereated: fig_xdrop_v0_theta.png')
    plt.close()
    
    
    
    outf.close()
    

    fig_xdrop_v0_theta.png