def main(number_of_time_intervals, time_step, want_to_plot, want_to_animate):
    #
    # ================ Make Directory for Plots if it's not there already =============
    #
    # detect the current working directory and print it
    path = os.getcwd()
    print("The current working directory is %s" % path)
    # define the name of the directory to be created
    Plot_Output = path + "/Plot_Output"

    if want_to_plot is True:
        if os.path.exists(Plot_Output):
            print("Directory for wave function plots already exists", Plot_Output)
        else:
            print(
                "Attempting to create directory for wave function plots ", Plot_Output,
            )
            try:
                os.mkdir(Plot_Output)
            except OSError:
                print("Creation of the directory %s failed" % Plot_Output)
            else:
                print("Successfully created the directory %s " % Plot_Output)
    # =====================================FEM_DVR===================================
    #  Set up the FEM DVR grid given only the Finite Element boundaries and order
    #  of Gauss Lobatto quadrature,  and compute the Kinetic Energy matrix for
    #  this FEM DVR for Mass = mass set in call (atomic units).
    #
    # Here is where the reduced mass is set
    # H_Mass = 1.007825032 #H atom atomic mass
    # H_Mass = 1.00727647  #proton atomic mass
    # standard atomic weight natural abundance, seems to be what Turner, McCurdy used
    H_Mass = 1.0078
    Daltons_to_eMass = 1822.89
    mu = (H_Mass / 2.0) * Daltons_to_eMass
    print("reduced mass mu = ", mu)
    bohr_to_Angstrom = 0.529177
    Hartree_to_eV = 27.211386245988  # NIST ref
    eV_to_wavennumber = 8065.54393734921  # NIST ref on constants + conversions
    # value from NIST ref on constants + conversions
    Hartree_to_wavenumber = 2.1947463136320e5
    HartreeToKelvin = 315773
    atu_to_fs = 24.18884326509 / 1000
    #  Set up the FEM-DVR grid
    n_order = 35
    FEM_boundaries = [
        0.4,
        1.0,
        2.0,
        3.0,
        4.0,
        5.0,
        6.0,
        7.0,
        8.0,
        10.0,
        11.0,
        12.0,
        15.0,
        20.0,
        25.0,
    ]
    # parameters for Fig 2 in Turner, McCurdy paper
    # Julia Turner and C. William McCurdy, Chemical Physics 71(1982) 127-133
    scale_factor = np.exp(1j * 20.0 * np.pi / 180.0)
    R0 = 10.0  # potential must be analytic for R >= R0
    fem_dvr = FEM_DVR(
        n_order, FEM_boundaries, Mass=mu, Complex_scale=scale_factor, R0_scale=R0,
    )
    print("\nFEM-DVR basis of ", fem_dvr.nbas, " functions")

    pertubation = Potential()
    # ==================================================================================
    #  Plot potential on the DVR grid points on which the wavefunction is defined
    print("\n Plot potential ")
    time = 0.0
    x_Plot = []
    pot_Plot = []
    for j in range(0, fem_dvr.nbas):
        x_Plot.append(np.real(fem_dvr.x_pts[j + 1]))
        pot_Plot.append(np.real(pertubation.V_Bernstein(fem_dvr.x_pts[j + 1], time)))

    if want_to_plot is True:
        plt.suptitle("V(x) at DVR basis function nodes", fontsize=14, fontweight="bold")
        string = "V"
        plt.plot(x_Plot, pot_Plot, "ro", label=string)
        plt.plot(x_Plot, pot_Plot, "-b")
        plt.legend(loc="best")
        plt.xlabel(" x ", fontsize=14)
        plt.ylabel("V", fontsize=14)
        print(
            "\n Running from terminal, close figure window to proceed and make .pdf file of figure"
        )
        #   Insert limits if necessary
        #   Generally comment this logic.  Here I am matching the Turner McCurdy Figure 2
        # CWM: need to use float() to get plt.xlim to work to set x limits
        ymax = float(0.05)
        plt.ylim([-0.18, ymax])
        # save plot to .pdf file
        plt.savefig("Plot_Output/" + "Plot_potential" + ".pdf", transparent=False)
        plt.show()
    #
    # =============Build Hamiltonian (at t=0 if time-dependent)=================================
    #     Pass name of potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian(pertubation.vectorized_V_Bernstein, time)
    print("\n Completed construction of Hamiltonian at t = 0")
    # ====================================================================================
    #
    # Find all the eigenvalues of the Hamiltonian so we can compare with known bound state energies
    # or make a plot of the spectrum -- For a time-independent Hamiltonian example here
    #
    # EigenVals = LA.eigvals(H_mat) # eigenvalues only for general matrix.  LA.eigvalsh for Hermitian
    print(
        "Calculating ",
        fem_dvr.nbas,
        " eigenvalues and eigenvectors for plotting eigenfunctions",
    )
    EigenVals, EigenVecs = LA.eig(H_mat, right=True, homogeneous_eigvals=True)
    print("after LA.eig()")
    #
    n_energy = fem_dvr.nbas
    file_opened = open("Spectrum_ECS.dat", "w")
    print("EigenVals shape ", EigenVals.shape)
    for i in range(0, n_energy):
        print("E( ", i, ") =   ", EigenVals[0, i], " hartrees")
        print(
            np.real(EigenVals[0, i]), "  ", np.imag(EigenVals[0, i]), file=file_opened,
        )
    # ====================================================================================
    #
    # Extract the n_Plot'th eigenfunction for plotting
    #
    # pick one of the bound states of Morse Potential to plot
    # numbering can depend on numpy and python installation that determines
    # behavior of the linear algebra routines.
    n_Plot = n_energy - 1  # This is generally the highest energy continuum eigenvalue
    n_Plot = 426
    wfcnPlot = []
    for j in range(0, fem_dvr.nbas):
        wfcnPlot.append(EigenVecs[j, n_Plot])
    #
    # Normalize  wave function from diagonalization
    # using integration of square on the contour
    # following the original Rescigno McCurdy idea for partial widths
    # from the paper
    # "Normalization of resonance wave functions and the calculation of resonance widths"
    #  Rescigno, McCurdy, Phys Rev A 34,1882 (1986)
    norm_squared = 0.0
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2
    wfcnPlot = wfcnPlot / np.sqrt(norm_squared)
    norm_squared = 0.0
    gamma_residue = 0.0
    # background momentum defined with Re(Eres)
    k_momentum = np.sqrt(2.0 * mu * np.real(EigenVals[0, n_Plot]))
    # k_momentum = np.real(np.sqrt(2.0*mu*EigenVals[0,n_Plot]))
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2
        if fem_dvr.x_pts[j + 1] < 5.8:
            free_wave = (2.0 * np.sqrt(mu / k_momentum)) * np.sin(
                k_momentum * fem_dvr.x_pts[j + 1]
            )
            gamma_residue = gamma_residue + wfcnPlot[j] * pertubation.V_Bernstein(
                fem_dvr.x_pts[j + 1], time
            ) * free_wave * np.sqrt(fem_dvr.w_pts[j + 1])
    print("Complex symmetric inner product (psi|psi) is being used")

    if want_to_plot is True:
        print(
            "Norm of wave function from int psi^2 on contour being plotted is ",
            np.sqrt(norm_squared),
        )

    print(" For this state the asymptotic value of k = ", k_momentum)
    print(
        "gamma from int = ", gamma_residue, " |gamma|^2 = ", np.abs(gamma_residue) ** 2,
    )
    # Plot wave function -- It must be type np.complex
    Cinitial = np.zeros((fem_dvr.nbas), dtype=np.complex)
    wfcnInitialPlot = np.zeros((fem_dvr.nbas), dtype=np.complex)
    for j in range(0, fem_dvr.nbas):
        Cinitial[j] = wfcnPlot[j]

    if want_to_plot is True:
        #
        # plot n_Plot'th eigenfunction
        #
        print(
            "\n Plot Hamiltonian eigenfunction number ",
            n_Plot,
            " with energy ",
            EigenVals[0, n_Plot],
        )
        tau = atu_to_fs / (-2.0 * np.imag(EigenVals[0, n_Plot]))
        print("  Lifetime tau = 1/Gamma = ", tau, " fs")
        number_string = str(n_Plot)
        title = "Wavefunction number = " + number_string
        wfn_plot_points = 2000
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Cinitial,
            plot_title_string=title,
            N_plot_points=wfn_plot_points,
            make_plot=True,
        )
        #
        #  Make data file for n_Plot'th eigenfunction
        #  Psi and the integrand of the residue factors, gamma, of the free-free matrix element
        #  of the full Green's function, as per
        #
        filename = "wavefunction" + number_string + ".dat"
        file_opened = open(filename, "w")
        print_points = len(x_Plot_array)
        print("x_Plot_array shape ", print_points)
        for i in range(print_points):
            free_wave = (2.0 * np.sqrt(mu / k_momentum)) * np.sin(
                k_momentum * x_Plot_array[i]
            )
            # for partial width gamma
            integrand = (
                Psi_plot_array[i]
                * pertubation.V_Bernstein(x_Plot_array[i], time)
                * free_wave
            )
            print(
                np.real(x_Plot_array[i]),
                "  ",
                np.imag(x_Plot_array[i]),
                "  ",
                np.real(Psi_plot_array[i]),
                "  ",
                np.imag(Psi_plot_array[i]),
                "  ",
                np.real(integrand),
                np.imag(integrand),
                file=file_opened,
            )
    #
    # ================# Initialize wave function at t = 0 ================================
    #   It must be type np.complex
    Cinitial = np.zeros((fem_dvr.nbas), dtype=np.complex)
    wfcnInitialPlot = np.zeros((fem_dvr.nbas), dtype=np.complex)
    for j in range(0, fem_dvr.nbas):
        #
        #  Displaced Gaussian packet in well. Displace to larger R because displacing
        #  to R < Req produces significant amplitude in the continuum
        #
        alpha = 40.0
        shift = 4.0
        Cinitial[j] = (
            np.sqrt(np.sqrt(2.0 * alpha / np.pi))
            * np.exp(-alpha * (fem_dvr.x_pts[j + 1] - shift) ** 2)
            * np.sqrt(fem_dvr.w_pts[j + 1])
        )
        wfcnInitialPlot[j] = Cinitial[j]
    #
    # =====================Propagation from t = tinitial to tfinal ============================
    # initialize arrays for storing the information for all the frames in the animation of
    # the propagation of the wave function
    times_array = []
    x_Plot_time_array = []
    Psi_plot_time_array = []
    tinitial = 0.0
    # specify final time and specify number of intervals for plotting
    #  commented info for Morse oscillator revivals, in atu
    # a = 1.0277
    # d = 0.1746
    # omega = np.sqrt((2.e0/mu)*d*a**2)
    # T_revival = 4*np.pi*mu/a**2 # for the morse oscillator
    # number_of_time_intervals = 25*np.int((tfinal-tinitial)*omega/(2.0*np.pi))
    # print("T_revival = ",T_revival," atomic time units ",T_revival*24.1888/1000.0," femtoseconds")
    tfinal = 5000  # specified in atomic time units
    print(
        "\nOverall propagation will be from ",
        tinitial,
        " to ",
        tfinal,
        " atu in ",
        number_of_time_intervals,
        " intervals",
    )

    if want_to_plot is True:
        #
        # plot initial wave packet
        #
        print("\n Plot initial wave function at t = ", tinitial)
        number_string = str(0.0)
        title = "Wavefunction at t = " + number_string
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Cinitial, plot_title_string=title, N_plot_points=1250, make_plot=True,
        )
        times_array.append(tinitial)
        x_Plot_time_array.append(x_Plot_array)
        Psi_plot_time_array.append(Psi_plot_array)
    #
    #  Loop over number_of_time_intervals intervals that make up t = 0 to tfinal
    #
    t_interval = (tfinal - tinitial) / number_of_time_intervals
    # for H2 reduced mass, Deltat = 0.05 in Morse oscillator seems to work for 10s of fs
    # for D2 reduced mass, Deltat = 0.1 in Morse oscillator seems to work for at least 2 ps
    # based on comparison with time steps 5 to 10 times smaller
    time_step = 0.1  # atomic time units
    # time_step = 0.05  # atomic time units
    # time_step = 0.015  # atomic time units
    for i_time_interval in range(0, number_of_time_intervals):
        t_start = tinitial + i_time_interval * t_interval
        t_finish = tinitial + (i_time_interval + 1) * t_interval
        N_time_steps = np.int(t_interval / time_step)
        Delta_t = t_interval / np.float(N_time_steps)
        #
        # Check norm of initial wavefunction on real part of ECS Contour
        #
        # find  FEM DVR function at the last real DVR point
        first_ECS_element = fem_dvr.i_elem_scale
        last_real_dvr_fcn = 1
        for i in range(0, fem_dvr.nbas):
            if fem_dvr.x_pts[i + 1] < FEM_boundaries[first_ECS_element]:
                last_real_dvr_fcn = last_real_dvr_fcn + 1
        # the point last_real_dvr_fcn still has complex bridging function weight.
        # small error for integral (0,R0) may result.
        norm_initial = 0
        for j in range(0, last_real_dvr_fcn):
            norm_initial = norm_initial + np.abs(Cinitial[j]) ** 2
        print(
            "\nNorm of starting  wave function on real part of ECS contour",
            norm_initial,
        )
        #
        #  use the propagator from DVR()
        #
        clock_start = timeclock.time()
        Ctfinal = fem_dvr.Crank_Nicolson(
            t_start,
            t_finish,
            N_time_steps,
            Cinitial,
            pertubation.vectorized_V_Bernstein,
            Is_H_time_dependent=False,
        )
        clock_finish = timeclock.time()
        print(
            "Time for ",
            N_time_steps,
            " Crank-Nicolson steps was ",
            clock_finish - clock_start,
            " secs.",
        )
        #
        # Check norms of initial and final wavefunctions
        #
        norm_final = 0
        for j in range(0, last_real_dvr_fcn):
            # should be change to real part of contour.
            norm_final = norm_final + np.abs(Ctfinal[j]) ** 2
        print(
            "Norm of final wave function on real part of ECS contour", norm_final,
        )

        if want_to_plot is True:
            #
            # Plot of packet at end of each interval using Plot_Psi from DVR()
            #
            print(
                "Plot function propagated to t = ",
                t_finish,
                " atomic time units ",
                t_finish * atu_to_fs,
                " fs",
            )
            number_string = str(t_finish)
            title = "Wavefunction at t = " + number_string
            x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
                Ctfinal, plot_title_string=title, N_plot_points=1250, make_plot=False,
            )
            times_array.append(t_finish)
            x_Plot_time_array.append(x_Plot_array)
            Psi_plot_time_array.append(Psi_plot_array)
        #
        #   reset initial packet as final packet for this interval
        for i in range(0, fem_dvr.nbas):
            Cinitial[i] = Ctfinal[i]
    #
    if want_to_plot is True:
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Ctfinal, plot_title_string=title, N_plot_points=1250, make_plot=True
        )
        print(
            "Final frame at tfinal = ",
            tfinal,
            "atomic time units  is showing -- ready to prepare animation ",
        )
        print("\n **** close the plot window to proceed ****")
    # ==============================================================================
    # # initialization function: plot the background of each frame
    # ==============================================================================

    def init():
        line.set_data([], [])
        time_text.set_text("")
        ax.set_xlabel(" x (bohr) ", fontsize=16, fontweight="bold")
        ax.set_ylabel(" Psi(t): Re, Im & Abs ", fontsize=16, fontweight="bold")
        fig.suptitle("Wave Packet in H2 Potential", fontsize=16, fontweight="bold")
        # put in a line at the value Phi = 0
        ax.plot([x_Plot[0], x_Plot[len(x_Plot) - 1]], [0, 0], "k")
        return line, time_text

    nframes = number_of_time_intervals

    def animate(i):
        # ==============================================================================
        #
        #  commands to specify changing elements of the animation
        #  We precomputed all the lines at all the times so that this
        #  wouldn't recompute everything at each frame, which is very slow...
        #
        # ==============================================================================
        time_string = str(times_array[i] * 24.189 / 1000.0)  # string for a plot label
        re_array = np.real(Psi_plot_time_array[i])
        im_array = np.imag(Psi_plot_time_array[i])
        abs_array = np.abs(Psi_plot_time_array[i])
        line1.set_data(x_Plot_array, re_array)
        line2.set_data(x_Plot_array, im_array)
        line3.set_data(x_Plot_array, abs_array)
        time_text.set_text("time = " + time_string + " fs")
        return (line1, line2, line3, time_text)

    if want_to_animate is True:
        # ==============================================================================
        #  Now plot the animation
        # ==============================================================================
        # reinitialize the figure for the next plot which is the animation
        fig = plt.figure()
        ymax = 1.25
        xmin = x_Plot[0]
        xmax = 20.0
        ax = fig.add_subplot(
            111, autoscale_on=False, xlim=(xmin, xmax), ylim=(-ymax, +ymax)
        )
        (line,) = ax.plot([], [], "-r", lw=2)
        (line1,) = ax.plot([], [], "-r", lw=2)
        (line2,) = ax.plot([], [], "-b", lw=2)
        (line3,) = ax.plot([], [], "k", lw=2)
        # define the object that will be the time printed on each frame
        time_text = ax.text(0.02, 0.95, "", transform=ax.transAxes)

        # ==============================================================================
        # call the animator.  blit=True means only re-draw the parts that have changed.
        # ==============================================================================
        anim = animation.FuncAnimation(
            fig,
            animate,
            init_func=init,
            frames=nframes + 1,
            interval=200,
            blit=True,
            repeat=True,
        )
        # ==============================================================================
        #  show the animation
        # ==============================================================================
        anim.save("Plot_Output/H2_wavepacket.mp4")
        plt.show()
        print("done")

    if want_to_plot is False:
        print(
            "\n\nSet the command line option want_to_plot=True to see figures and create plotting directory.\n\n"
        )

    if want_to_animate is False:
        print("Set the command line option want_to_animate=True to see animation.\n\n")
Ejemplo n.º 2
0
def main(want_to_plot):
    #
    # ============== Make Directory for Plots if it's not there already ==========
    #
    # detect the current working directory and print it
    path = os.getcwd()
    print("The current working directory is %s" % path)
    # define the name of the directory to be created
    Plot_Output = path + "/Plot_Output"

    if want_to_plot is True:
        if os.path.exists(Plot_Output):
            print("Directory for wave function plots already exists",
                  Plot_Output)
        else:
            print(
                "Attempting to create directory for wave function plots ",
                Plot_Output,
            )
            try:
                os.mkdir(Plot_Output)
            except OSError:
                print("Creation of the directory %s failed" % Plot_Output)
            else:
                print("Successfully created the directory %s " % Plot_Output)
    #
    # === Make Directory for for the .dat output  if it's not there already ======
    #
    # detect the current working directory and print it
    path = os.getcwd()
    print("The current working directory is %s" % path)
    # define the name of the directory to be created
    Data_Output = path + "/Data_Output"
    if os.path.exists(Data_Output):
        print("Directory for output .dat files  already exists", Plot_Output)
    else:
        print(
            "Attempting to create directory for .dat output files  ",
            Plot_Output,
        )
        try:
            os.mkdir(Data_Output)
        except OSError:
            print("Creation of the directory %s failed" % Data_Output)
        else:
            print("Successfully created the directory %s " % Data_Output)
    # ===================================================================
    if want_to_plot is True:
        wfcn_plotfile = open("Data_Output/wavefunctions.dat",
                             "w")  # data file for saving wavefunctions
    #
    # =============Constants and conversion factors ==============
    Daltons_to_eMass = 1822.89
    bohr_to_Angstrom = 0.529177
    Hartree_to_eV = 27.211386245988  # NIST ref
    eV_to_wavennumber = 8065.54393734921  # NIST ref on constants + conversions
    Hartree_to_wavenumber = (
        2.1947463136320e5  # value from NIST ref on constants + conversions
    )
    atu_to_fs = 24.18884326509 / 1000
    HartreeToKelvin = 315773
    #
    # =====================================FEM_DVR===================================
    #  Set up the FEM DVR grid given only the Finite Element boundaries and order
    #  of Gauss Lobatto quadrature,  and compute the Kinetic Energy matrix for
    #  this FEM DVR for Mass = mass set in call (atomic units).
    #
    # Here is where the reduced mass (or mass for any 1D problem) is set
    O_Mass = 15.99491461957  # O 16 mass from NIST tables
    C_Mass = 12.0  # C 12 mass
    mu = (O_Mass * C_Mass / (O_Mass + C_Mass)) * Daltons_to_eMass
    n_order = 30
    FEM_boundaries = [
        0.801,
        1.25,
        1.5,
        2.0,
        2.5,
        3.0,
        3.5,
        4.0,
        4.5,
        5.0,
        5.5,
        6.0,
        6.5,
        7.0,
        7.5,
        8.0,
        8.5,
        8.99,
    ]
    fem_dvr = FEM_DVR(n_order, FEM_boundaries, Mass=mu)
    print("\nFEM-DVR basis of ", fem_dvr.nbas, " functions")
    #
    #   Function to define potential at x and t (if potential is time-dependent)
    #   DVRHelper class library expects function for V(x,t) in general
    #
    # =================================Potential=====================================
    #  Read in files with points for potential curve in hartrees
    #  and load in arrays for interpolation
    path = Path(__file__).parent.absolute()
    perturbation = Potential(path / "potcurve_CISDT_CO_ccpvDZ.dat")

    n_vals_pot = perturbation.r_data.shape[0]
    #

    # ===========================================================================================
    #  Plot potential on the DVR grid points on which the wavefunction is defined
    #  and ALSO the interpolation to check we are using the potential that we mean to.
    #
    if want_to_plot is True:
        print("\n Plot potential ")
        x_Plot = []
        pot_Plot = []
        for j in range(0, n_vals_pot):
            x_Plot.append(np.real(perturbation.r_data[j]))
            pot_Plot.append(np.real(perturbation.V_data[j]))
        plt.suptitle("V(r) interpolation", fontsize=14, fontweight="bold")
        string = "V input points"
        plt.plot(x_Plot, pot_Plot, "ro", label=string)
        #
        x_Plot = []
        pot_Plot = []
        Number_plot_points = 731
        dx = (np.real(fem_dvr.x_pts[fem_dvr.nbas - 1]) -
              np.real(fem_dvr.x_pts[0])) / float(Number_plot_points - 1)
        time = 0.0  # dummy time in general call to potential function
        for j in range(0, Number_plot_points):
            x = np.real(fem_dvr.x_pts[0]) + j * dx
            try:
                x >= perturbation.r_data[0] and x <= perturbation.r_data[
                    n_vals_pot - 1]
            except IndexError:
                print(
                    "Number of plot points is out of range of pertubation data"
                )
            x_Plot.append(x)
            pot_Plot.append(perturbation.V_Interpolated(x, time))

        plt.plot(x_Plot,
                 pot_Plot,
                 "-b",
                 label="Interpolation on DVR grid range")
        plt.legend(loc="best")
        plt.xlabel(" x ", fontsize=14)
        plt.ylabel("V", fontsize=14)
        print(
            "\n Running from terminal, close figure window to proceed and make .pdf file of figure"
        )
        #   Insert limits if necessary
        # xmax = float(rmax)  # CWM: need to use float() to get plt.xlim to work to set x limits
        # plt.xlim([0,xmax])
        # number_string = str(a)
        plt.savefig("Plot_Output/" + "Plot_potential" + ".pdf",
                    transparent=False)
        plt.show()
    #
    #
    # =============Build Hamiltonian (using general routine with dummy time t=0)=========
    #     Pass name of potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian(perturbation.vectorized_V_Interpolated, time)
    print("\n Completed construction of Hamiltonian ")
    # ====================================================================================
    #
    # Find all the eigenvalues of the Hamiltonian so we can compare with known bound state energies
    # or make a plot of the spectrum -- For a time-independent Hamiltonian example here
    #
    EigenVals = LA.eigvalsh(H_mat)
    #
    n_energy = 20
    for i in range(0, n_energy):
        print(
            "E( ",
            i,
            ") =   ",
            EigenVals[i],
            " hartrees, excitation energy = ",
            (EigenVals[i] - EigenVals[0]) * Hartree_to_wavenumber,
            " cm^-1",
        )

    if want_to_plot is True:
        # ====================================================================================
        #
        # Extract the n_Plot'th eigenfunction for plotting and use as initial wave function
        # to test propagation
        #
        number_of_eigenvectors = 20
        #
        #  Here n_Plot picks which eigenfunction to plot
        n_Plot = 10  # pick a state of this potential to plot < number_of_eigenvectors -1
        #
        print(
            "Calculating ",
            number_of_eigenvectors,
            " eigenvectors for plotting eigenfunctions",
        )
        EigenVals, EigenVecs = LA.eigh(H_mat,
                                       eigvals=(0, number_of_eigenvectors))
        wfcnPlot = []
        for j in range(0, fem_dvr.nbas):
            wfcnPlot.append(EigenVecs[j, n_Plot])
        #
        # normalize  wave function from diagonalization
        #
        norm_squared = 0.0
        for j in range(0, fem_dvr.nbas):
            norm_squared = norm_squared + np.abs(wfcnPlot[j])**2
        wfcnPlot = wfcnPlot / np.sqrt(norm_squared)
        norm_squared = 0.0
        for j in range(0, fem_dvr.nbas):
            norm_squared = norm_squared + np.abs(wfcnPlot[j])**2
        print("Norm of wave function being plotted is ", np.sqrt(norm_squared))
        #
        # ================# Plot the  wave function specified by n_Plot above======================
        #   It must be type np.complex for this general wave function plotting logic
        #
        Cinitial = np.zeros((fem_dvr.nbas), dtype=np.complex)
        wfcnInitialPlot = np.zeros((fem_dvr.nbas), dtype=np.complex)
        for j in range(0, fem_dvr.nbas):
            Cinitial[j] = wfcnPlot[j]
        #
        print(
            "\n Plot wave function ",
            n_Plot,
            " (numbered in order of increasing energy)",
        )
        title = "Wavefunction" + str(n_Plot)
        #  note that the dvr.Plot_Psi function makes a .pdf file in the Plot_Output directory
        #  That's what make_plot=True controls.
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Cinitial,
            plot_title_string=title,
            N_plot_points=Number_plot_points,
            make_plot=True,
        )
        # write the data in file also
        for j in range(0, Number_plot_points):
            print(
                x_Plot_array[j],
                "  ",
                np.real(Psi_plot_array[j]),
                "  ",
                np.imag(Psi_plot_array[j]),
                file=wfcn_plotfile,
            )
        print("&  \n ", file=wfcn_plotfile)
        #
        # plot square of wave function (radial probability distribution)
        #
        Csquared = np.zeros(fem_dvr.nbas, dtype=np.complex)
        raverage = 0.0
        for i in range(fem_dvr.nbas):
            Csquared[i] = (Cinitial[i]**2) / np.sqrt(fem_dvr.w_pts[i + 1])
            #   compute <r> for this wave function
            #   note that Cinitial[i] contains the necessary weight, sqrt(dvr.w_pts[i+1])
            raverage = raverage + Cinitial[i]**2 * fem_dvr.x_pts[i + 1]
        print("\n Average value of r using DVR for the integral, <r> = ",
              raverage)
        title = "Wavefunction" + str(n_Plot) + "^2"
        #  note that the dvr.Plot_Psi function makes a .pdf file in the Plot_Output directory
        #  That's what make_plot=True controls.
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Csquared,
            plot_title_string=title,
            N_plot_points=Number_plot_points,
            make_plot=True,
        )
        # write the data in file also
        for j in range(0, Number_plot_points):
            print(
                x_Plot_array[j],
                "  ",
                np.real(Psi_plot_array[j]),
                "  ",
                np.imag(Psi_plot_array[j]),
                file=wfcn_plotfile,
            )
        print("&  \n ", file=wfcn_plotfile)
Ejemplo n.º 3
0
def main(want_to_plot):

    # Making directories for plots and data
    if want_to_plot is True:
        path = os.getcwd()
        Plot_Output = path + "/Plot_Output_2D"

        if os.path.exists(Plot_Output):
            print("Directory for wave function plots already exists",
                  Plot_Output)
        else:
            print(
                "Attempting to create directory for wave function plots ",
                Plot_Output,
            )
            try:
                os.mkdir(Plot_Output)
            except OSError:
                print("Creation of the directory %s failed" % Plot_Output)
            else:
                print("Successfully created the directory %s " % Plot_Output)
        #
        Data_Output = path + "/Data_Output_2D"
        if os.path.exists(Data_Output):
            print("Directory for output .dat files  already exists",
                  Data_Output)
        else:
            print(
                "Attempting to create directory for .dat output files  ",
                Data_Output,
            )
            try:
                os.mkdir(Data_Output)
            except OSError:
                print("Creation of the directory %s failed" % Data_Output)
            else:
                print("Successfully created the directory %s " % Data_Output)
    #%%
    # ===============================FEM_DVR=========================================
    #  Set up the FEM DVR grid given the Finite Element boundaries and order
    #  of Gauss Lobatto quadrature, and compute the Kinetic Energy matrix for
    #  this FEM DVR for Mass = mass set in call (atomic units).
    #
    # Here is where the reduced mass in kinetic energy is set
    Elec_Mass = 1.0
    mu = Elec_Mass
    print("reduced mass mu = ", mu)
    bohr_to_Angstrom = 0.529177
    Hartree_to_eV = 27.211386245988  # NIST ref
    eV_to_wavennumber = 8065.54393734921  # NIST ref on constants + conversions
    Hartree_to_wavenumber = (
        2.1947463136320e5  # value from NIST ref on constants + conversions
    )
    HartreeToKelvin = 315773
    atu_to_fs = 24.18884326509 / 1000
    #
    #  Specify the FEM-DVR grid
    #
    n_order = (
        15  # grid setup here is for calculating bound states of He or H- target
    )
    FEM_boundaries = [0.0, 1.0, 5.0, 10.0, 20.0, 30.0]
    theta_scale = 20
    scale_factor = np.exp(1j * theta_scale * np.pi /
                          180.0)  # scale_factor = 1 (Integer) for no scaling
    R0 = 19.0  # Complex scaling begins at greatest FEM boundary < R0 specified here
    print("ECS scaling by ", theta_scale, " degrees at least FEM boundary > ",
          R0)
    fem_dvr = FEM_DVR(
        n_order,
        FEM_boundaries,
        Mass=mu,
        Complex_scale=scale_factor,
        R0_scale=R0,
    )
    print("\nFEM-DVR basis of ", fem_dvr.nbas, " functions")

    # Initialize perturbations
    pertubation = Potential()

    # =============Build one-electron Hamiltonian ===============================
    #     Pass name of one-body potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian(pertubation.vectorized_V_Coulomb, time)
    print("\n Completed construction of 1D Hamiltonian at t = 0")
    #
    # ========= Calculate two-electron integrals in FEM-DVR basis ===============
    #  These are diagonal in the FEM-DVR basis satisfying
    #     <ij|kl> = delta_ik delta_jl v_ij  See
    #     C W McCurdy et al 2004 J. Phys. B: At. Mol. Opt. Phys. 37 R137
    #
    #  They therefore have the same form as a local potential V(r1,r2)
    #  that would be represented by V(r1_i,r2_j) = v_ij at points on the grid
    #  We pass the matrix v_ij to fem_dvr.Hamiltonian_2D() to build the Hamiltonian
    #  in both cases
    #
    Temkin_Poet = (
        True  # Temkin-Poet model is the radial limit model 1/r12 = 1/r>
    )
    T_mat = 2.0 * fem_dvr.KE_mat
    Two_electron_ints = LA.inv(T_mat)
    R0_ECS = fem_dvr.FEM_boundaries[fem_dvr.i_elem_scale]
    R_max = fem_dvr.FEM_boundaries[len(fem_dvr.FEM_boundaries) - 1]
    if Temkin_Poet:
        print("\n Evaluating two-electron integrals  for Temkin-Poet model")
        print(
            " Grid has R0_ECS = ",
            R0_ECS,
            " R_max ",
            R_max,
            " shape of inverse of KE ",
            Two_electron_ints.shape,
        )
    for i in range(fem_dvr.nbas):
        for j in range(fem_dvr.nbas):
            if Temkin_Poet:
                Two_electron_ints[i, j] = (
                    Two_electron_ints[i, j] /
                    (fem_dvr.x_pts[i + 1] * fem_dvr.x_pts[j + 1] *
                     np.sqrt(fem_dvr.w_pts[i + 1] * fem_dvr.w_pts[j + 1])) +
                    1.0 / R_max)
            else:
                Two_electron_ints[i, j] = pertubation.V_colinear_model(
                    fem_dvr.x_pts[i + 1],
                    fem_dvr.x_pts[j + 1])  # colinear model
    #
    # ============ Build two-electron Hamiltonian ================================
    # pass matrix of two-electron integrals in FEM-DVR or matrix of FEM-DVR
    # representation of local two-electron interaction
    #
    H_mat_2D = fem_dvr.Hamiltonian_2D(H_mat, Two_electron_ints, time)
    print("\n Completed construction of 2D Hamiltonian")
    # =============================================================================
    #
    # ====== Find all the eigenvalues and eigenvectorsof the 2D Hamiltonian =====
    #
    # Here we use LA.eig, which solves the complex generalized eigenvalue & eigenvector problem
    #     (use LA.eigvalsh() for Hermitian matrices)
    #  Called here with arguments right=True for right hand eigenvectors
    #
    print(
        "Calculating ",
        fem_dvr.nbas**2,
        " eigenvalues and eigenvectors of 2D Hamiltonian ",
    )
    EigenVals, EigenVecs = LA.eig(H_mat_2D,
                                  right=True,
                                  homogeneous_eigvals=False)
    print("after LA.eig()")
    #
    # Sort eigenvalues and eigenvectors using np.argsort() function
    #
    print(" shape of EigenVals on return from LA.eig ", EigenVals.shape)
    print(" shape of EigenVecs on return from LA.eig ", EigenVecs.shape)
    size_eigen_system = EigenVals.shape[
        0]  # note shape is an array two long = (2,dimension)
    print("size_eigen_system = ", size_eigen_system)
    real_parts_eigenvals = np.empty(size_eigen_system)
    for i in range(0, size_eigen_system):
        real_parts_eigenvals[i] = np.real(EigenVals[i])
    idx = np.argsort(
        real_parts_eigenvals)  # argsort() returns indices to sort array
    EigenVals = EigenVals[
        idx]  # mysterious python syntax to indirectly address the array
    EigenVecs = EigenVecs[:,
                          idx]  # mysterious python syntax to indirectly address 2D array
    #
    # Make a file with sorted complex eigenvalues
    #
    n_energy = fem_dvr.nbas * fem_dvr.nbas
    n_lowest = 10
    print("\n  Lowest ", n_lowest, " eigenvalues of 2D Hamiltonian")
    for i in range(0, min(n_lowest, n_energy)):
        print(
            "E( ",
            i,
            ") =   ",
            np.real(EigenVals[i]),
            "  ",
            np.imag(EigenVals[i]),
            " i",
            "    hartrees",
        )
    #
    if want_to_plot is True:
        file_opened = open(Data_Output + "/Spectrum_ECS.dat", "w")
        for i in range(0, n_energy):
            print(
                np.real(EigenVals[i]),
                "  ",
                np.imag(EigenVals[i]),
                file=file_opened,
            )
        print(" Complete spectrum, sorted on Re(energy) written to file ")
    #
    # =============  Plot of eigenfunctions of 2D Hamiltonian ==================
    #
    n_state_plot = 31  # n_state_plot = 0 is lowest energy (by real part) state
    print(
        "Plotting eigenstate ",
        n_state_plot,
        " with energy ",
        EigenVals[n_state_plot],
    )
    Coef_vector = []
    for i in range(0, n_energy):
        Coef_vector.append(EigenVecs[i, n_state_plot])
    # normalize vector
    sum = 0.0
    for i in range(0, n_energy):
        sum = sum + np.abs(Coef_vector[i])**2
    Norm_const = 1.0 / np.sqrt(sum)
    for i in range(0, n_energy):
        Coef_vector[i] = Norm_const * Coef_vector[i]
    #
    # ========  Optional plot at FEM-DVR points only without interpolation ============
    #
    Plot_at_DVR_only = False
    if want_to_plot is True:
        if Plot_at_DVR_only:
            print(" making plot data file at DVR grid points")
            plot_file_opened = open(Data_Output + "/wave_function_2D.dat", "w")
            for i in range(1, fem_dvr.nbas + 1):
                for j in range(1, fem_dvr.nbas + 1):
                    print(
                        np.real(fem_dvr.x_pts[i]),
                        "   ",
                        np.real(fem_dvr.x_pts[j]),
                        "   ",
                        np.real(Coef_vector[j + (i - 1) * fem_dvr.nbas - 1] /
                                np.sqrt(fem_dvr.w_pts[i] * fem_dvr.w_pts[j])),
                        file=plot_file_opened,
                    )
                print("    ", file=plot_file_opened)
            exit()
    #
    # =========== Plot of wave function on a grid evenly spaced within FEMs ==============
    #
    if want_to_plot is True:
        plot_pts = 50
        x_Plot_array, y_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi_2D(
            Coef_vector,
            plot_title_string="Real_Psi_on_2D_ECS_grid",
            N_plot_points=plot_pts,
            make_plot=True,
        )

        plot_file_opened = open(Data_Output + "/wave_function_2D.dat", "w")
        print(" Input number of plotting points in each dimension ", plot_pts)
        print(
            "  Actual number of plotting ponts  ",
            len(x_Plot_array),
            " evenly spaced in elements on ECS contour",
        )

        #  write data file for external plotting
        ipt = 0
        plot_pts_on_contour = len(
            x_Plot_array
        )  #  can differ from plot_pts, PLot_Psi_2D puts an integer number in each element
        for i in range(plot_pts_on_contour):
            for j in range(plot_pts_on_contour):
                print(
                    np.real(x_Plot_array[i]),
                    "   ",
                    np.real(y_Plot_array[j]),
                    "   ",
                    np.real(Psi_plot_array[ipt]),
                    file=plot_file_opened,
                )
                ipt = ipt + 1
            print("   ", file=plot_file_opened)
        print(
            " File of points for plot written: ",
            Data_Output + "/wave_function_2D.dat",
        )
def main(number_of_time_intervals, time_step, want_to_plot_animate):
    # ================ Make Directory for Plots if it's not there already =============
    #
    # detect the current working directory and print it
    path = os.getcwd()
    print("The current working directory is %s" % path)
    # define the name of the directory to be created
    Plot_Output = path + "/Plot_Output"
    if want_to_plot_animate is True:
        if os.path.exists(Plot_Output):
            print("Directory for wave function plots already exists",
                  Plot_Output)
        else:
            print(
                "Attempting to create directory for wave function plots ",
                Plot_Output,
            )
            try:
                os.mkdir(Plot_Output)
            except OSError:
                print("Creation of the directory %s failed" % Plot_Output)
            else:
                print("Successfully created the directory %s " % Plot_Output)
    # =====================================FEM_DVR===================================
    #  Set up the FEM DVR grid given only the Finite Element boundaries and order
    #  of Gauss Lobatto quadrature,  and compute the Kinetic Energy matrix for
    #  this FEM DVR for Mass = mass set in call (atomic units).
    #
    massH = 1836.0
    mu = massH / 2.0
    n_order = 30  # 40th order with finite elements of 1 or 2 bohr OK for light masses here
    FEM_boundaries = [0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 8.0]
    #
    # constants and conversions
    #
    Daltons_to_eMass = 1822.89
    bohr_to_Angstrom = 0.529177
    Hartree_to_wavenumber = 8065.54 * 27.211
    Hartree_to_eV = 27.211
    atu_to_fs = 24.18884 / 1000.0
    c_light = 137.035999084  # speed of light in atomic units
    a0_to_m = bohr_to_Angstrom * 10**(-10)
    I_0 = (
        3.5095 * 10**16
    )  # conversion for intensity in W/cm^2 I =I_0*(A_0*omega/c_light)**2
    #
    print("Mass = ", mu)
    fem_dvr = FEM_DVR(n_order, FEM_boundaries, Mass=mu)
    print("\nFEM-DVR basis of ", fem_dvr.nbas, " functions")
    #
    #  pulse parameters for the dipole coupling between the two
    #  electronic states
    #
    T_pulse = 3.0  # set in femtoseconds
    omega = 0.2  # central frequency 0.113903 hartrees  is 400 nm
    A_0 = 10.0  # strength of A field 6.422 atomic units is 10^12 W/cm^2 for 400 nm
    print("\n\n*** Radiation Pulse ***")
    print(" Duration = ", T_pulse, " fs,  ")
    print(
        " Intensity of pulse  = ",
        "%15.3e" % (I_0 * (A_0 * omega / c_light)**2),
        " W/cm^2",
    )
    print(
        " Central energy = ",
        omega * Hartree_to_eV,
        " eV,  lambda = ",
        (10**9) * a0_to_m * 2 * np.pi * c_light / omega,
        " nm",
    )

    def E_field(time):
        #
        #  Definition of E in - mu E(t) radiative coupling between electronic states
        #  One pulse here, followed by A = 0, durations and other parameters set in main program
        #
        if 0 <= time * atu_to_fs <= T_pulse:
            T = T_pulse / atu_to_fs  # pulse duration  converted from fs
            field = ((A_0 * omega / c_light) * (np.sin(np.pi * time / T)**2) *
                     np.cos(omega * time - omega * T / 2))
        else:
            field = 0.0
        return field

    def Vcoupling(x, time):
        #
        #  mu_dipole * E(t)
        #
        field = E_field(time)
        mu_dipole = 1.0  # assumed value of dipole matrix element
        coupling = mu_dipole * field
        return coupling

    def Vzero(x, time):
        Vcoupzero = 0.0
        return Vcoupzero

    pertubation = Potential()

    # =============Build Hamiltonian without coupling to pick an initial state=========
    #     Pass name of potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian_Two_States(pertubation.V_morse_1,
                                           pertubation.V_morse_2, Vzero, time)
    print("\n Completed construction of Hamiltonian at t = 0")
    # ====================================================================================
    #
    # Find all the eigenvalues of the Hamiltonian so we can compare with known bound state energies
    # or make a plot of the spectrum -- For a time-independent Hamiltonian example here
    #
    EigenVals_diabatic = LA.eigvalsh(H_mat)
    # ====================================================================================
    #
    # Print the lowest few eigenvalues as a check, and help pick an initial state in one diabatic well
    print(
        "\nEigenvalues of the uncoupled Hamiltonians for the two potential curves"
    )
    print("n      E_uncoupled")
    for i in range(0, 50):
        print(i, "    ", EigenVals_diabatic[i])
    #
    # ===========================================================================================
    #  Plot potential on the DVR grid points on which the wavefunction is defined
    time = (1 / 2) * T_pulse / atu_to_fs  # Maximum of pulse
    #
    print("\n Plot potential ")
    x_Plot = []
    pot1_Plot = []
    pot2_Plot = []
    coup_Plot = []
    for j in range(0, fem_dvr.nbas):
        x_Plot.append(fem_dvr.x_pts[j + 1])
        pot1_Plot.append(pertubation.V_morse_1(fem_dvr.x_pts[j + 1], time))
        pot2_Plot.append(pertubation.V_morse_2(fem_dvr.x_pts[j + 1], time))
        coup_Plot.append(10.0 * Vcoupling(fem_dvr.x_pts[j + 1], time))
    if want_to_plot_animate is True:
        plt.suptitle(
            "Uncoupled potentials and max value of coupling",
            fontsize=14,
            fontweight="bold",
        )
        string = "V1"
        # plt.plot(x_Plot,pot1_Plot,'ro',label=string) # plot points only
        plt.plot(x_Plot, pot1_Plot, "-r", label=string)
        string = "V2"
        plt.plot(x_Plot, pot2_Plot, "-b", label=string)
        string = "Max Vcoup x 10"
        plt.plot(x_Plot, coup_Plot, "-g", label=string)
        plt.legend(loc="best")
        plt.xlabel(" x (bohr)", fontsize=14)
        plt.ylabel("V (hartrees)", fontsize=14)
        print(
            "\n Running from terminal, close figure window to proceed and make .pdf file of figure"
        )
        #   Insert limits if necessary
        # xmax = float(rmax)  # CWM: need to use float() to get plt.xlim to work to set x limits
        # plt.xlim([0,xmax])
        plt.ylim([-0.2, 0.2])
        # number_string = str(a)
        plt.savefig(
            "Plot_Output/" + "Plot_two_state_potentials" + ".pdf",
            transparent=False,
        )
        plt.show()
    #
    # ====================================================================================
    #
    # Extract the n_Plot'th eigenfunction of uncoupled Hamiltonians for plotting
    #
    #
    np1 = 20
    number_of_eigenvectors = np1
    #  Initial wave function chosen here -- change n_Plot to change it
    n_Plot = 0  # pick one of the bound states of Potential to plot and use as initial state below
    print("Calculating ", np1, " eigenvectors for plotting eigenfunctions")
    EigenVals = []
    EigenVals, EigenVecs = LA.eigh(H_mat, eigvals=(0, number_of_eigenvectors))
    wfcnPlot = []
    for j in range(0, 2 * fem_dvr.nbas):
        wfcnPlot.append(EigenVecs[j, n_Plot])
    #
    # normalize 2 component wave function from diagonalization
    # so that it has the right overall normalization
    #
    norm_squared = 0.0
    for j in range(0, 2 * fem_dvr.nbas):
        norm_squared = norm_squared + np.abs(wfcnPlot[j])**2
    wfcnPlot = wfcnPlot / np.sqrt(norm_squared)
    norm_squared = 0.0
    for j in range(0, 2 * fem_dvr.nbas):
        norm_squared = norm_squared + np.abs(wfcnPlot[j])**2
    print("Norm of wave function being plotted is ", np.sqrt(norm_squared))
    # now extract the two components
    wfcnPlot1 = []
    wfcnPlot2 = []
    for j in range(0, fem_dvr.nbas):
        wfcnPlot1.append(wfcnPlot[j, ])
        wfcnPlot2.append(wfcnPlot[j + fem_dvr.nbas])
    #
    # Uncomment to both components of the eigenfunction.  For uncoupled Hamiltonians
    # only one component will be nonzero for each eigenfunction.
    #
    # x_Plot_array, Psi1_plot_array, Psi2_plot_array = fem_dvr.Plot_Psi_Two_States(wfcnPlot1, wfcnPlot2,plot_title_string="psi_1 and psi_2  with n ="+str(n_Plot),N_plot_points=750,make_plot=True)
    #
    #
    # =============Build Hamiltonian at t=0==============================================
    #     Pass name of potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian_Two_States(pertubation.V_morse_1,
                                           pertubation.V_morse_2, Vcoupling,
                                           time)
    print("\n Completed construction of Hamiltonian at t = 0")
    #
    # ================# Initialize wave function at t = 0 ================================
    #
    #  Bound state of one of the two uncoupled Hamiltonians chosen as initial state
    #
    Cinitial = np.zeros(2 * fem_dvr.nbas, dtype=np.complex)
    for i in range(0, 2 * fem_dvr.nbas):
        Cinitial[i] = (
            wfcnPlot[i] + 0.0 * 1j
        )  # this loop was necessary to make Cinitial an array of complex numbers
    wfcnInitialPlot1 = np.zeros(fem_dvr.nbas, dtype=np.complex)
    wfcnInitialPlot2 = np.zeros(fem_dvr.nbas, dtype=np.complex)
    norm_1 = 0.0
    norm_2 = 0.0
    for j in range(0, fem_dvr.nbas):
        wfcnInitialPlot1[j] = Cinitial[j]
        wfcnInitialPlot2[j] = Cinitial[j + fem_dvr.nbas]
        norm_1 = norm_1 + np.abs(wfcnInitialPlot1[j])**2
        norm_2 = norm_2 + np.abs(wfcnInitialPlot2[j])**2
    #
    # plot initial wave packet
    #
    tinitial = 0.0
    x_Plot_time_array = []
    Psi1_plot_time_array = []
    Psi2_plot_time_array = []
    if want_to_plot_animate is True:
        print("\n Plot initial wave function at t = ", tinitial)
        number_string = str(0.0)
        title = "Wavefunction at t = " + number_string
        (
            x_Plot_array,
            Psi1_plot_array,
            Psi2_plot_array,
        ) = fem_dvr.Plot_Psi_Two_States(
            wfcnInitialPlot1,
            wfcnInitialPlot2,
            plot_title_string=title,
            N_plot_points=750,
            make_plot=True,
        )
        x_Plot_time_array.append(x_Plot_array)
        Psi1_plot_time_array.append(Psi1_plot_array)
        Psi2_plot_time_array.append(Psi2_plot_array)
    #
    # initialize arrays for storing the information for all the frames in the animation of
    # the propagation of the wave function
    times_array = []
    norm_1_array = []
    norm_2_array = []
    times_array.append(tinitial)
    norm_1_array.append(norm_1)
    norm_2_array.append(norm_2)
    #
    # =====================Propagation from t = tinitial to tfinal ============================
    # specify final time and specify number of intervals for plotting
    #
    t_final_propagation = 15.0  # femtoseconds
    tfinal = t_final_propagation / atu_to_fs
    print(
        "\nOverall propagation will be from ",
        tinitial,
        " to ",
        tfinal,
        " = ",
        tfinal * 24.189 / 1000.0,
        "fs in ",
        number_of_time_intervals,
        " intervals",
    )
    #
    #  Loop over n_intervals intervals that make up t = 0 to tfinal
    #
    t_interval = (tfinal - tinitial) / number_of_time_intervals
    time_step = (
        0.1  # atomic time units  -- denser grids require smaller time steps
    )
    for i_time_interval in range(0, number_of_time_intervals):
        t_start = tinitial + i_time_interval * t_interval
        t_finish = tinitial + (i_time_interval + 1) * t_interval
        N_time_steps = np.int(t_interval / time_step)
        Delta_t = t_interval / np.float(N_time_steps)
        #
        # Check norm of initial wavefunction
        #
        norm_initial = 0.0
        norm_1 = 0.0
        for j in range(0, 2 * fem_dvr.nbas):
            norm_initial = norm_initial + np.abs(Cinitial[j])**2
            if j < fem_dvr.nbas:
                norm_1 = norm_1 + np.abs(Cinitial[j])**2
        norm_2 = norm_initial - norm_1
        print(
            "\nNorm of starting  wave function",
            norm_initial,
            " norm on state 1 ",
            norm_1,
            " norm on state 2 ",
            norm_2,
        )
        #
        #  use the propagator from DVR()
        #
        clock_start = timeclock.time()
        if t_start * atu_to_fs < T_pulse:
            Ctfinal = fem_dvr.Crank_Nicolson_Two_States(
                t_start,
                t_finish,
                N_time_steps,
                Cinitial,
                pertubation.V_morse_1,
                pertubation.V_morse_2,
                Vcoupling,
                Is_H_time_dependent=True,
            )
        else:
            Ctfinal = fem_dvr.Crank_Nicolson_Two_States(
                t_start,
                t_finish,
                N_time_steps,
                Cinitial,
                pertubation.V_morse_1,
                pertubation.V_morse_2,
                Vzero,
                Is_H_time_dependent=False,
            )

        clock_finish = timeclock.time()
        print(
            "Time for ",
            N_time_steps,
            " Crank-Nicolson steps was ",
            clock_finish - clock_start,
            " secs.",
        )
        #
        # Check norms of initial and final wavefunctions
        #
        norm_final = 0
        norm_1 = 0.0
        for j in range(0, 2 * fem_dvr.nbas):
            norm_final = norm_final + np.abs(Ctfinal[j])**2
            if j < fem_dvr.nbas:
                norm_1 = norm_1 + np.abs(Ctfinal[j])**2
        norm_2 = norm_final - norm_1
        print(
            "Norm of final  wave function",
            norm_final,
            " norm on state 1 ",
            norm_1,
            " norm on state 2 ",
            norm_2,
        )
        #
        # Plot of packet at end of each interval using Plot_Psi from DVR()
        #  "False" returns the values but doesn't make a plot, so the values
        #  can be used in the animation.
        #
        print(
            "Plot function propagated to t = ",
            t_finish,
            " atomic time units = ",
            t_finish * 24.189 / 1000.0,
            " fs",
        )
        number_string = str(t_finish)
        title = "Wavefunction at t = " + number_string

        wfcn_at_t_Plot1 = np.zeros(fem_dvr.nbas, dtype=np.complex)
        wfcn_at_t_Plot2 = np.zeros(fem_dvr.nbas, dtype=np.complex)
        for j in range(0, fem_dvr.nbas):
            wfcn_at_t_Plot1[j] = Ctfinal[j]
            wfcn_at_t_Plot2[j] = Ctfinal[j + fem_dvr.nbas]
        # plot using the Plot_Psi_Two_States function in DVRHelper, returns two
        # components of wave function separately
        if want_to_plot_animate is True:
            (
                x_Plot_array,
                Psi1_plot_array,
                Psi2_plot_array,
            ) = fem_dvr.Plot_Psi_Two_States(
                wfcn_at_t_Plot1,
                wfcn_at_t_Plot2,
                plot_title_string=title,
                N_plot_points=750,
                make_plot=False,
            )
            times_array.append(t_finish)
            norm_1_array.append(norm_1)
            norm_2_array.append(norm_2)
            x_Plot_time_array.append(x_Plot_array)
            Psi1_plot_time_array.append(Psi1_plot_array)
            Psi2_plot_time_array.append(Psi2_plot_array)
        #
        #   reset initial packet as final packet for this interval
        for i in range(0, 2 * fem_dvr.nbas):
            Cinitial[i] = Ctfinal[i]
        #
        if want_to_plot_animate is True:
            (
                x_Plot_array,
                Psi1_plot_array,
                Psi2_plot_array,
            ) = fem_dvr.Plot_Psi_Two_States(
                wfcn_at_t_Plot1,
                wfcn_at_t_Plot2,
                plot_title_string=title,
                N_plot_points=750,
                make_plot=True,
            )
            print(
                "Final frame at tfinal = ",
                tfinal,
                "atomic time units  is showing -- ready to prepare animation ",
            )
            print("\n **** close the plot window to proceed ****")
            #
            # print a file with the norms of the components of the wave function as a function of time
            #
            norms_file = open("./Plot_Output/Wavepacket_norms.dat", "w")
            for k in range(0, number_of_time_intervals):
                norms_file.write(
                    "%12.8f %12.8f %12.8f\n" %
                    (times_array[k], norm_1_array[k], norm_2_array[k]))
            norms_file.close()

    # ==============================================================================
    # # initialization function: plot the background of each frame
    # ==============================================================================
    def init():
        line.set_data([], [])
        time_text.set_text("")
        ax.set_xlabel(" x (bohr) ", fontsize=16, fontweight="bold")
        ax.set_ylabel(" |Psi_1(t)| and |Psi_2(t)| ",
                      fontsize=16,
                      fontweight="bold")
        fig.suptitle("Both Components of Wave Packet",
                     fontsize=16,
                     fontweight="bold")
        ax.plot([x_Plot[0], x_Plot[len(x_Plot) - 1]], [0, 0],
                "k")  # put in a line at the value Phi = 0
        return line, time_text

    nframes = number_of_time_intervals

    def animate(i):
        # ==============================================================================
        #
        #  commands to specify changing elements of the animation
        #  We precomputed all the lines at all the times so that this
        #  wouldn't recompute everything at each frame, which is very slow...
        #
        # ==============================================================================
        time_string = str(times_array[i] * 24.189 /
                          1000.0)  # string for a plot label
        #    re_array = np.real(Psi1_plot_time_array[i])
        #    im_array = np.imag(Psi1_plot_time_array[i])
        abs_array1 = np.abs(Psi1_plot_time_array[i])
        abs_array2 = np.abs(Psi2_plot_time_array[i])
        line1.set_data(x_Plot_array, abs_array1)
        line2.set_data(x_Plot_array, abs_array2)
        #    line3.set_data(x_Plot_array,abs_array)
        time_text.set_text("time = " + time_string + " fs")
        #    return (line1,line2,line3,time_text)
        return (line1, line2, time_text)

    if want_to_plot_animate is True:
        # ==============================================================================
        #  Now plot the animation
        # ==============================================================================
        # reinitialize the figure for the next plot which is the animation
        fig = plt.figure()
        ymax = 2.0
        xmin = x_Plot[0]
        xmax = x_Plot[len(x_Plot) - 1]
        ax = fig.add_subplot(111,
                             autoscale_on=False,
                             xlim=(xmin, xmax),
                             ylim=(0, +ymax))
        (line, ) = ax.plot([], [], "-r", lw=2)
        (line1, ) = ax.plot([], [], "-r", lw=2)
        (line2, ) = ax.plot([], [], "-b", lw=2)
        # line3, = ax.plot([], [], 'k',lw=2)
        # define the object that will be the time printed on each frame
        time_text = ax.text(0.02, 0.95, "", transform=ax.transAxes)

        # ==============================================================================
        # call the animator.  blit=True means only re-draw the parts that have changed.
        # ==============================================================================
        anim = animation.FuncAnimation(
            fig,
            animate,
            init_func=init,
            frames=nframes + 1,
            interval=200,
            blit=True,
            repeat=True,
        )
        # ==============================================================================
        #  show the animation
        # ==============================================================================
        anim.save("Non_adiabatic_coupled_Morse_pots.mp4")
        plt.show()
        print("done")

    if want_to_plot_animate is False:
        print(
            "\n\nSet the command line option want_to_plot_animate=True to see figures, "
            "animation, and create plotting directory.\n\n")
def main(want_to_plot):
    #
    # ================ Make Directory for Plots if it's not there already =============
    #
    # detect the current working directory and print it
    path = os.getcwd()
    print("The current working directory is %s" % path)
    # define the name of the directory to be created
    Plot_Output = path + "/Plot_Output"

    if want_to_plot == True:
        if os.path.exists(Plot_Output):
            print(
                "Directory for wave function plots already exists", Plot_Output
            )
        else:
            print(
                "Attempting to create directory for wave function plots ",
                Plot_Output,
            )
            try:
                os.mkdir(Plot_Output)
            except OSError:
                print("Creation of the directory %s failed" % Plot_Output)
            else:
                print("Successfully created the directory %s " % Plot_Output)
    # =====================================FEM_DVR===================================
    #  Set up the FEM DVR grid given only the Finite Element boundaries and order
    #  of Gauss Lobatto quadrature,  and compute the Kinetic Energy matrix for
    #  this FEM DVR for Mass = mass set in call (atomic units).
    #
    # Here is where the reduced mass is set
    # H_Mass = 1.007825032 #H atom atomic mass
    # H_Mass = 1.00727647  #proton atomic mass
    # standard atomic weight natural abundance, seems to be what Turner, McCurdy used
    H_Mass = 1.0078
    Daltons_to_eMass = 1822.89
    mu = (H_Mass / 2.0) * Daltons_to_eMass
    print("reduced mass mu = ", mu)
    bohr_to_Angstrom = 0.529177
    Hartree_to_eV = 27.211386245988  # NIST ref
    eV_to_wavennumber = 8065.54393734921  # NIST ref on constants + conversions
    # value from NIST ref on constants + conversions
    Hartree_to_wavenumber = 2.1947463136320e5
    HartreeToKelvin = 315773
    atu_to_fs = 24.18884326509 / 1000
    #  Set up the FEM-DVR grid
    n_order = 25
    FEM_boundaries = [
        0.4,
        1.0,
        2.0,
        3.0,
        4.0,
        5.0,
        6.0,
        7.0,
        8.0,
        10.0,
        15.0,
        20.0,
        25.0,
        30.0,
        50,
    ]
    # parameters for Fig 2 in Turner, McCurdy paper
    # Julia Turner and C. William McCurdy, Chemical Physics 71(1982) 127-133
    scale_factor = np.exp(1j * 37.0 * np.pi / 180.0)
    R0 = 22.75
    fem_dvr = FEM_DVR(
        n_order,
        FEM_boundaries,
        Mass=mu,
        Complex_scale=scale_factor,
        R0_scale=R0,
    )
    print("\nFEM-DVR basis of ", fem_dvr.nbas, " functions")
    #

    pertubation = Potential()
    # ==================================================================================
    #  Plot potential on the DVR grid points on which the wavefunction is defined

    if want_to_plot is True:
        print("\n Plot potential ")
        print("Test V", pertubation.V_Bernstein(5.0 + 0.0 * 1j, 0.0))
        print("Test V", pertubation.V_Bernstein(10.0 + 0.5 * 1j, 0.0))

    time = 0.0
    x_Plot = []
    pot_Plot = []
    for j in range(0, fem_dvr.nbas):
        x_Plot.append(np.real(fem_dvr.x_pts[j + 1]))
        pot_Plot.append(
            np.real(pertubation.V_Bernstein(fem_dvr.x_pts[j + 1], time))
        )

    if want_to_plot is True:
        plt.suptitle(
            "V(x) at DVR basis function nodes", fontsize=14, fontweight="bold"
        )
        string = "V"
        plt.plot(x_Plot, pot_Plot, "ro", label=string)
        plt.plot(x_Plot, pot_Plot, "-b")
        plt.legend(loc="best")
        plt.xlabel(" x ", fontsize=14)
        plt.ylabel("V", fontsize=14)
        print(
            "\n Running from terminal, close figure window to proceed and make .pdf file of figure"
        )
        #   Insert limits if necessary
        #   Generally comment this logic.  Here I am matching the Turner McCurdy Figure 2
        # CWM: need to use float() to get plt.xlim to work to set x limits
        ymax = float(0.05)
        plt.ylim([-0.18, ymax])
        # save plot to .pdf file
        plt.savefig(
            "Plot_Output/" + "Plot_potential" + ".pdf", transparent=False
        )
        plt.show()
    #
    # =============Build Hamiltonian (at t=0 if time-dependent)=================================
    #     Pass name of potential function explicitly here
    time = 0.0
    H_mat = fem_dvr.Hamiltonian(pertubation.vectorized_V_Bernstein, time)
    print("\n Completed construction of Hamiltonian at t = 0")
    # ====================================================================================
    #
    # Find all the eigenvalues of the Hamiltonian so we can compare with known bound state energies
    # or make a plot of the spectrum -- For a time-independent Hamiltonian example here
    #
    # EigenVals = LA.eigvals(H_mat) # eigenvalues only for general matrix.  LA.eigvalsh for Hermitian
    print(
        "Calculating ",
        fem_dvr.nbas,
        " eigenvalues and eigenvectors for plotting eigenfunctions",
    )
    EigenVals, EigenVecs = LA.eig(H_mat, right=True, homogeneous_eigvals=True)
    print("after LA.eig()")
    #
    n_energy = fem_dvr.nbas
    file_opened = open("Spectrum_ECS.dat", "w")
    print("EigenVals shape ", EigenVals.shape)
    for i in range(0, n_energy):
        print("E( ", i, ") =   ", EigenVals[0, i], " hartrees")
        print(
            np.real(EigenVals[0, i]),
            "  ",
            np.imag(EigenVals[0, i]),
            file=file_opened,
        )
    # ====================================================================================
    #
    # Extract the n_Plot'th eigenfunction for plotting
    #
    # pick one of the bound states of Morse Potential to plot
    # numbering can depend on numpy and python installation that determines
    # behavior of the linear algebra routines.
    n_Plot = (
        n_energy - 1
    )  # This is generally the highest energy continuum eigenvalue
    n_Plot = 292
    wfcnPlot = []
    for j in range(0, fem_dvr.nbas):
        wfcnPlot.append(EigenVecs[j, n_Plot])
    #
    # Normalize  wave function from diagonalization
    # using integration of square on the contour
    # following the original Rescigno McCurdy idea for partial widths
    # from the paper
    # "Normalization of resonance wave functions and the calculation of resonance widths"
    #  Rescigno, McCurdy, Phys Rev A 34,1882 (1986)
    norm_squared = 0.0
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2
    wfcnPlot = wfcnPlot / np.sqrt(norm_squared)
    norm_squared = 0.0
    gamma_residue = 0.0
    # background momentum defined with Re(Eres)
    k_momentum = np.sqrt(2.0 * mu * np.real(EigenVals[0, n_Plot]))
    # k_momentum = np.real(np.sqrt(2.0*mu*EigenVals[0,n_Plot]))
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2
        if fem_dvr.x_pts[j + 1] < 5.8:
            free_wave = (2.0 * np.sqrt(mu / k_momentum)) * np.sin(
                k_momentum * fem_dvr.x_pts[j + 1]
            )
            gamma_residue = gamma_residue + wfcnPlot[
                j
            ] * pertubation.V_Bernstein(
                fem_dvr.x_pts[j + 1], time
            ) * free_wave * np.sqrt(
                fem_dvr.w_pts[j + 1]
            )
    print("Complex symmetric inner product (psi|psi) is being used")
    if want_to_plot is True:
        print(
            "Norm of wave function from int psi^2 on contour being plotted is ",
            np.sqrt(norm_squared),
        )
    print(" For this state the asymptotic value of k = ", k_momentum)
    print(
        "gamma from int = ",
        gamma_residue,
        " |gamma|^2 = ",
        np.abs(gamma_residue) ** 2,
    )
    # Plot wave function -- It must be type np.complex
    Cinitial = np.zeros((fem_dvr.nbas), dtype=np.complex)
    wfcnInitialPlot = np.zeros((fem_dvr.nbas), dtype=np.complex)
    for j in range(0, fem_dvr.nbas):
        Cinitial[j] = wfcnPlot[j]

    if want_to_plot is True:
        #
        # plot n_Plot'th eigenfunction
        #
        print(
            "\n Plot Hamiltonian eigenfunction number ",
            n_Plot,
            " with energy ",
            EigenVals[0, n_Plot],
        )
        tau = atu_to_fs / (-2.0 * np.imag(EigenVals[0, n_Plot]))
        print("  Lifetime tau = 1/Gamma = ", tau, " fs")
        number_string = str(n_Plot)
        title = "Wavefunction number = " + number_string
        wfn_plot_points = 2000
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Cinitial,
            plot_title_string=title,
            N_plot_points=wfn_plot_points,
            make_plot=True,
        )
        #
        #  Make data file for n_Plot'th eigenfunction
        #  Psi and the integrand of the residue factors, gamma, of the free-free matrix element
        #  of the full Green's function, as per
        #
        filename = "wavefunction" + number_string + ".dat"
        file_opened = open(filename, "w")
        print_points = len(x_Plot_array)
        print("x_Plot_array shape ", print_points)
        for i in range(print_points):
            free_wave = (2.0 * np.sqrt(mu / k_momentum)) * np.sin(
                k_momentum * x_Plot_array[i]
            )
            # for partial width gamma
            integrand = (
                Psi_plot_array[i]
                * pertubation.V_Bernstein(x_Plot_array[i], time)
                * free_wave
            )
            print(
                np.real(x_Plot_array[i]),
                "  ",
                np.imag(x_Plot_array[i]),
                "  ",
                np.real(Psi_plot_array[i]),
                "  ",
                np.imag(Psi_plot_array[i]),
                "  ",
                np.real(integrand),
                np.imag(integrand),
                file=file_opened,
            )
    #
    # exit()

    # ====================================================================================
    #
    # Extract the n_Plot'th eigenfunction for plotting
    #
    # pick one of the eigenstates of Potential to plot
    # numbering can depend on numpy and python installation that determines
    # behavior of the linear algebra routines.
    n_Plot = 292

    if want_to_plot is True:
        print(
            "Calculating ",
            fem_dvr.nbas,
            " eigenvectors for plotting eigenfunctions",
        )
    EigenVals2, EigenVecs = LA.eig(H_mat, right=True, homogeneous_eigvals=True)
    wfcnPlot = []
    for j in range(0, fem_dvr.nbas):
        wfcnPlot.append(EigenVecs[j, n_Plot])
    #
    # normalize  wave function from diagonalization
    # using integration of square on the contour
    # following the old Rescigno McCurdy idea for partial widths
    norm_squared = 0.0
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2
    wfcnPlot = wfcnPlot / np.sqrt(norm_squared)
    norm_squared = 0.0
    for j in range(0, fem_dvr.nbas):
        norm_squared = norm_squared + (wfcnPlot[j]) ** 2

    if want_to_plot is True:
        print(
            "Norm of wave function from int psi^2 on contour being plotted is ",
            np.sqrt(norm_squared),
        )
    # Plot wave function -- It must be type np.complex
    Cinitial = np.zeros((fem_dvr.nbas), dtype=np.complex)
    wfcnInitialPlot = np.zeros((fem_dvr.nbas), dtype=np.complex)
    for j in range(0, fem_dvr.nbas):
        Cinitial[j] = wfcnPlot[j]

    if want_to_plot is True:
        #
        # plot n_Plot'th eigenfunction
        #
        print(
            "\n Plot Hamiltonian eigenfunction number ",
            n_Plot,
            " with energy ",
            EigenVals[0, n_Plot],
        )
        number_string = str(n_Plot)
        title = "Wavefunction number = " + number_string
        wfn_plot_points = 1000
        x_Plot_array, Psi_plot_array = fem_dvr.Plot_Psi(
            Cinitial,
            plot_title_string=title,
            N_plot_points=wfn_plot_points,
            make_plot=True,
        )
        #
        filename = "wavefunction" + number_string + ".dat"
        file_opened = open(filename, "w")
        print_points = len(x_Plot_array)
        # print("x_Plot_array shape ",print_points)
        for i in range(print_points):
            print(
                np.real(x_Plot_array[i]),
                "  ",
                np.imag(x_Plot_array[i]),
                "  ",
                np.real(Psi_plot_array[i]),
                "  ",
                np.imag(Psi_plot_array[i]),
                file=file_opened,
            )

    if want_to_plot is False:
        print(
            "\n\n Set the command line option want_to_plot=True to see figures and create plotting directory.\n\n"
        )