Analysis and pplotting of KKR calculations¶

  • Jij extraction from normal state
  • YSR states in calculated superconducting DOS

Used Python and AiiDA versions¶

  • AiiDA v2.3.0
  • Python 3.8.10
  • requirements for Python environment are in requirements_kkr.txt
In [1]:
%matplotlib inline

from aiida import load_profile
from aiida.orm import load_node, ArrayData, Dict
from aiida.engine import CalcJob
from aiida_kkr.calculations import KkrCalculation, KkrimpCalculation
from aiida_kkr.workflows import kkr_imp_sub_wc, kkr_imp_wc, combine_imps_wc
from aiida_kkr.workflows._combine_imps import parse_Jij
from aiida_kkr.tools.parse_dos import parse_dosfiles
from aiida_kkr.workflows.kkr_imp_dos import parse_impdosfiles
from masci_tools.io.common_functions import get_Ry2eV

import io
import re
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.ticker as ticker
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

# connect to aiida db
profile = load_profile()
Version of workflow: 0.7.2

Functions¶

In [2]:
def plot_dos_from_calc(calc, iatom, ispin=None, sum_atoms=False, average_atoms=False, orbital='total', xfactor=1, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False):

    # get number of atoms
    if calc.process_class == KkrCalculation or calc.process_class == KkrimpCalculation: 
        natom = calc.outputs.output_parameters.get_attribute('number_of_atoms_in_unit_cell')
    else:
        print('ERROR : node input must be a KkrCalculation or KkrimpCalculation node')
        
    # get number of spins
    nspin = calc.outputs.output_parameters.get_attribute('nspin')
    
    # get number of atoms
    natoms = calc.outputs.output_parameters.get_attribute('number_of_atoms_in_unit_cell')
    
    if not lmdos:
        orbital_map = {
            'total': 0,'s': 1,'p': 2,'d': 3,'f': 4,'ns': -1
        }
    else:
        orbital_map = {
            's': 0,
            'p1': 1, 'p2': 2, 'p3': 3,
            'd1': 4,'d2': 5,'d3': 6,'d4': 7,'d5': 8,\
            'f1': 9,'f2': 10,'f3': 11,'f4': 12,'f5': 13,'f6': 14,'f7': 15
        }

    if orbital in orbital_map:
        iorb = orbital_map[orbital]
    else:
        if not lmdos:
            print('ERROR: chose between total / s / p / d / f / ns')
        else:
            print('ERROR: Invalid orbital for lmdos')

    
    # get dos_data node from the calc retrieved folder
    if calc.process_class == KkrCalculation:
        dos_host_dict = parse_dosfiles(calc.outputs.retrieved)
    if calc.process_class == KkrimpCalculation:
        try:
            host_calc = calc.inputs.host_Greenfunction_folder.get_incoming(node_class=KkrCalculation).first().node
            parent_calc = host_calc.inputs.parent_folder.get_incoming(node_class=KkrCalculation).first().node
            ef = parent_calc.outputs.output_parameters.get_attribute('fermi_energy')
        except:
            #get fermi energy from parent impurity calculation
            if 'parent_calc_folder' in calc.inputs:
                host_folder = calc.inputs.parent_calc_folder
                parent_calc = host_folder.get_incoming(node_class=CalcJob).all()[-1].node
                out_potential_content = parent_calc.outputs.retrieved.get_object_content("out_potential")
            else:
                potential = calc.get_incoming(node_class=kkr_imp_sub_wc).first().node.inputs.host_imp_startpot
                out_potential_content = potential.get_content()
            out_potential_content = out_potential_content.split('\n')
            ef = float(out_potential_content[3].split()[1])

        dos_host_dict = parse_impdosfiles(calc.outputs.retrieved, natom, nspin, ef, lmdos)

    dos_data = dos_host_dict['dos_data']
        
    x_data = dos_data.get_x()[1][0]
    y_data = dos_data.get_y()[iorb][1]
    y_data_atoms = []
    if nspin==1:
        y_data_atom = np.zeros(len(x_data))
    else:
        y_data_atom = np.array([np.zeros(len(x_data)), np.zeros(len(x_data))])
    
    plots = []
    if type(iatom) == int:
        iatom = [iatom]
    elif iatom == 'all':
        iatom = range(1,natoms+1,1)
    elif iatom == 'vac':
        vac_atoms = []
        core_states = imp_calc.outputs.output_parameters.get_attribute('charge_core_states_per_atom')
        for i, core_state in enumerate(core_states):
            if core_state == 0:
                vac_atoms.append(i+1)
                iatom = vac_atoms
                
    for atom in iatom:
        if nspin ==1:
            y_data_atom = y_data[atom-1]
        elif nspin ==2:
            y_data_atom = (y_data[(atom-1)*2], y_data[(atom-1)*2+1])
            if ispin == 1:
                y_data_atom = y_data_atom[0]
            elif ispin == 2:
                y_data_atom = y_data_atom[1]
            elif ispin == None:
                y_data_atom = y_data_atom[0] + y_data_atom[1]
            else:
                print('ERROR : Spin value should be 1 or 2')
        if _abs == True:
            y_data_atom=abs(y_data_atom)
        
        y_data_atoms.append(y_data_atom)
        
        if not return_data:
            if not sum_atoms:
                plot, = plt.plot(x_data*xfactor, y_data_atom*yfactor+yoffset)
                plots.append(plot)
    if sum_atoms:
        y_data_sum = np.sum(y_data_atoms, axis=0)
        if average_atoms:
            plot, = plt.plot(x_data*xfactor, y_data_sum*yfactor/len(iatom)+yoffset)
        else:
            plot, = plt.plot(x_data*xfactor, y_data_sum*yfactor+yoffset)
                
    if return_data:
        return dos_host_dict, x_data*xfactor, y_data_atoms
    else:
        if len(iatom) == 1 or sum_atoms:
            return plot
        else:
            return plots
        
def plot_ldos_iatom(DOS_calc, iatom, xfactor=1000, xlim=[-3,3], at_type=None, legend_show=False):
    
    calc = DOS_calc.get_outgoing(node_class=kkr_imp_sub_wc).first().node.get_outgoing(node_class=KkrimpCalculation).first().node
    
    # Plot and store the plot objects
    plot1 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='total', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot2 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='s', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot3 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='p', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot4 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot5 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='f', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot6 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='ns', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)


    # Set colors for the plots
    plot1.set_color('black')
    plot1.set_linewidth(3)

    ls = '-'
    lw = 2
    title_size = 24
    label_size = 20
    legend_size = 19
    legend_loc = 2
    
    plot2.set_color(f'C{0}')
    plot2.set_linestyle(ls)
    plot2.set_linewidth(lw)

    plot3.set_color(f'C{1}')
    plot3.set_linestyle(ls)
    plot3.set_linewidth(lw)

    plot4.set_color(f'C{2}')
    plot4.set_linestyle(ls)
    plot4.set_linewidth(lw)

    plot5.set_color(f'C{3}')
    plot5.set_linestyle(ls)
    plot5.set_linewidth(lw)

    plot6.set_color(f'C{4}')
    plot6.set_linestyle(ls)
    plot6.set_linewidth(lw)

    plt.xlim(xlim)
    plt.ylabel('DOS (1/eV)', fontsize=label_size)
    plt.xlabel('Energy (meV)', fontsize=label_size)
    plt.yticks(fontsize=label_size)
    plt.xticks(fontsize=label_size)
    
    if iatom ==1:
        if legend_show:
            plt.legend(labels=['Total', 's', 'p', 'd', 'f', 'ns'], fontsize=legend_size, loc=legend_loc)
        plt.title('Gd', fontsize=title_size)
    else:
        if legend_show:
            plt.legend(labels=['Total', 's', 'p', 'd', 'f', 'ns'], fontsize=legend_size, loc=legend_loc)
        if at_type is None:
            if iatom ==8:
                plt.title('Nb1', fontsize=title_size)
            if iatom ==9:
                plt.title('Nb2', fontsize=title_size)
            if iatom ==14:
                plt.title('Nb3', fontsize=title_size)
        else:
            plt.title(f'{at_type}', fontsize=title_size)
    
def plot_d_dos_iatom(DOS_calc, iatom, xfactor=1000, xlim=[-3, 3], at_type=None, legend_show=False):
    
    calc = DOS_calc.get_outgoing(node_class=kkr_imp_sub_wc).first().node.get_outgoing(node_class=KkrimpCalculation).first().node
    
    # Plot and store the plot objects
    plot1 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='total', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    # plot2 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d', xfactor=1000, yfactor=1, _abs=False, yoffset=0., lmdos=False, return_data=False)
    plot3 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d1', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=True, return_data=False)
    plot4 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d2', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=True, return_data=False)
    plot5 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d3', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=True, return_data=False)
    plot6 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d4', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=True, return_data=False)
    plot7 = plot_dos_from_calc(calc, iatom, ispin=None, orbital='d5', xfactor=xfactor, yfactor=1, _abs=False, yoffset=0., lmdos=True, return_data=False)

    # Set colors for the plots
    plot1.set_color('black')
    plot1.set_linewidth(3)

    # plot2.set_color(f'C{0}')
    
    ls = '-'
    lw = '2'
    title_size = 24
    label_size = 20
    legend_size = 19
    legend_loc = 2

    plot3.set_color(f'C{0}')
    plot3.set_linestyle(ls)
    plot3.set_linewidth(lw)

    plot4.set_color(f'C{1}')
    plot4.set_linestyle(ls)
    plot4.set_linewidth(lw)

    plot5.set_color(f'C{2}')
    plot5.set_linestyle(ls)
    plot5.set_linewidth(lw)

    plot6.set_color(f'C{3}')
    plot6.set_linestyle(ls)
    plot6.set_linewidth(lw)

    plot7.set_color(f'C{4}')
    plot7.set_linestyle(ls)
    plot7.set_linewidth(lw)

    plt.xlim(xlim)
    plt.ylabel('DOS (1/eV)', fontsize=label_size)
    plt.xlabel('Energy (meV)', fontsize=label_size)
    plt.yticks(fontsize=label_size)
    plt.xticks(fontsize=label_size)
    
    if iatom==1:
        if legend_show==True:
            plt.legend(labels=['Total', '$d_{x^2-y^2}$', '$d_{xz}$', '$d_{z^2}$', '$d_{yz}$', '$d_{xy}$'], fontsize=legend_size, loc=legend_loc)
        plt.title('Gd', fontsize=title_size)
    else:
        if legend_show==True:
            plt.legend(labels=['Total', '$d_{x^2-y^2}$', '$d_{xz}$', '$d_{z^2}$', '$d_{yz}$', '$d_{xy}$'], fontsize=legend_size, loc=legend_loc)
        if at_type is None:
            if iatom==8:
                plt.title('Nb1', fontsize=title_size)
            if iatom==9:
                plt.title('Nb2', fontsize=title_size)
            if iatom==14:
                plt.title('Nb3', fontsize=title_size)
        else:
            plt.title(f'{at_type}', fontsize=title_size)
    
def plot_gap_edges(gap=1.91, lim=3, color='black', alpha=0.4):
    plt.axvspan(-lim, -gap, facecolor=color, alpha=alpha/8)
    plt.axvspan(gap, lim, facecolor=color, alpha=alpha/8)
    plt.axvline(-gap, color=color, linestyle='--', alpha=alpha)
    plt.axvline(gap, color=color, linestyle='--', alpha=alpha)
    
def get_peak_energy(calc, iatom, iorb, lmdos=True):
    
    if type(iorb) is not list:
        iorb = [iorb]
        
    iatom = iatom-1

    if lmdos:
        x_data = calc.outputs.dos_data_lm.get_x()[1][0]
        y_data = calc.outputs.dos_data_lm.get_y()
    else:
        x_data = calc.outputs.dos_data.get_x()[1][0]
        y_data = calc.outputs.dos_data.get_y()
    
    x_peak = []
    y_peak = []
    
    for orb in iorb:
        y_data_orb = y_data[orb][1]
        y_data_orb_atom = y_data_orb[2*iatom] + y_data_orb[2*iatom+1]
        y_max_orb_atom = np.max(y_data_orb_atom)
        y_peak.append(y_max_orb_atom)
        
        for i, val in enumerate(y_data_orb_atom):
            if val == y_max_orb_atom:
                x_peak.append(x_data[i]*1000)

    return x_peak, y_peak

# Function to download the content of a remote file into memory
def get_remote_file_data(remote_data, relpath):
    """Retrieve the content of a remote file as an in-memory array."""
    # Create a temporary file in-memory
    with io.BytesIO() as temp_file:
        with open('/tmp/temp_remote_file', 'wb') as temp_path:
            remote_data.getfile(relpath, temp_path.name)
        # Read and return the file data as a numpy array
        with open(temp_path.name, 'r') as f:
            return np.loadtxt(f)

# Function to store JIJ data into arrays
def get_J_all_energies(calc, pattern, reload_data=False, test=False):
    remote_data = calc.outputs.remote_folder

    # Get Gd atom indices
    Gd_atoms = [int(k.split('=')[1]) + 1 for k, v in calc.inputs.settings_LDAU.items() if 'iatom' in k]
    # print('Gd atoms:', Gd_atoms)
    
    # Initialize data storage variables
    J_tot_all, J_all = [], []
    D_tot_all, D_all = [], []
    Dx_tot_all, Dx_all = [], []
    Dy_tot_all, Dy_all = [], []
    Dz_tot_all, Dz_all = [], []
    J_tot = D_tot = Dx_tot = Dy_tot = Dz_tot = 0

    # Load data from node extra if exists and reload_data is False, otherwise read from files
    if 'uuid_saved_Jij_data' in remote_data.extras and not reload_data:
        print('Loading data from node extra')
        data_node = load_node(remote_data.extras['uuid_saved_Jij_data'])
        data_all = data_node.get_array('data')
    else:
        print('Loading data from files and storing in node extra')
        # List all files in the remote directory
        try:
            all_files = remote_data.listdir()
        except IOError as e:
            print(f"Error while listing directory: {e}")
            return

        # Filter files matching the given pattern
        Jij_files = sorted([f for f in all_files if re.match(pattern, f)])
        if not Jij_files:
            print("No files matching the pattern found.")
            return

        # Limit files for testing if `test` is set to True
        if test:
            Jij_files = Jij_files[:2]
        
        data_all = [get_remote_file_data(remote_data, filename) for filename in Jij_files]
        data_all = np.array(data_all)
        
        # Save data to a new ArrayData node
        data_node = ArrayData()
        data_node.set_array('data', data_all)
        data_node.store()
        remote_data.set_extra('uuid_saved_Jij_data', data_node.uuid)
        
    # Process each file and compute J and D values
    for i in range(len(data_all)):
        data = data_all[i]
        for line in data:
            if line[0] == Gd_atoms[0] and line[1] == Gd_atoms[1]:
                J_matrix = line[4:].reshape(3, 3) * 1000 * get_Ry2eV() * -1  # Convert to meV and adjust sign
                J = np.trace(J_matrix) / 3  # Average diagonal for isotropic J
                Dx, Dy, Dz = J_matrix[1, 2], J_matrix[2, 0], J_matrix[0, 1]
                D = np.sqrt(Dx**2 + Dy**2 + Dz**2)

                # Accumulate values
                J_tot += J
                D_tot += D
                Dx_tot += Dx
                Dy_tot += Dy
                Dz_tot += Dz

                # Append values to arrays
                J_all.append(J)
                D_all.append(D)
                Dx_all.append(Dx)
                Dy_all.append(Dy)
                Dz_all.append(Dz)
                J_tot_all.append(J_tot)
                D_tot_all.append(D_tot)
                Dx_tot_all.append(Dx_tot)
                Dy_tot_all.append(Dy_tot)
                Dz_tot_all.append(Dz_tot)

                # Print values for verification
                # print(f'J = {J}, J_tot = {J_tot}')
                # print(f'D = {D}, D_tot = {D_tot}')
                break
                
    return J_all, J_tot_all, D_all, D_tot_all, Dx_tot_all, Dy_tot_all, Dz_tot_all

# Function to plot DOS
def plot_dos(ax, DOS_calc, d_ef=0, ylabel_left=True, ylabel_right=True, no_ytick_labels_left=False, no_ytick_labels_right=False):
    x_data = DOS_calc.outputs.dos_data_lm.get_x()[1][0]
    y_data_all_i, c_all_i = [], []

    for i, color in zip(range(4, 9), range(5)):
        y_label = DOS_calc.outputs.dos_data_lm.get_y()[i][0]
        y_data = DOS_calc.outputs.dos_data_lm.get_y()[i][1][0] + DOS_calc.outputs.dos_data_lm.get_y()[i][1][1] # Sum the two channels
        y_data_all_i.append(y_data)

        # Calculate cumulative charge
        c_all = np.cumsum((x_data[::-1][1:] - x_data[::-1][:-1]) * y_data[::-1][:-1])
        c_all_shifted = c_all - c_all[len(c_all) // 2] # Shift to have c=0 at ef
        c_all_i.append(c_all_shifted)

    #Total DOS
    y_data_i_sum = np.sum(y_data_all_i, axis=0)
    ax.plot(x_data, y_data_i_sum, label='d tot', color='black', linewidth=linewidth)

    # Total cumulative charge
    ax2 = plt.twinx(ax)
    twin_axes.append(ax2)  # Store the twin axis
    c_all_i_sum = np.sum(c_all_i, axis=0)
    c_all_i_sum = c_all_i_sum + d_ef # Shift to have c=c(ef) at ef
    ax2.plot(x_data[::-1][:-1], c_all_i_sum, label='Total charge', color=f'C{3}', linestyle='-', linewidth=linewidth)
    # ax2.tick_params(axis='y', colors=darker_C3)
    
    if ylabel_left:
        ax.set_ylabel(r'DOS (1/eV)', fontsize=label_fontsize)
        ax.yaxis.set_label_coords(label_x_coord, 0.5)  # Set fixed x-position for alignment
    if no_ytick_labels_left:
        ax.set_yticklabels([])
    if ylabel_right:
        ax2.set_ylabel(r'$\rho_d$ (e)', color=darker_C3, fontsize=label_fontsize)
        ax2.yaxis.set_label_coords(1-label_x_coord, 0.5)  # Set fixed x-position for alignment
    if no_ytick_labels_right:
        ax2.set_yticklabels([])

    #ax.legend(labels=['$d_{x^2-y^2}$', '$d_{xz}$', '$d_{z^2}$', '$d_{yz}$', '$d_{xy}$', '$d_{tot}$', r'$\Delta\rho_{d}$'], loc=2, prop={'size': legend_size})
    ax.axvline(0, color=f'black', linestyle='--', linewidth=linewidt_vline, alpha=alpha_vline)
    # ax.axhline(0, color='black', linewidth=linewidt_hline, alpha=alpha_hline)
    ax.set_xlim([-1, 1])
    ax.set_ylim([0, 2.5])
    ax2.set_ylim([0, 2.5])

# Function to plot J_tot and D_tot
def plot_j_tot(ax, J_tot, D_tot, J_offset, D_offset, label_y_right=False, 
               no_ytick_labels_left=False, no_ytick_labels_right=False):
    x = np.linspace(-2, 2, 192)
    J_shifted = np.array(J_tot) - (J_tot[95] + J_tot[96]) / 2 + J_offset
    ax.plot(x, J_shifted, color='C0', linewidth=linewidth)
    ax.axhline(0, color='C0', linewidth=linewidt_hline, alpha=alpha_hline)
    ax.axvline(0, color='black', linestyle='--', linewidth=linewidt_vline, alpha=alpha_vline)
    ax.set_ylim([-20, 40])

    if no_ytick_labels_left:
        ax.set_yticklabels([])

    ax2 = ax.twinx()
    twin_axes.append(ax2)
    D_shifted = np.array(D_tot) - (D_tot[95] + D_tot[96]) / 2 + D_offset
    ax2.plot(x, D_shifted, color='C1', linewidth=linewidth)

    if label_y_right:
        ax2.set_ylabel('D (meV)', color='C1', fontsize=label_fontsize)
    
    ax2.axhline(0, color='C1', linewidth=linewidt_hline, alpha=alpha_hline)
    ax2.set_ylim([-3.5, 1])
    if no_ytick_labels_right:
        ax2.set_yticklabels([])

def get_ldec_charges(calc):
    # Access the 'retrieved' folder
    retrieved_folder = calc.outputs.retrieved

    # Open and read 'out_log.000.txt'
    with retrieved_folder.open("out_log.000.txt", "r") as file:
        lines = file.readlines()

    # Find the last occurrence of 'l-dec'
    last_occurrence_index = None
    for i, line in enumerate(lines):
        if 'l-dec' in line:
            last_occurrence_index = i

    # Initialize dictionary to store orbital information
    orbital_data = {}

    # If 'l-dec' was found, get the 12 lines following the last occurrence
    if last_occurrence_index is not None:
        selected_lines = lines[last_occurrence_index + 1:last_occurrence_index + 13]
        
        # Define a regex pattern to capture orbital information with '=' sign
        pattern = re.compile(r"(\w+)\s*=\s*([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)")
        
        for line in selected_lines:
            # Check if the line matches the pattern with '='
            match = pattern.search(line)
            if match:
                orbital = match.group(1)
                spin_dn = float(match.group(2))
                spin_up = float(match.group(3))
                m_spin = float(match.group(4))
                
                # Store the data in the dictionary
                orbital_data[orbital] = {
                    'spin_dn': spin_dn,
                    'spin_up': spin_up,
                }
            elif len(line.strip().split()) == 4:  # Handle 'ns' and 'TOT' lines
                parts = line.strip().split()
                orbital = parts[0]
                spin_dn = float(parts[1])
                spin_up = float(parts[2])
                m_spin = float(parts[3])
                
                # Store the data in the dictionary
                orbital_data[orbital] = {
                    'spin_dn': spin_dn,
                    'spin_up': spin_up,
                }

        return orbital_data
    else:
        print("No 'l-dec' found in the file.")
        return None

def get_J_and_D(calc):
    
    impurity1_output_node = impurity2_output_node = Dimer_short_wf.inputs.impurity1_output_node

    retrieved = calc.outputs.retrieved
    impurity_info = calc.inputs.impurity_info

    calc_JiJ_data = parse_Jij(retrieved, impurity_info, impurity1_output_node, impurity2_output_node)['Jijdata'].get_array('JijData')
    J = calc_JiJ_data[0][3]
    D = calc_JiJ_data[0][4]

    return J, D

Load nodes¶

In [3]:
# SCF clac
Dimer_short_wf = load_node('ccd90de3-5e20-4129-a5d3-bc326e8ce1c0')
Dimer_long_wf = load_node('bec9e4ae-28ce-42b3-b473-7974691ba9ef')

# JIJ calc
Dimer_short_Jij_ef = load_node('c5219333-9d05-4b01-a9fe-658441f63828')
Dimer_long_Jij_ef = load_node('1eb745d8-bc1d-4256-b3b5-e5341ea27d94')
Dimer_short_Jij = load_node('ea3073e4-b574-496d-a347-92beb1a2275c')
Dimer_long_Jij = load_node('ba3c538e-0a4f-4cd0-a2ed-20821de71c4a')

# Normal state DOS
Dimer_short_DOS = load_node('f92edd22-23ea-4668-b561-1acb05c9ee94')
Dimer_long_DOS = load_node('2bcad826-54d0-41ca-914d-bd3a66f8cf46')

# BdG lmDOS
Single_Gd_BdG_lmDOS = load_node('47e694bd-7f41-41b3-9d13-de71cd31ebe6')
Dimer_short_BdG_lmDOS = load_node('ee5234a7-f761-46be-a2ff-4def32fdcc7a')
Dimer_long_BdG_lmDOS = load_node('f99f916e-9cbd-4164-9e90-9360cdb7f27f')
In [4]:
# !verdi archive create --compress 9 -N ccd90de3-5e20-4129-a5d3-bc326e8ce1c0 bec9e4ae-28ce-42b3-b473-7974691ba9ef c5219333-9d05-4b01-a9fe-658441f63828 1eb745d8-bc1d-4256-b3b5-e5341ea27d94 ea3073e4-b574-496d-a347-92beb1a2275c ba3c538e-0a4f-4cd0-a2ed-20821de71c4a f92edd22-23ea-4668-b561-1acb05c9ee94 2bcad826-54d0-41ca-914d-bd3a66f8cf46 47e694bd-7f41-41b3-9d13-de71cd31ebe6 ee5234a7-f761-46be-a2ff-4def32fdcc7a f99f916e-9cbd-4164-9e90-9360cdb7f27f e062f1c8-ca25-41fa-9c19-d843173eba00 1998492c-bf32-42c4-a67a-decd748e57f6 -f export_kkr.aiida

Fig 2¶

In [5]:
# Experiment data
Nb_host_single = np.loadtxt('data/deconvoluted/Nb_single_deconvoluted.txt').T # Nb substrate single atom
Nb_host_dimer = np.loadtxt('data/deconvoluted/Nb_dimers_deconvoluted.txt').T # Nb substrate dimers
Gd_single = np.loadtxt('data/deconvoluted/Gd_single_deconvoluted.txt').T # Gd single
Gd_dimer_short = np.loadtxt('data/deconvoluted/Gd_dimer_short_deconvoluted.txt').T # 001 dimer
Gd_dimer_long = np.loadtxt('data/deconvoluted/Gd_dimer_long_deconvoluted.txt').T # 1-10 dimer
In [6]:
Gd_data_all = [Gd_single, Gd_dimer_long, Gd_dimer_short]
DOS_calcs = [Single_Gd_BdG_lmDOS, Dimer_long_BdG_lmDOS, Dimer_short_BdG_lmDOS]

label_fontsize = 24
fig, axes = plt.subplots(len(DOS_calcs), 2, figsize=(12, 3*len(DOS_calcs)))  # Adjust the figsize if needed
system=['single\nGd', 'long\ndimer', 'short\ndimer']
linewidth_set = 4
for i, (Gd_data, DOS_calc) in enumerate(zip(Gd_data_all, DOS_calcs)):
    linewidth = linewidth_set
    
    if i == 0:
        Nb_host = Nb_host_single
    else:
        Nb_host = Nb_host_dimer
    axes[i,0].plot(Nb_host[0]*1000, Nb_host[1]/Nb_host[1][107], linestyle='--', color='black', linewidth=linewidth)
    axes[i,0].plot(Gd_data[0]*1000, Gd_data[1]/Gd_data[1][107], linewidth=linewidth, color=f'C{i}')
    axes[i,0].set_xlim([-4, 4])
    axes[i,0].set_ylabel('dI/dU (norm.)', fontsize = label_fontsize)
    axes[i,0].tick_params(axis='both', which='major', labelsize=label_fontsize)
    
    data_max = max(Gd_data[1])
    # if i==0:
    #     text_y = 1
    # else:
    #     text_y = 2
    text_y = 0.75*data_max
    axes[i,0].text(0, text_y, system[i], fontsize=label_fontsize-1, color=f'C{i}', horizontalalignment='center')

    # if axes[i,0] == axes[0,0]:
    #     axes[i,0].legend(['Host Nb'], fontsize=label_fontsize, loc=2)
    axes[i,0].set_ylim([0, 1.05*data_max])
    
    if axes[i,0] is not axes[-1,0]:
        axes[i,0].set_xticklabels([])
        
    host_calc = DOS_calc.inputs.gf_dos_remote.get_incoming(node_class=KkrCalculation).first().node
    imp_calc = DOS_calc.get_outgoing(node_class=kkr_imp_sub_wc).first().node.get_outgoing(node_class=KkrimpCalculation).first().node
    
    # Create a temporary figure to capture the Line2D plots
    temp_fig, temp_ax = plt.subplots()

    # Plot total DOS and vacuum DOS on the temporary figure
    plot_dos_from_calc(host_calc,iatom='all',sum_atoms=True,average_atoms=True,ispin=1,orbital='total',xfactor=1000,_abs=False,yfactor=1,yoffset=0.,lmdos=False,return_data=False) 
    plot_dos_from_calc(imp_calc,iatom='all',sum_atoms=True,average_atoms=True,ispin=None,orbital='total',xfactor=1000,_abs=False,yfactor=1,yoffset=0.,lmdos=False,return_data=False) 
    plot_dos_from_calc(imp_calc,iatom=1,sum_atoms=True,average_atoms=True,ispin=None,orbital='total',xfactor=1000,_abs=False,yfactor=1,yoffset=0.,lmdos=False,return_data=False)

    
    # Extract data from the Line2D objects and re-plot on the target subplot
    for j, line in enumerate(temp_ax.get_lines()):
        linewidth = linewidth_set
        if j==0:
            linestyle='--'
            color='black'
        else:
            linestyle='-'
            color=f'C{i}'
            if j==2:
                linestyle=':'
                linewidth=3
        
        x_data = line.get_xdata()
        y_data = line.get_ydata()
        # Normalise
        y_data_norm_fac = sum(y_data[0:38])/38
        y_data = y_data/y_data_norm_fac
        axes[i,1].plot(x_data, y_data, label=line.get_label(), linewidth=linewidth, linestyle=linestyle, color=color)
        axes[i,1].tick_params(axis='both', which='major', labelsize=label_fontsize)
        axes[i,1].yaxis.set_major_locator(ticker.MultipleLocator(2))
    
    axes[i,1].set_xlim([-4,4])
    axes[i,1].set_ylabel('DOS (norm.)', fontsize=label_fontsize)
    
    if axes[i,1] is not axes[-1,1]:
        axes[i,1].set_xticklabels([])

    # Close the temporary figure
    plt.close(temp_fig)

# Adjust layout to prevent overlap
plt.tight_layout()
axes[-1,0].set_xlabel('Bias (mV)', fontsize = label_fontsize)
axes[-1,1].set_xlabel('Energy (meV)', fontsize=label_fontsize)
plt.subplots_adjust(hspace=0.12, wspace=0.3)
# plt.savefig('Fig2_new.pdf', bbox_inches='tight')
# plt.savefig('Fig2_new.svg', bbox_inches='tight')
plt.show()
No description has been provided for this image

Fig 5¶

In [7]:
# get non-integrated J and D values
file_pattern = r'test_jijmatrix_eres_ie_int.*\.dat'
J_all_Dimer_short, J_tot_all_Dimer_short, D_all_Dimer_short, D_tot_all_Dimer_short, Dx_tot_all_Dimer_short, Dy_tot_all_Dimer_short, Dz_tot_all_Dimer_short = get_J_all_energies(Dimer_short_Jij, file_pattern)
J_all_Dimer_long, J_tot_all_Dimer_long, D_all_Dimer_long, D_tot_all_Dimer_long, Dx_tot_all_Dimer_long, Dy_tot_all_Dimer_long, Dz_tot_all_Dimer_long = get_J_all_energies(Dimer_long_Jij, file_pattern)

# J and D values at ef 
J_ef_short, D_ef_short = get_J_and_D(Dimer_short_Jij_ef)
J_ef_long, D_ef_long = get_J_and_D(Dimer_short_Jij_ef)

# Get d orbital occupation at ef
charges_dimer_short = get_ldec_charges(Dimer_short_Jij_ef)
charges_dimer_long = get_ldec_charges(Dimer_long_Jij_ef)
d_ef_dimer_short = charges_dimer_short['d']['spin_dn'] + charges_dimer_short['d']['spin_up']
d_ef_dimer_long = charges_dimer_long['d']['spin_dn'] + charges_dimer_long['d']['spin_up']

# Plotting variables
label_fontsize = 20
ticks_fontsize = 20
linewidth = 3
alpha_vline, linewidt_vline = 0.4, 1.25
alpha_hline, linewidt_hline = 0.6, 1.25
label_x_coord = -0.16  # Define fixed x-coordinate for aligned y-labels
legend_size = 17

# Define custom darker colors
darker_C0 = (0.2, 0.4, 0.6)  # Darker shade for 'C0'
darker_C1 = (0.9, 0.35, 0)  # Darker shade for 'C1'
darker_C3 = (0.8, 0, 0)

# Set up figure and subplots
fig, axs = plt.subplots(2, 2, figsize=(12, 10), sharex=True, gridspec_kw={'height_ratios': [2, 2]})

# Store twin axes for setting tick label sizes
twin_axes = []


# Plotting for DOS Dimer_short and DOS Dimer_long
plot_dos(axs[1, 0], Dimer_short_DOS, d_ef = d_ef_dimer_short, ylabel_right=False, no_ytick_labels_right=True)
axs[1, 0].set_xlabel('Energy (eV)', fontsize=label_fontsize)
plot_dos(axs[1, 1], Dimer_long_DOS, d_ef = d_ef_dimer_long, ylabel_left=False, no_ytick_labels_left=True)
axs[1, 1].set_xlabel('Energy (eV)', fontsize=label_fontsize)


# Plot J_tot_all_Dimer_short and J_tot_all_Dimer_long with distinct offsets
plot_j_tot(axs[0, 0], J_tot_all_Dimer_short, D_tot_all_Dimer_short, J_offset=J_ef_short, D_offset=D_ef_short, no_ytick_labels_right=True)
axs[0, 0].set_ylabel('J (meV)', color=darker_C0, fontsize=label_fontsize)
axs[0, 0].yaxis.set_label_coords(label_x_coord, 0.5)  # Align y-labels for J_tot plots
plot_j_tot(axs[0, 1], J_tot_all_Dimer_long, D_tot_all_Dimer_long, J_offset=J_ef_long, D_offset=D_ef_long, label_y_right=True, no_ytick_labels_left=True)

# Annotations
axs[0, 0].text(-0.14, 1.1, 'a', weight='bold', transform=axs[0, 0].transAxes, fontsize=label_fontsize, va='top', ha='right')
axs[0, 1].text(-0.04, 1.1, 'b', weight='bold', transform=axs[0, 1].transAxes, fontsize=label_fontsize, va='top', ha='right')
axs[1, 0].text(-0.14, 1.1, 'c', weight='bold', transform=axs[1, 0].transAxes, fontsize=label_fontsize, va='top', ha='right')
axs[1, 1].text(-0.04, 1.1, 'd', weight='bold', transform=axs[1, 1].transAxes, fontsize=label_fontsize, va='top', ha='right')

# Set tick label size for all subplots, including twin y-axes
for ax in axs.flat:
    ax.tick_params(axis='both', which='major', labelsize=ticks_fontsize)
for ax2 in twin_axes:
    ax2.tick_params(axis='y', which='major', labelsize=ticks_fontsize)

# Reduce space between plots
plt.subplots_adjust(hspace=0.2, wspace=0.15)

# plt.savefig(f'J_Dimers.pdf', bbox_inches='tight')
# plt.savefig(f'J_Dimers.png', bbox_inches='tight')

plt.show()
Loading data from node extra
Loading data from node extra
/opt/aiida-core/aiida/orm/querybuilder.py:1369: AiidaEntryPointWarning: Process type 'aiida_kkr.workflows._combine_imps.combine_imps_wc' does not correspond to a registered entry. This risks queries to fail once the location of the process class changes. Add an entry point for 'aiida_kkr.workflows._combine_imps.combine_imps_wc' to remove this warning.
  warnings.warn(
/opt/aiida-core/aiida/orm/querybuilder.py:1369: AiidaEntryPointWarning: Process type 'aiida_kkr.workflows._combine_imps.combine_imps_wc' does not correspond to a registered entry. This risks queries to fail once the location of the process class changes. Add an entry point for 'aiida_kkr.workflows._combine_imps.combine_imps_wc' to remove this warning.
  warnings.warn(
/opt/aiida-core/aiida/orm/querybuilder.py:1369: AiidaEntryPointWarning: Process type 'aiida_kkr.workflows._combine_imps.combine_imps_wc' does not correspond to a registered entry. This risks queries to fail once the location of the process class changes. Add an entry point for 'aiida_kkr.workflows._combine_imps.combine_imps_wc' to remove this warning.
  warnings.warn(
/opt/aiida-core/aiida/orm/querybuilder.py:1369: AiidaEntryPointWarning: Process type 'aiida_kkr.workflows._combine_imps.combine_imps_wc' does not correspond to a registered entry. This risks queries to fail once the location of the process class changes. Add an entry point for 'aiida_kkr.workflows._combine_imps.combine_imps_wc' to remove this warning.
  warnings.warn(
No description has been provided for this image

Fig S5¶

In [8]:
# Create a figure with 3 subplots (2 rows, 3 columns)
fig, axes = plt.subplots(2, 3, figsize=(18, 10))  # Adjust the figsize if needed

# Define subplot labels
subplot_labels = ['a', 'b', 'c', 'd', 'e', 'f']

for l in [0, 1]:
    # Iterate over atoms and subplot axes
    for i, iatom in enumerate([1, 8, 14]):
        at_type = None
        if iatom == 1:
            legend_show = True
        else:
            legend_show = False

        if iatom == 1:
            at_type = 'Gd'
        elif iatom == 8:
            at_type = 'Nb1'
        elif iatom == 14:
            at_type = 'Nb2'

        # Compute the row and column index for the 2D axes array
        row = l  # l is either 0 or 1, representing the row
        col = i  # i is 0, 1, or 2, representing the column

        # Set the current axis to the corresponding subplot
        plt.sca(axes[row, col])

        if l == 0:
            plot_ldos_iatom(Single_Gd_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)
        elif l == 1:
            plot_d_dos_iatom(Single_Gd_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)

        plot_gap_edges()

        # Set y-axis limit for the current subplot
        plt.ylim([0, 5])

        # Set y-axis label only for the first subplot in each row
        if col == 0:
            pass  # You can add a y-axis label here if desired
        else:
            axes[row, col].set_ylabel('')  # Remove y-axis label for other subplots

        # Add subplot label (a), (b), (c), etc.
        axes[row, col].text(0.04, 1.12, subplot_labels[l * 3 + i], 
                            transform=axes[row, col].transAxes, 
                            fontsize=24, weight='bold', va='top', ha='right')

# Adjust layout to avoid overlap
plt.tight_layout()

# plt.savefig('Gd_single_atom_DOS_combined.pdf', bbox_inches='tight')
No description has been provided for this image

Fig S6¶

In [9]:
# Create a figure with 3 subplots (2 rows, 3 columns)
fig, axes = plt.subplots(2, 4, figsize=(24, 10))  # Adjust the figsize if needed

# Define subplot labels
subplot_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

for l in [0, 1]:
    # Iterate over atoms and subplot axes
    for i, iatom in enumerate([1, 8, 9, 14]):
        at_type = None
        if iatom == 1:
            legend_show = True
        else:
            legend_show = False

        if iatom == 1:
            at_type='Gd'
            legend_show=True
        elif iatom == 8:
            at_type='Nb1'
        elif iatom == 9:
            at_type='Nb2'
        elif iatom == 14:
            at_type='Nb3'

        # Compute the row and column index for the 2D axes array
        row = l  # l is either 0 or 1, representing the row
        col = i  # i is 0, 1, or 2, representing the column

        # Set the current axis to the corresponding subplot
        plt.sca(axes[row, col])

        if l == 0:
            plot_ldos_iatom(Dimer_short_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)
        elif l == 1:
            plot_d_dos_iatom(Dimer_short_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)

        plot_gap_edges()

        # Set y-axis limit for the current subplot
        plt.ylim([0, 7])

        # Set y-axis label only for the first subplot in each row
        if col == 0:
            pass  # You can add a y-axis label here if desired
        else:
            axes[row, col].set_ylabel('')  # Remove y-axis label for other subplots

        # Add subplot label (a), (b), (c), etc.
        axes[row, col].text(0.04, 1.12, subplot_labels[l * 4 + i], 
                            transform=axes[row, col].transAxes, 
                            fontsize=24, weight='bold', va='top', ha='right')

# Adjust layout to avoid overlap
plt.tight_layout()

# plt.savefig('Gd_dimer_short_DOS_combined.pdf', bbox_inches='tight')
No description has been provided for this image

Fig S7¶

In [10]:
# Create a figure with 3 subplots (2 rows, 3 columns)
fig, axes = plt.subplots(2, 4, figsize=(24, 10))  # Adjust the figsize if needed

# Define subplot labels
subplot_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

for l in [0, 1]:
    # Iterate over atoms and subplot axes
    for i, iatom in enumerate([1, 14, 15, 8]):
        at_type = None
        if iatom == 1:
            legend_show = True
        else:
            legend_show = False

        if iatom == 1:
            at_type='Gd'
            legend_show=True
        elif iatom == 14:
            at_type='Nb1'
        elif iatom == 15:
            at_type='Nb2'
        elif iatom == 8:
            at_type='Nb3'

        # Compute the row and column index for the 2D axes array
        row = l  # l is either 0 or 1, representing the row
        col = i  # i is 0, 1, or 2, representing the column

        # Set the current axis to the corresponding subplot
        plt.sca(axes[row, col])

        if l == 0:
            plot_ldos_iatom(Dimer_long_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)
        elif l == 1:
            plot_d_dos_iatom(Dimer_long_BdG_lmDOS, iatom, at_type=at_type, legend_show=legend_show)

        plot_gap_edges()

        # Set y-axis limit for the current subplot
        plt.ylim([0, 5])

        # Set y-axis label only for the first subplot in each row
        if col == 0:
            pass  # You can add a y-axis label here if desired
        else:
            axes[row, col].set_ylabel('')  # Remove y-axis label for other subplots

        # Add subplot label (a), (b), (c), etc.
        axes[row, col].text(0.04, 1.12, subplot_labels[l * 4 + i], 
                            transform=axes[row, col].transAxes, 
                            fontsize=24, weight='bold', va='top', ha='right')

# Adjust layout to avoid overlap
plt.tight_layout()

# plt.savefig('Gd_dimer_long_DOS_combined.pdf', bbox_inches='tight')
No description has been provided for this image
In [ ]: