Source code for pybec.plotters

"""
plotters.py
Several convenience functions for plotting Born Effective Charges in various ways.
"""

import numpy as np
import matplotlib.pyplot as plt
import pybec.parsers as parsers
import pybec.analysis as analysis
import os
import re
import pandas as pd

# Default Markers to use when plotting different elements
MARKERS = 'o s h + x * p D v ^ < >'

# x = 0, y = 1, z = 2
DIRS = ['x', 'y', 'z']

#keep track of possible slice directions as a set
DIRS_SET = set(DIRS)


[docs]def plot_BEC_v_spher(no_efield, clamped_ion, xyz, matrix_name, np_name, np_element, to_plot='r', centroid_of='all', e_field=.001, e_field_direction=[0,0,1], cmap=None, cbar_pos = 'top', legend=True, figsize=(8,4), marker_dict=None, color_dict=None, grid=False, alpha=1.0, cbar_shrink=1.0): """ Plots the Born Effective Charges for each ion against the distance of that ion from the nanoparticle centroid. Parameters --------- no_efield : str File path to the zero field step output file clamped_ion : str File path to the clamped ion step output file xyz : str File path to the optimized structure .xyz file matrix_name : str Whatever you want to call the matrix, e.g. 'MgO' np_name : str Whatever you want to call the mNP, e.g. 'Ag8_333' np_element : str Elemental symbol for nanoparticle element. centroid_of : str, optional The element you want to calculate the centroid for when calculating and plotting against the distance to centroid. If 'all', it will calculate the centroid of the whole unit cell. 'Ag' will calculate the centroid of all silver ions in the unit cell in the relaxed structure. Default: 'all' e_field : float, optional The electric field in au. Default: 0.001 e_field_direction : int, optional The direction the field is applied, where the x-axis is [1,0,0], y-axis is [0,1,0], and z-axis is [0,0,1]. Default: [0,0,1] cmap : string, optional The matplotlib colormap style used to color the absolute value of their Born Effective Charge. If None, the data instead will be colored by element using the color_dict colors or default matplotlib ones. Default: None cbar_pos : str, optional Where to place the colorbar if cmap is used. Can be 'top', 'right', or 'bottom'. Default: 'top' legend : bool, optional Whether to include a legend labelling the different elements. Default: True figsize : (int, int), optional Width, Height of the figure. Default: (8,4) marker_dict : dict, optional Custom dictionary specifying which markers to use for each element, where the keys are element symbols and the values are valid matplotlib marker symbols. Default: None Ex: {'O': 'o', 'Ag': 'x', 'Mg': '>'} color_dict : dict, optional Custom dictionary specifying which colors to use for each element, where the keys are element symbols and the values are valid matplotlib color symbols. Default: None Ex: {'O': 'r', 'Ag': 'k', 'Mg': 'y'} grid : bool, optional Whether to include grid lines in the plot. Default: False alpha : float The alpha channel value for the plot colors. Default: 1.0 Returns ------- None """ # parse coordinates coords = parsers.get_coordinates(xyz) # calculate unit cell centroid: centroid = analysis.get_centroid(coords, centroid_of) # calculate the distances of all ions to centroid r, phi, theta = analysis.get_spherical_coords_to_centroid(coords, centroid) if to_plot == 'theta': to_plot_ = theta elif to_plot == 'phi': to_plot_ = phi else: to_plot_ = r max_dist = max([max(to_plot_[key]) for key in to_plot_]) # for plotting min_dist = min([min(to_plot_[key]) for key in to_plot_]) # for plotting # parse forces for_0, for_1 = parsers.get_converged_forces(no_efield), parsers.get_converged_forces(clamped_ion) # calculate Born Effective Charges BEC = analysis.get_BECs(for_0, for_1, e_field, e_field_direction) # for colormap max_BEC = max([max(abs(BEC[key])) for key in BEC if key != np_element]) # for plotting min_BEC = min([min(abs(BEC[key])) for key in BEC if key != np_element]) # for plotting # plot the BECs against distance from centroid fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) for i, el in enumerate(BEC): # choose marker, using the provided ones if given if marker_dict is not None: try: marker = marker_dict[el] except KeyError: raise ValueError("Marker dict provided has no marker specified for {}".format(el)) # use the default color list else: marker = MARKERS[i % len(MARKERS)] # if a colormap is desired if cmap is not None and el != np_element: plot = ax.scatter(to_plot_[el], BEC[el], c=np.abs(BEC[el]), cmap=cmap, alpha=alpha, edgecolors='k', label=el, marker=marker, vmin=min_BEC, vmax=max_BEC) # else just group elements by color else: color = None if el == np_element: color = 'k' # use custom color dictionary if provided if color_dict is not None: try: color = color_dict[el] except KeyError: raise ValueError("Color dict provided has no color specified for {}".format(el)) ax.scatter(to_plot_[el], BEC[el], alpha=alpha, c=color, edgecolors='k', label=el, marker=marker, vmin=min_BEC, vmax=max_BEC) ax.plot(np.linspace(0, max_dist + 1, 2), np.zeros(2), 'k:') if to_plot == 'r': ax.set_xlabel('Distance from {} mNP Centroid / $\AA$'.format(np_name)) if to_plot == 'theta': ax.set_xlabel('Azimuthal Angle / $rad$'.format(np_name)) if to_plot == 'phi': ax.set_xlabel('Polar Angle / $rad$'.format(np_name)) ax.set_ylabel('$Z^*$') #ax.set_ylabel('$Z^*_{{{}{}}}$'.format(DIRS[e_field_direction], DIRS[e_field_direction])) ax.set_title('Born Effective Charges for {} Matrix with {} NanoParticle'.format(matrix_name, np_name)) buffer = (max_dist - min_dist) * 0.05 ax.set_xlim([min_dist - buffer, max_dist + buffer]) # Now add the colorbar if specified if cmap is not None: cb, pad = add_colorbar(fig, plot, cbar_pos, shrink=cbar_shrink) if legend: ax.legend(loc='right') if grid: ax.grid() return fig, ax, cb, pad
[docs]def plot_FE_E_v_spher(xyz, xyzE, matrix_name, np_name, np_element, to_plot='r', centroid_of='all', sub_mean_field=False, e_field=.25, e_field_direction=[0,0,1], center_dict=None, ref_neg=True, cmap=None, cbar_pos = 'top', legend=True, figsize=(8,4), marker_dict=None, color_dict=None, grid=False, alpha=1.0, cbar_shrink=1.0, units="au"): """ Plots the electric field predicted on each ion by a continuum (FE) approach. Parameters --------- xyz : str File path to the optimized structure .xyz file xyzE : str or dict File path to the file containing the FE efield for each atomic coordinate, or Dictionary of electric field values, where the keys are the element symbols, and the values are the Nx3 numpy array of electric field values for all atoms of that element. Ex: xyzE=get_dipole_field(get_coordinates(xyz), get_centroid(blah, key='Ag'), q=1) matrix_name : str Whatever you want to call the matrix, e.g. 'MgO' np_name : str Whatever you want to call the mNP, e.g. 'Ag8_333' centroid_of : str, optional The element you want to calculate the centroid for when calculating and plotting against the distance to centroid. If 'all', it will calculate the centroid of the whole unit cell. 'Ag' will calculate the centroid of all silver ions in the unit cell in the relaxed structure. defaults to 'all' sub_mean_field: bool, optional If set, the external applied field is subtracted from the calculated fields, meaning that only the local field disturbance caused by the inclusion will be plotted. defaults to True e_field : float, optional The applied electric field in V/m. Default: 0.25 e_field_direction : int, optional The direction the field is applied, where the x-axis is [1,0,0], y-axis is [0,1,0], and z-axis is [0,0,1]. Default: [0,0,1] center_dict : dict, optional Custom dictionary specifying where to center each element on the y-axis of the plot, where the keys are element symbols and the values are floats or integers. Default: None Ex: {'O': -2, 'Mg': 2, 'Ag': 0} cmap : string, optional The matplotlib colormap style used to color the absolute value of their Born Effective Charge. If None, the data instead will be colored by element using the color_dict colors or default matplotlib ones. Default: None cbar_pos : str, optional Where to place the colorbar if cmap is used. Can be 'top', 'right', or 'bottom'. Default: 'top' legend : bool, optional Whether to include a legend labelling the different elements. Default: True figsize : (int, int), optional Width, Height of the figure. Default: (8,4) marker_dict : dict, optional Custom dictionary specifying which markers to use for each element, where the keys are element symbols and the values are valid matplotlib marker symbols. Default: None Ex: {'O': 'o', 'Ag': 'x', 'Mg': '>'} color_dict : dict, optional Custom dictionary specifying which colors to use for each element, where the keys are element symbols and the values are valid matplotlib color symbols. Default: None Ex: {'O': 'r', 'Ag': 'k', 'Mg': 'y'} grid : bool, optional Whether to include grid lines in the plot. Default: False alpha : float, optional The alpha channel value for the plot colors. Default: 1.0 units : str, optional The units for electric field to be added to the y-axis. Default: "au" Returns ------- None """ # parse coordinates coords = parsers.get_coordinates(xyz) # parse Efield from the FE simulation if isinstance(xyzE, str): if not os.path.isfile(xyzE): raise ValueError('Invalid file path given for electric field at atomic coordinates.') FE_Efield = parsers.get_coordinates(xyzE, skip=1, d=',') elif isinstance(xyzE, dict): # efield already read in or generated some other way FE_Efield = xyzE else: raise ValueError('xyzE must either be file path for electric field in .xyz format or dictionary with' 'elements for keys and Nx3 ndarray of efield for values') # calculate unit cell centroid: centroid = analysis.get_centroid(coords, centroid_of) # calculate the distances of all ions to centroid r, phi, theta = analysis.get_spherical_coords_to_centroid(coords, centroid) if to_plot == 'theta': to_plot_ = theta elif to_plot == 'phi': to_plot_ = phi else: to_plot_ = r max_dist = max([max(to_plot_[key]) for key in to_plot_]) # for plotting min_dist = min([min(to_plot_[key]) for key in to_plot_]) # for plotting # calculate field in desired direction if FE_Efield[list(FE_Efield)[0]].ndim > 1: field_to_plot = analysis.get_field_along_d(FE_Efield, sub_mean_field, e_field, e_field_direction) else: field_to_plot = FE_Efield max_ftp = max([max(abs(field_to_plot[key])) for key in field_to_plot if key != np_element]) # for plotting min_ftp = min([min(abs(field_to_plot[key])) for key in field_to_plot if key != np_element]) # for plotting # center different elements at different points on y-axis if desired if center_dict is not None: for i, el in enumerate(field_to_plot): try: # also need to reflect data vertically to mimic greater magnitude of BEC with bigger EField if center_dict[el] < 0 and ref_neg: field_to_plot[el] *= -1 field_to_plot[el] += center_dict[el] except KeyError: raise ValueError("Center dict provided has no center specified for {}".format(el)) else: center_dict = {el: 0 for el in field_to_plot} # for colormap # old way # max_ftp = max([max(abs(field_to_plot[key])) for key in field_to_plot if key != np_element]) # for plotting # min_ftp = min([min(abs(field_to_plot[key])) for key in field_to_plot if key != np_element]) # for plotting # plot the BECs against desired spherical coordinate fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) for i, el in enumerate(field_to_plot): # choose marker, using the provided ones if given if marker_dict is not None: try: marker = marker_dict[el] except KeyError: raise ValueError("Marker dict provided has no marker specified for {}".format(el)) # use the default color list else: marker = MARKERS[i % len(MARKERS)] # if a colormap is desired if cmap is not None and el != np_element: # old coloring option: np.abs(field_to_plot[el]) plot = ax.scatter(to_plot_[el], field_to_plot[el], c=(field_to_plot[el] - center_dict[el])*(-1)**(center_dict[el]<0), cmap=cmap, alpha=alpha, edgecolors='k', label=el, marker=marker, vmin=min_ftp, vmax=max_ftp) # else just group elements by color else: color = None if el == np_element: color = 'k' # use custom color dictionary if provided if color_dict is not None: try: color = color_dict[el] except KeyError: raise ValueError("Color dict provided has no color specified for {}".format(el)) ax.scatter(to_plot_[el], field_to_plot[el], alpha=alpha, c=color, edgecolors='k', label=el, marker=marker, vmin=min_ftp, vmax=max_ftp) ax.plot(np.linspace(0, max_dist + 1, 2), np.zeros(2), 'k:') if to_plot == 'r': ax.set_xlabel('Distance from {} mNP Centroid / $\AA$'.format(np_name)) if to_plot == 'theta': ax.set_xlabel('Azimuthal Angle / $rad$'.format(np_name)) if to_plot == 'phi': ax.set_xlabel('Polar Angle / $rad$'.format(np_name)) ax.set_ylabel(f'$E$ / ${units}$') #ax.set_ylabel('$Z^*_{{{}{}}}$'.format(DIRS[e_field_direction], DIRS[e_field_direction])) ax.set_title('Electric Field for {} Matrix with {} NanoParticle'.format(matrix_name, np_name)) buffer = (max_dist - min_dist) * 0.05 ax.set_xlim([min_dist - buffer, max_dist + buffer]) # Now add the colorbar if specified if cmap is not None: cb, pad = add_colorbar(fig, plot, cbar_pos, shrink=cbar_shrink) if legend: ax.legend(loc='right') if grid: ax.grid() return fig, ax, cb, pad
[docs]def plot_BEC_3D(no_efield, clamped_ion, xyz, matrix_name, np_name, e_field=.001, e_field_direction=2, cbar_pos = 'top', legend=True, figsize=(8,8), marker_dict=None, grid=False, cmap='magma', alpha=1.0, cbar_shrink=1.0): # parse coordinates coords = parsers.get_coordinates(xyz) # parse forces for_0, for_1 = parsers.get_converged_forces(no_efield), parsers.get_converged_forces(clamped_ion) # calculate Born Effective Charges BEC = analysis.get_BECs(for_0, for_1, e_field, e_field_direction) # for colormap max_BEC = max([max(BEC[key]) for key in BEC]) # for plotting min_BEC = min([min(BEC[key]) for key in BEC]) # for plotting fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111, projection='3d') for i, el in enumerate(BEC): # choose marker, using the provided ones if given if marker_dict is not None: try: marker = marker_dict[el] except KeyError: raise ValueError("Marker dict provided has no marker specified for {}".format(el)) # use the default color list else: marker = MARKERS[i % len(MARKERS)] # plot on 3D axes plot = ax.scatter(coords[el][:,0], coords[el][:,1], coords[el][:,2], c=BEC[el], cmap=cmap, alpha=alpha, edgecolors='k', label=el, marker=marker, vmin=min_BEC, vmax=max_BEC) ax.set_xlabel('X / $\AA$') ax.set_ylabel('Y / $\AA$') ax.set_zlabel('Z / $\AA$') ax.set_title('Born Effective Charges for {} Matrix with {} NanoParticle'.format(matrix_name, np_name)) # Now add the colorbar if specified cb, pad = add_colorbar(fig, plot, cbar_pos, shrink=cbar_shrink) if legend: ax.legend() if grid: ax.grid() return fig, ax, cb, pad
[docs]def remove_keymap_conflicts(new_keys_set): for prop in plt.rcParams: if prop.startswith('keymap.'): keys = plt.rcParams[prop] remove_list = set(keys) & new_keys_set for key in remove_list: keys.remove(key)
[docs]def multi_slice_viewer(volume, fig): remove_keymap_conflicts({'j', 'k', 'l'}) ax = fig.add_subplot(111) ax.volume = volume ax.dir = 0 ax.index = volume.shape[ax.dir] // 2 ax.set_title('Slices in the X-direction') ax.set_xlabel('Y / $\AA$') ax.set_ylabel('Z / $\AA$') plot = ax.imshow(volume[ax.index].T, origin='bottom', ) fig.canvas.mpl_connect('key_press_event', process_key) return fig, ax, plot
[docs]def process_key(event): fig = event.canvas.figure ax = fig.axes[0] if event.key == 'j': previous_slice(ax) elif event.key == 'k': next_slice(ax) elif event.key == 'l': next_direction(ax) fig.canvas.draw()
[docs]def previous_slice(ax): """Go to the previous slice.""" volume = ax.volume ax.index = (ax.index - 1) % volume.shape[ax.dir] # wrap around using % p = re.compile(r'\d+\s\/') title = ax.get_title() ax.set_title(p.sub('{} /'.format(ax.index), title)) if ax.is_kriging: direction = (ax.dir + 1) % 3 else: direction = ax.dir if direction == 0: img = volume[ax.index] elif direction == 1: img = volume[:, ax.index, :] else: img = volume[:, :, ax.index] if not ax.is_kriging: img = img.T ax.images[0].set_array(img) if hasattr(ax, 'add_atoms') and ax.add_atoms: plot_atoms(ax)
[docs]def next_slice(ax): """Go to the next slice.""" volume = ax.volume ax.index = (ax.index + 1) % volume.shape[ax.dir] p = re.compile(r'\d+\s\/') title = ax.get_title() ax.set_title(p.sub('{} /'.format(ax.index), title)) if ax.is_kriging: direction = (ax.dir + 1) % 3 else: direction = ax.dir if direction == 0: img = volume[ax.index] elif direction == 1: img = volume[:, ax.index, :] else: img = volume[:, :, ax.index] if not ax.is_kriging: img = img.T ax.images[0].set_array(img) if hasattr(ax, 'add_atoms') and ax.add_atoms: plot_atoms(ax)
[docs]def plot_atoms(ax): ax.collections = [] # clear existing atoms # Plot the atoms using the provided color map and marker dict if given for i, el in enumerate(ax.atoms['element'].unique()): # get a slice corresponding to the current index df_slice = ax.atoms.loc[lambda df: np.array([ax.index in item for item in df['interval']])] # get all of the current element rows df_el_slice = df_slice.loc[df_slice['element'] == el] # choose marker, using the provided ones if given (one for each unique element) if ax.marker_dict is not None: try: marker = ax.marker_dict[el] except KeyError: raise ValueError("Marker dict provided has no marker specified for {}".format(el)) # use the default color list else: marker = 'o' if ax.cmap_atoms: color = df_el_slice['BEC'] vmin, vmax = ax.min_ion_color, ax.max_ion_color elif ax.color_dict is None: color, vmin, vmax, ax.ion_cmap = None, None, None, None else: try: color = ax.color_dict[el] vmin, vmax, ax.ion_cmap = None, None, None except KeyError: raise ValueError("Color dict provided has no color specified for {}".format(el)) # plot on 2D axes plot_atoms = ax.scatter(df_el_slice[ax.other_dirs[0]], df_el_slice[ax.other_dirs[1]], c=color, cmap=ax.ion_cmap, alpha=1, edgecolors='k', label=el, marker=marker, vmin=vmin, vmax=vmax) return plot_atoms
[docs]def next_direction(ax): """Go to the next slice direction.""" p = re.compile(r'[XYZ](?=\-direction)') volume = ax.volume ax.dir = (ax.dir + 1) % 3 slice_dir = DIRS[ax.dir].upper() ax.other_dirs = [direct.upper() for direct in list(DIRS_SET - set(slice_dir.lower()))] title = ax.get_title() ax.set_title(p.sub(DIRS[ax.dir].upper(), title)) ax.set_xlabel('{} / $\AA$'.format(ax.other_dirs[0])) ax.set_ylabel('{} / $\AA$'.format(ax.other_dirs[1])) if ax.is_kriging: direction = (ax.dir + 1) % 3 else: direction = ax.dir if direction == 0: img = volume[ax.index] elif direction == 1: img = volume[:, ax.index, :] else: img = volume[:, :, ax.index] if not ax.is_kriging: img = img.T ax.images[0].set_array(img) if hasattr(ax, 'add_atoms') and ax.add_atoms: labels = range(1, ax.num_bins + 1) ax.atoms['bin'] = pd.cut(ax.atoms[slice_dir], ax.num_bins, labels=labels) ax.atoms = ax.atoms.sort_values(by='bin', ascending=True) intervals = pd.cut(pd.Series(np.arange(0, ax.res)), ax.num_bins).unique() ax.atoms['interval'] = intervals[ax.atoms['bin'].values.astype(int) - 1] plot_atoms(ax)
[docs]def multi_slice_viewer_BEC(fig, volume, lattice, add_atoms=True, cell=None, color_dict=None, marker_dict=None, cmap_atoms=False, cmap='viridis', ion_cmap='plasma', num_bins=6, resolution=50, is_kriging=False): # configure matplotlib to use my defined keystroke callbacks for j,k,l instead of any defaults remove_keymap_conflicts({'j', 'k', 'l'}) ax = fig.add_subplot(111) # always start in the x direction ax.dir = 0 slice_dir = DIRS[ax.dir].upper() ax.other_dirs = [direct.upper() for direct in list(DIRS_SET - set(slice_dir.lower()))] ax.is_kriging = is_kriging # get the limits of the lattice along the slice direction x_lat = lattice[:, DIRS.index(ax.other_dirs[0].lower())] y_lat = lattice[:, DIRS.index(ax.other_dirs[1].lower())] # for plotting xlim and ylim min_x, max_x = min(x_lat), max(x_lat) min_y, max_y = min(y_lat), max(y_lat) # the interpolated BECs ax.volume = volume # start in the middle slice if ax.is_kriging: ax.index = volume.shape[(ax.dir + 1) % 3] // 2 else: ax.index = volume.shape[ax.dir] // 2 # add some runtime properties to the ax to store configuration ax.add_atoms = add_atoms if add_atoms: ax.atoms = cell ax.color_dict = color_dict ax.marker_dict = marker_dict ax.min_ion_color = min(cell['BEC']) ax.max_ion_color = max(cell['BEC']) ax.cmap_atoms = cmap_atoms ax.ion_cmap = ion_cmap ax.num_bins = num_bins ax.res = resolution min_volume = np.nanmin(volume) max_volume = np.nanmax(volume) ax.set_xlabel('{} / $\AA$'.format(ax.other_dirs[0])) ax.set_ylabel('{} / $\AA$'.format(ax.other_dirs[1])) ax.set_xlim(min_x, max_x) ax.set_ylim(min_y, max_y) if ax.is_kriging: img = volume[:, ax.index, :] else: img = volume[ax.index].T enhance_plot = ax.imshow(img, origin='lower', extent=(min_x, max_x, min_y, max_y), cmap=cmap, vmin=min_volume, vmax=max_volume) if ax.add_atoms: labels = range(1, ax.num_bins + 1) ax.atoms['bin'] = pd.cut(ax.atoms[slice_dir], ax.num_bins, labels=labels) ax.atoms = ax.atoms.sort_values(by='bin', ascending=True) intervals = pd.cut(pd.Series(np.arange(0, ax.res)), ax.num_bins).unique() ax.atoms['interval'] = intervals[ax.atoms['bin'].values.astype(int) - 1] atoms_plot = plot_atoms(ax) else: atoms_plot = None fig.canvas.mpl_connect('key_press_event', process_key) return ax, enhance_plot, atoms_plot
[docs]def add_colorbar(fig, plot, cbar_pos, shrink=0.5): # Now add the colorbar pad = 0.0 # helps tight layout if cbar_pos == 'top': pad = 3.0 # helps tight layout cbaxes = fig.add_axes([0.14, 1, 0.8, 0.03]) cb = fig.colorbar(plot, cax=cbaxes, orientation='horizontal', shrink=shrink) elif cbar_pos == 'bottom': cb = fig.colorbar(plot, orientation='horizontal', shrink=shrink) else: cb = fig.colorbar(plot, shrink=shrink) return cb, pad
[docs]def plot_BEC_heatmap_slices(fig, no_efield, clamped_ion, xyz, matrix_name, np_name, matrix_no_efield, matrix_clamped_ion, matrix_xyz, e_field=.001, e_field_direction=2, interpolation='linear', cbar_pos='top', legend=True, marker_dict=None, pad=0.2, color_dict=None, grid=False, cmap='magma', ion_cmap='plasma', num_bins=6, track_slices=True, res=100, add_atoms=True, cmap_atoms=False, cbar_shrink=1.0): ###################################### # 1. Interpolate for the nanocomposite ###################################### # combine the atoms data into a pandas dataframe cell = analysis.gen_BEC_df(no_efield, clamped_ion, xyz, e_field=e_field, e_field_direction=e_field_direction) lattice = parsers.get_lattice(xyz) # pad the cell by a few layers according to its periodicity cell_ex = analysis.pad_cell(cell, lattice, pad=pad) # interpolate over a grid of points that spans at least the original unit cell NC_interp_BEC = analysis.interp_3d(cell_ex[['X', 'Y', 'Z']].values, abs(cell_ex['BEC'].values), resolution=res, lattice=lattice, method=interpolation) ###################################### # 2. Interpolate for the Matrix Only ###################################### # combine the atoms data into a pandas dataframe matrix_cell = analysis.gen_BEC_df(matrix_no_efield, matrix_clamped_ion, matrix_xyz) # pad the cell by a few layers according to its periodicity matrix_cell_ex = analysis.pad_cell(matrix_cell, lattice, pad=pad) # interpolate over a grid of points that spans at least the original unit cell matrix_interp_BEC = analysis.interp_3d(matrix_cell_ex[['X', 'Y', 'Z']].values, abs(matrix_cell_ex['BEC'].values), resolution=res, lattice=lattice, method=interpolation) ################################################### # 3. Take the Difference to measure the enhancement ################################################### enhancement_grid = NC_interp_BEC - matrix_interp_BEC ax, enhance_plot, atoms_plot = multi_slice_viewer_BEC(fig, enhancement_grid, lattice, add_atoms=add_atoms, cell=cell, color_dict=color_dict, marker_dict=marker_dict, cmap_atoms=cmap_atoms, cmap=cmap, ion_cmap=ion_cmap, num_bins=num_bins, resolution=res, is_kriging=(interpolation == 'kriging')) if track_slices: ax.set_title( 'BECs for {} Matrix with {} NanoParticle ({} / {})'.format(matrix_name, np_name, res // 2, res)) else: ax.set_title( 'BECs for {} Matrix with {} NanoParticle ({} / {})'.format(matrix_name, np_name, res // 2, res)) # Now add the colorbars if atoms_plot is not None and cmap_atoms: cb_atoms, pad = add_colorbar(fig, atoms_plot, cbar_pos, shrink=cbar_shrink) cb_atoms.set_label('Ionic BEC') else: cb_atoms = None cb_enhance, pad = add_colorbar(fig, enhance_plot, cbar_pos, shrink=cbar_shrink) cb_enhance.set_label('BEC Enhancement') if legend and add_atoms: ax.legend() if grid: ax.grid() return ax, cb_enhance, cb_atoms, pad