Search code examples
pythonplotlinear-algebrasympysymbolic-math

How to plot eigenvalues representing symbolic functions in Python?


I need to calculate the eigenvalues of an 8x8-matrix and plot each of the eigenvalues for a symbolic variable occuring in the matrix. For the matrix I'm using I get 8 different eigenvalues where each is representing a function in "W", which is my symbolic variable.

Using python I tried calculating the eigenvalues with Scipy and Sympy which worked kind of, but the results are stored in a weird way (at least for me as a newbie not understanding much of programming so far) and I didn't find a way to extract just one eigenvalue in order to plot it.

import numpy as np
import sympy as sp

W = sp.Symbol('W')

w0=1/780
wl=1/1064

# This is my 8x8-matrix
A= sp.Matrix([[w0+3*wl, 2*W, 0, 0, 0, np.sqrt(3)*W, 0, 0],
    [2*W, 4*wl, 0, 0, 0, 0, 0, 0],
    [0, 0, 2*wl+w0, np.sqrt(3)*W, 0, 0, 0, np.sqrt(2)*W],
    [0, 0, np.sqrt(3)*W, 3*wl, 0, 0, 0, 0],
    [0, 0, 0, 0, wl+w0, np.sqrt(2)*W, 0, 0],
    [np.sqrt(3)*W, 0, 0, 0, np.sqrt(2)*W, 2*wl, 0, 0],
    [0, 0, 0, 0, 0, 0, w0, W],
    [0, 0, np.sqrt(2)*W, 0, 0, 0, W, wl]])

# Calculating eigenvalues
eva = A.eigenvals()
evaRR = np.array(list(eva.keys()))
eva1p = evaRR[0]   # <- this is my try to refer to the first eigenvalue

In the end I hope to get a plot over "W" where the interesting range is [-0.002 0.002]. For the ones interested it's about atomic physics and W refers to the rabi frequency and I'm looking at so called dressed states.


Solution

  • You're not doing anything incorrectly -- I think you're just caught up since your eigenvalues look so jambled and complicated.

    import numpy as np
    import sympy as sp
    import matplotlib.pyplot as plt
    
    W = sp.Symbol('W')
    
    w0=1/780
    wl=1/1064
    
    # This is my 8x8-matrix
    A= sp.Matrix([[w0+3*wl, 2*W, 0, 0, 0, np.sqrt(3)*W, 0, 0],
        [2*W, 4*wl, 0, 0, 0, 0, 0, 0],
        [0, 0, 2*wl+w0, np.sqrt(3)*W, 0, 0, 0, np.sqrt(2)*W],
        [0, 0, np.sqrt(3)*W, 3*wl, 0, 0, 0, 0],
        [0, 0, 0, 0, wl+w0, np.sqrt(2)*W, 0, 0],
        [np.sqrt(3)*W, 0, 0, 0, np.sqrt(2)*W, 2*wl, 0, 0],
        [0, 0, 0, 0, 0, 0, w0, W],
        [0, 0, np.sqrt(2)*W, 0, 0, 0, W, wl]])
    
    # Calculating eigenvalues
    eva = A.eigenvals()
    evaRR = np.array(list(eva.keys()))
    # The above is copied from your question
    # We have to answer what exactly the eigenvalue is in this case
    print(type(evaRR[0])) # >>> Piecewise
    # Okay, so it's a piecewise function (link to documentation below).
    # In the documentation we see that we can use the .subs method to evaluate
    # the piecewise function by substituting a symbol for a value. For instance,
    
    print(evaRR[0].subs(W, 0)) # Will substitute 0 for W
    # This prints out something really nasty with tons of fractions.. 
    # We can evaluate this mess with sympy's numerical evaluation method, N
    print(sp.N(evaRR[0].subs(W, 0))) 
    # >>> 0.00222190090611143 - 6.49672880062804e-34*I
    
    # That's looking more like it! Notice the e-34 exponent on the imaginary part...
    # I think it's safe to assume we can just trim that off.
    # This is done by setting the chop keyword to True when using N:
    print(sp.N(evaRR[0].subs(W, 0), chop=True)) # >>> 0.00222190090611143
    
    
    # Now let's try to plot each of the eigenvalues over your specified range
    fig, ax = plt.subplots(3, 3) # 3x3 grid of plots (for our 8 e.vals)
    ax = ax.flatten() # This is so we can index the axes easier
    
    plot_range = np.linspace(-0.002, 0.002, 10) # Range from -0.002 to 0.002 with 10 steps
    for n in range(8):
        current_eigenval = evaRR[n]
        # There may be a way to vectorize this computation, but I'm not familiar enough with sympy.
        evaluated_array = np.zeros(np.size(plot_range))
        # This will be our Y-axis (or W-value). It is set to be the same shape as
        # plot_range and is initally filled with all zeros.
    
        for i in range(np.size(plot_range)):
            evaluated_array[i] = sp.N(current_eigenval.subs(W, plot_range[i]), 
                                      chop=True)
            # The above line is evaluating your eigenvalue at a specific point,
            # approximating it numerically, and then chopping off the imaginary.
        ax[n].plot(plot_range, evaluated_array, "c-")
        ax[n].set_title("Eigenvalue #{}".format(n))
        ax[n].grid()
    
    plt.tight_layout()
    plt.show()
    

    Result

    And as promised, the Piecewise documentation.