def test_iron(self): recip = reciprocal_lattice([[-2.708355, 2.708355, 2.708355], [2.708355, -2.708355, 2.708355], [2.708355, 2.708355, -2.708355]]) expected_recip = [[0., 1.15996339, 1.15996339], [1.15996339, 0., 1.15996339], [1.15996339, 1.15996339, 0.]] npt.assert_allclose(recip, expected_recip)
def test_identity(self): recip = reciprocal_lattice([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) expected_recip = [[2*math.pi, 0., 0.], [0., 2*math.pi, 0.], [0., 0., 2*math.pi]] npt.assert_allclose(recip, expected_recip)
def test_graphite(self): recip = reciprocal_lattice([[4.025915, -2.324363, 0.000000], [-0.000000, 4.648726, 0.000000], [0.000000, 0.000000, 12.850138]]) expected_recip = [[1.56068503860106, 0., 0.], [0.780342519300529, 1.3515929541082, 0.], [0., 0., 0.488958586061845]] npt.assert_allclose(recip, expected_recip)
def plot_dispersion(data, title='', btol=10.0, up=True, down=True, **line_kwargs): """ Creates a Matplotlib figure of the band structure Parameters ---------- data: Data object Data object containing the frequencies and other data required for plotting (qpts, n_ions, cell_vecs) title : string, optional The figure title. Default: '' btol : float, optional Determines the limit for plotting sections of reciprocal space on different subplots, as a fraction of the median distance between q-points. Default: 10.0 up : boolean, optional Whether to plot spin up frequencies (if applicable). Default: True down : boolean, optional Whether to plot spin down frequencies (if applicable). Default: True **line_kwargs : Line2D properties, optional Used in the axes.plot command to specify properties like linewidth, linestyle Returns ------- fig : matplotlib.figure.Figure or None If matplotlib.pyplot can be imported, returns a Figure containing subplot(s) for the plotted band structure, otherwise returns None. If there is a large gap between some q-points there can be multiple subplots. """ try: import matplotlib.pyplot as plt except ImportError: warnings.warn(('Cannot import Matplotlib to plot dispersion (maybe ' 'Matplotlib is not installed?). To install Euphonic\'s' ' optional Matplotlib dependency, try:\n\npip install' ' euphonic[matplotlib]\n')) raise cell_vec = (data.cell_vec.to('angstrom').magnitude) recip_latt = reciprocal_lattice(cell_vec) abscissa = calc_abscissa(data.qpts, recip_latt) # Determine reciprocal space coordinates that are far enough apart to be # in separate subplots, and determine index limits diff = np.diff(abscissa) median = np.median(diff) breakpoints = np.where(diff / median > btol)[0] imin = np.concatenate(([0], breakpoints + 1)) imax = np.concatenate((breakpoints, [len(abscissa) - 1])) # Calculate width ratios so that the x-scale is the same for each subplot subplot_widths = [ abscissa[imax[i]] - abscissa[imin[i]] for i in range(len(imax)) ] gridspec = dict( width_ratios=[w / subplot_widths[0] for w in subplot_widths]) # Create figure with correct number of subplots n_subplots = len(breakpoints) + 1 fig, subplots = plt.subplots(1, n_subplots, sharey=True, gridspec_kw=gridspec) if n_subplots == 1: # Ensure subplots is always an array subplots = np.array([subplots]) # Y-axis formatting, only need to format y-axis for first subplot as they # share the y-axis # Replace 1/cm with cm^-1 units_str = data._e_units inverse_unit_index = units_str.find('/') if inverse_unit_index > -1: units_str = units_str[inverse_unit_index + 1:] subplots[0].set_ylabel('Energy (' + units_str + r'$^{-1}$)') else: subplots[0].set_ylabel('Energy (' + units_str + ')') subplots[0].ticklabel_format(style='sci', scilimits=(-2, 2), axis='y') # Configure each subplot # Calculate x-axis (recip space) ticks and labels xlabels, qpts_with_labels = recip_space_labels(data) for i, label in enumerate(xlabels): if label == 'GAMMA': xlabels[i] = r'$\Gamma$' if np.all(xlabels == ''): xlabels = np.around(data.qpts[qpts_with_labels, :], decimals=2) xticks = abscissa[qpts_with_labels] for i, ax in enumerate(subplots): # X-axis formatting # Set high symmetry point x-axis ticks/labels ax.set_xticks(xticks) ax.xaxis.grid(True, which='major') # Convert xlabels to list from Numpy array to avoid elementwise # comparison FutureWarning when calling set_xticklabels if not isinstance(xlabels, list): xlabels = xlabels.tolist() # Rotate long tick labels if len(max(xlabels, key=len)) >= 11: ax.set_xticklabels(xlabels, rotation=90) else: ax.set_xticklabels(xlabels) ax.set_xlim(left=abscissa[imin[i]], right=abscissa[imax[i]]) # Plot frequencies and Fermi energy if up: if hasattr(data, 'split_i') and data.split_i.size > 0: split_i = data.split_i else: split_i = None # If there is LO-TO splitting, plot in sections if split_i is not None: section_i = np.where( np.logical_and(split_i > imin[i], split_i < imax[i]))[0] n_sections = section_i.size + 1 else: n_sections = 1 # Put frequencies in order if reorder_freqs() has been called if hasattr(data, '_mode_map'): freqs = data.freqs.magnitude[ np.arange(len(data.qpts))[:, np.newaxis], data._mode_map] if split_i is not None: split_freqs = data.split_freqs.magnitude[ np.arange(len(split_i))[:, np.newaxis], data._mode_map[split_i]] else: freqs = data.freqs.magnitude if split_i is not None: split_freqs = data.split_freqs.magnitude if n_sections > 1: section_edges = np.concatenate( ([imin[i]], split_i[section_i], [imax[i]])) for n in range(n_sections): plot_freqs = np.copy( freqs[section_edges[n]:section_edges[n + 1] + 1]) if n == 0: if (imin[i] in split_i): # First point in this subplot is split gamma point # Replace freqs with split freqs at gamma point split_idx = np.where(split_i == imin[i])[0][0] plot_freqs[0] = split_freqs[split_idx] else: # Replace freqs with split freqs at gamma point plot_freqs[0] = split_freqs[section_i[n - 1]] ax.plot(abscissa[section_edges[n]:section_edges[n + 1] + 1], plot_freqs, lw=1.0, **line_kwargs) else: ax.plot(abscissa[imin[i]:imax[i] + 1], freqs[imin[i]:imax[i] + 1], lw=1.0, **line_kwargs) if down and hasattr(data, 'freq_down') and len(data.freq_down) > 0: freq_down = data.freq_down.magnitude ax.plot(abscissa[imin[i]:imax[i] + 1], freq_down[imin[i]:imax[i] + 1], lw=1.0, **line_kwargs) if hasattr(data, 'fermi'): for i, ef in enumerate(data.fermi.magnitude): if i == 0: ax.axhline(y=ef, ls='dashed', c='k', label=r'$\epsilon_F$') else: ax.axhline(y=ef, ls='dashed', c='k') # Only set legend for last subplot, they all have the same legend labels if hasattr(data, 'fermi'): subplots[-1].legend() # Make sure axis/figure titles aren't cut off. Rect is used to leave some # space at the top of the figure for suptitle fig.suptitle(title) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) return fig
def output_grace(data, seedname='out', up=True, down=True): """ Creates a .agr Grace file of the band structure Parameters ---------- data: Data object Data object containing the frequencies and other data required for plotting (qpts, n_ions, cell_vecs) seedname : string, optional Determines the figure title and output file name, seedname.agr. Default: 'out' up : boolean, optional Whether to plot spin up frequencies (if applicable). Default: True down : boolean, optional Whether to plot spin down frequencies (if applicable). Default: True """ try: from PyGrace.grace import Grace except ImportError: warnings.warn(('PyGrace is not installed, attempting to write .agr ' 'Grace file anyway. If using Python 2, you can install' ' PyGrace from https://github.com/pygrace/pygrace'), stacklevel=2) # Do calculations required for axis, tick labels etc. # Calculate distance along x axis cell_vec = (data.cell_vec.to('angstrom').magnitude) recip_latt = reciprocal_lattice(cell_vec) abscissa = calc_abscissa(data.qpts, recip_latt) # Calculate x-axis (recip space) ticks and labels xlabels, qpts_with_labels = recip_space_labels(data) units_str = data._e_units inverse_unit_index = units_str.find('/') if inverse_unit_index > -1: units_str = units_str[inverse_unit_index + 1:] yaxis_label = '\\f{{Symbol}}e\\f{{}} ({0}\S-1\\N)'.format(units_str) else: yaxis_label = '\\f{{Symbol}}e\\f{{}} ({0})'.format(units_str) # Format tick labels for i, label in enumerate(xlabels): if label == 'GAMMA': # Format gamma symbol label = '\\f{Symbol}G\\f{}' if 'PyGrace' in sys.modules: grace = Grace() grace.background_fill = 'on' graph = grace.add_graph() graph.yaxis.label.text = yaxis_label graph.xaxis.tick.set_spec_ticks(abscissa[qpts_with_labels].tolist(), [], xlabels.tolist()) graph.xaxis.tick.major_grid = 'on' if len(max(xlabels, key=len)) >= 11: graph.xaxis.ticklabel.configure(angle=315, char_size=1.0) for i in range(data.n_branches): if up: ds = graph.add_dataset( zip(abscissa, data.freqs[:, i].magnitude)) ds.line.configure(linewidth=2.0, color=i % 16) ds.symbol.shape = 0 if down and hasattr(data, 'freq_down') and len(data.freq_down) > 0: ds = graph.add_dataset( zip(abscissa, data.freq_down[:, i].magnitude)) ds.line.configure(linewidth=2.0, color=i % 16) ds.symbol.shape = 0 if hasattr(data, 'fermi'): for i, ef in enumerate(data.fermi.magnitude): ds = graph.add_dataset(zip([0, abscissa[-1]], [ef, ef])) ds.line.configure(linewidth=2.0, color=1, linestyle=3) graph.set_world_to_limits() grace.write_file(seedname + '.agr') else: with open(seedname + '.agr', 'w') as f: f.write('@with g0\n') f.write('@title "{0}"\n'.format(seedname)) f.write('@view 0.150000, 0.250000, 0.700000, 0.850000\n') f.write('@world xmin 0\n') f.write('@world xmax {0:.3f}\n'.format(abscissa[-1] + 0.002)) f.write('@world ymin 0\n') f.write('@default linewidth 2.0\n') f.write('@default char size 1.5\n') f.write('@autoscale onread yaxes\n') f.write('@yaxis bar linewidth 2.0\n') f.write('@yaxis label "{0}"\n'.format(yaxis_label)) f.write('@yaxis label char size 1.5\n') f.write('@yaxis ticklabel char size 1.5\n') f.write('@xaxis bar linewidth 2.0\n') f.write('@xaxis label char size 1.5\n') f.write('@xaxis tick major linewidth 1.6\n') f.write('@xaxis tick major grid on\n') f.write('@xaxis tick spec type both\n') f.write('@xaxis tick spec {0:d}\n'.format(len(xlabels))) # Rotate long tick labels if len(max(xlabels, key=len)) <= 11: f.write('@xaxis ticklabel char size 1.5\n') else: f.write('@xaxis ticklabel char size 1.0\n') f.write('@xaxis ticklabel angle 315\n') for i, label in enumerate(xlabels): f.write('@xaxis tick major {0:d},{1:8.3f}\n'.format( i, abscissa[qpts_with_labels[i]])) f.write('@xaxis ticklabel {0:d},"{1}"\n'.format(i, label)) # Write frequencies for i in range(data.n_branches): n_sets = 0 if up: f.write('@target G0.S{0:d}\n'.format(n_sets)) f.write('@type xy\n') for j, freq in enumerate(data.freqs[:, i].magnitude): f.write('{0: .15e} {1: .15e}\n'.format( abscissa[j], freq)) n_sets += 1 if (down and hasattr(data, 'freq_down') and len(data.freq_down) > 0): f.write('@target G0.S{0:d}\n'.format(n_sets)) f.write('@type xy\n') for j, freq in enumerate(data.freq_down[:, i].magnitude): f.write('{0: .15e} {1: .15e}\n'.format( abscissa[j], freq)) n_sets += 1 f.write('&\n') # Write Fermi level if hasattr(data, 'fermi'): for i, ef in enumerate(data.fermi.magnitude): f.write('@ G0.S{0:d} line linestyle 3\n'.format( data.n_branches + i)) f.write( '@ G0.S{0:d} line color 1\n'.format(data.n_branches + i)) f.write('@target G0.S{0:d}\n'.format(data.n_branches + i)) f.write('@type xy\n') f.write('{0: .15e} {1: .15e}\n'.format(0, ef)) f.write('{0: .15e} {1: .15e}\n'.format(abscissa[-1], ef)) f.write('&\n')
def plot_sqw_map(data, vmin=None, vmax=None, ratio=None, ewidth=0, qwidth=0, cmap='viridis', title=''): """ Plots an q-E scattering plot using imshow Parameters ---------- data : PhononData or InterpolationData object Data object for which calculate_sqw_map has been called, containing sqw_map and sqw_ebins attributes for plotting vmin : float, optional Minimum of data range for colormap. See Matplotlib imshow docs Default: None vmax : float, optional Maximum of data range for colormap. See Matplotlib imshow docs Default: None ratio : float, optional Ratio of the size of the y and x axes. e.g. if ratio is 2, the y-axis will be twice as long as the x-axis Default: None ewidth : float, optional, default 0 The FWHM of the Gaussian energy resolution function in the same units as freqs qwidth : float, optional, default 0 The FWHM of the Gaussian q-vector resolution function cmap : string, optional, default 'viridis' Which colormap to use, see Matplotlib docs title : string, optional The figure title. Default: '' Returns ------- fig : matplotlib.figure.Figure or None If matplotlib.pyplot can be imported, is a Figure with a single subplot, otherwise is None ims : (n_qpts,) 'matplotlib.image.AxesImage' ndarray or None If matplotlib.pyplot can be imported, is an array of AxesImage objects, one for each q-point, for easier access to some attributes/functions. Otherwise is None """ try: import matplotlib as mpl import matplotlib.pyplot as plt except ImportError: warnings.warn(('Cannot import Matplotlib to plot S(q,w) (maybe ' 'Matplotlib is not installed?). To install Euphonic\'s' ' optional Matplotlib dependency, try:\n\npip install' ' euphonic[matplotlib]\n')) raise ebins = data.sqw_ebins.magnitude # Apply broadening if ewidth or qwidth: qbin_width = np.linalg.norm( np.mean(np.absolute(np.diff(data.qpts, axis=0)), axis=0)) qbins = np.linspace(0, qbin_width * data.n_qpts + qbin_width, data.n_qpts + 1) # If no width has been set, make widths small enough to have # effectively no broadening if not qwidth: qwidth = (qbins[1] - qbins[0]) / 10 if not ewidth: ewidth = (ebins[1] - ebins[0]) / 10 sqw_map = signal.fftconvolve( data.sqw_map, np.transpose(gaussian_2d(qbins, ebins, qwidth, ewidth)), 'same') else: sqw_map = data.sqw_map # Calculate qbin edges cell_vec = (data.cell_vec.to('angstrom').magnitude) recip = reciprocal_lattice(cell_vec) abscissa = calc_abscissa(data.qpts, recip) qmid = (abscissa[1:] + abscissa[:-1]) / 2 qwidths = qmid + qmid[0] qbins = np.concatenate(([0], qwidths, [2 * qwidths[-1] - qwidths[-2]])) if ratio: ymax = qbins[-1] / ratio else: ymax = 1.0 if vmin is None: vmin = np.amin(sqw_map) if vmax is None: vmax = np.amax(sqw_map) fig, ax = plt.subplots(1, 1) ims = np.empty((data.n_qpts), dtype=mpl.image.AxesImage) for i in range(data.n_qpts): ims[i] = ax.imshow(np.transpose(sqw_map[i, np.newaxis]), interpolation='none', origin='lower', extent=[qbins[i], qbins[i + 1], 0, ymax], vmin=vmin, vmax=vmax, cmap=cmap) ax.set_ylim(0, ymax) ax.set_xlim(qbins[0], qbins[-1]) # Calculate energy tick labels ytick_spacing_opts = [100, 50, 20, 10, 5, 2, 1, 0.1] min_n_yticks = 5 ytick_spacing = ytick_spacing_opts[np.argmax( (ebins[-1] - ebins[0]) / ytick_spacing_opts > min_n_yticks)] ytick_min = np.ceil(ebins[0] / ytick_spacing) * ytick_spacing ytick_max = np.ceil(ebins[-1] / ytick_spacing) * ytick_spacing ylabels = np.arange(ytick_min, ytick_max, ytick_spacing) yticks = (ylabels - ebins[0]) / (ebins[-1] - ebins[0]) * ymax ax.set_yticks(yticks) ax.set_yticklabels(ylabels) units_str = data._e_units inverse_unit_index = units_str.find('/') if inverse_unit_index > -1: units_str = units_str[inverse_unit_index + 1:] ax.set_ylabel('Energy (' + units_str + r'$^{-1}$)') else: ax.set_ylabel('Energy (' + units_str + ')') # Calculate q-space ticks and labels xlabels, qpts_with_labels = recip_space_labels(data) for i, label in enumerate(xlabels): if label == 'GAMMA': xlabels[i] = r'$\Gamma$' if np.all(xlabels == ''): xlabels = np.around(data.qpts[qpts_with_labels, :], decimals=2) xticks = (qbins[qpts_with_labels] + qbins[qpts_with_labels + 1]) / 2 # Set high symmetry point x-axis ticks/labels ax.set_xticks(xticks) ax.xaxis.grid(True, which='major') # Convert xlabels to list from Numpy array to avoid elementwise # comparison FutureWarning when calling set_xticklabels if not isinstance(xlabels, list): xlabels = xlabels.tolist() # Rotate long tick labels if len(max(xlabels, key=len)) >= 11: ax.set_xticklabels(xlabels, rotation=90) else: ax.set_xticklabels(xlabels) fig.suptitle(title) return fig, ims
def _read_phonon_data(seedname, path): """ Reads data from a .phonon file and returns it in a dictionary Parameters ---------- seedname : str Seedname of file(s) to read path : str Path to dir containing the file(s), if in another directory Returns ------- data_dict : dict A dict with the following keys: 'n_ions', 'n_branches', 'n_qpts' 'cell_vec', 'recip_vec', 'ion_r', 'ion_type', 'ion_mass', 'qpts', 'weights', 'freqs', 'eigenvecs', 'split_i', 'split_freqs', 'split_eigenvecs' Meta information: 'seedname', 'path' and 'model'. """ file = os.path.join(path, seedname + '.phonon') with open(file, 'r') as f: (n_ions, n_branches, n_qpts, cell_vec, ion_r, ion_type, ion_mass) = _read_phonon_header(f) qpts = np.zeros((n_qpts, 3)) weights = np.zeros(n_qpts) freqs = np.zeros((n_qpts, n_branches)) ir = np.array([]) raman = np.array([]) eigenvecs = np.zeros((n_qpts, n_branches, n_ions, 3), dtype='complex128') split_i = np.array([], dtype=np.int32) split_freqs = np.empty((0, n_branches)) split_eigenvecs = np.empty((0, n_branches, n_ions, 3)) # Need to loop through file using while rather than number of q-points # as sometimes points are duplicated first_qpt = True qpt_line = f.readline() prev_qpt_num = -1 qpt_num_patt = re.compile('q-pt=\s*(\d+)') float_patt = re.compile('-?\d+\.\d+') while qpt_line: qpt_num = int(re.search(qpt_num_patt, qpt_line).group(1)) - 1 floats = re.findall(float_patt, qpt_line) qpts[qpt_num] = [float(x) for x in floats[:3]] weights[qpt_num] = float(floats[3]) freq_lines = [f.readline().split() for i in range(n_branches)] tmp = np.array([float(line[1]) for line in freq_lines]) if qpt_num != prev_qpt_num: freqs[qpt_num, :] = tmp elif is_gamma(qpts[qpt_num]): split_i = np.concatenate((split_i, [qpt_num])) split_freqs = np.concatenate((split_freqs, tmp[np.newaxis])) ir_index = 2 raman_index = 3 if is_gamma(qpts[qpt_num]): ir_index += 1 raman_index += 1 if len(freq_lines[0]) > ir_index: if first_qpt: ir = np.zeros((n_qpts, n_branches)) ir[qpt_num, :] = [float(line[ir_index]) for line in freq_lines] if len(freq_lines[0]) > raman_index: if first_qpt: raman = np.zeros((n_qpts, n_branches)) raman[qpt_num, :] = [ float(line[raman_index]) for line in freq_lines ] [f.readline() for x in range(2)] # Skip 2 label lines lines = np.array( [f.readline().split()[2:] for x in range(n_ions * n_branches)], dtype=np.float64) lines_i = np.column_stack(([ lines[:, 0] + lines[:, 1] * 1j, lines[:, 2] + lines[:, 3] * 1j, lines[:, 4] + lines[:, 5] * 1j ])) tmp = np.zeros((n_branches, n_ions, 3), dtype=np.complex128) for i in range(n_branches): tmp[i, :, :] = lines_i[i * n_ions:(i + 1) * n_ions, :] if qpt_num != prev_qpt_num: eigenvecs[qpt_num] = tmp elif is_gamma(qpts[qpt_num]): split_eigenvecs = np.concatenate( (split_eigenvecs, tmp[np.newaxis])) first_qpt = False qpt_line = f.readline() prev_qpt_num = qpt_num data_dict = {} data_dict['n_ions'] = n_ions data_dict['n_branches'] = n_branches data_dict['n_qpts'] = n_qpts data_dict['cell_vec'] = (cell_vec * ureg('angstrom').to('bohr')).magnitude data_dict['recip_vec'] = ((reciprocal_lattice(cell_vec) / ureg.angstrom).to('1/bohr')).magnitude data_dict['ion_r'] = ion_r data_dict['ion_type'] = ion_type data_dict['ion_mass'] = ion_mass * (ureg('amu')).to('e_mass').magnitude data_dict['qpts'] = qpts data_dict['weights'] = weights data_dict['freqs'] = ((freqs * (1 / ureg.cm)).to('E_h', 'spectroscopy')).magnitude data_dict['eigenvecs'] = eigenvecs data_dict['split_i'] = split_i data_dict['split_freqs'] = ((split_freqs * (1 / ureg.cm)).to( 'E_h', 'spectroscopy')).magnitude data_dict['split_eigenvecs'] = split_eigenvecs # Meta information data_dict['model'] = 'CASTEP' data_dict['seedname'] = seedname data_dict['path'] = path return data_dict
def _read_bands_data(seedname, path): """ Reads data from a .bands file (and a .castep file if available) and returns it in a dictionary Parameters ---------- seedname : str Seedname of file(s) to read path : str Path to dir containing the file(s), if in another directory Returns ------- data_dict : dict A dict with the following keys: 'n_qpts', 'n_spins', 'n_branches', 'fermi', 'cell_vec', 'recip_vec', 'qpts', 'weights', 'freqs', 'freq_down'. If a .castep file is available to read, the keys 'n_ions', 'ion_r' and 'ion_type' are also present. Meta information: 'seedname', 'path' and 'model'. """ file = os.path.join(path, seedname + '.bands') with open(file, 'r') as f: n_qpts = int(f.readline().split()[3]) n_spins = int(f.readline().split()[4]) f.readline() # Skip number of electrons line n_branches = int(f.readline().split()[3]) fermi = np.array([float(x) for x in f.readline().split()[5:]]) f.readline() # Skip unit cell vectors line cell_vec = [[float(x) for x in f.readline().split()[0:3]] for i in range(3)] qpts = np.zeros((n_qpts, 3)) weights = np.zeros(n_qpts) freqs_qpt = np.zeros(n_branches) freqs = np.zeros((n_qpts, n_branches)) if n_spins == 2: freq_down = np.zeros((n_qpts, n_branches)) else: freq_down = np.array([]) # Need to loop through file using while rather than number of k-points # as sometimes points are duplicated line = f.readline().split() while line: qpt_num = int(line[1]) - 1 qpts[qpt_num, :] = [float(x) for x in line[2:5]] weights[qpt_num] = float(line[5]) for j in range(n_spins): spin = int(f.readline().split()[2]) # Read frequencies for k in range(n_branches): freqs_qpt[k] = float(f.readline()) if spin == 1: freqs[qpt_num, :] = freqs_qpt elif spin == 2: freq_down[qpt_num, :] = freqs_qpt line = f.readline().split() data_dict = {} data_dict['n_qpts'] = n_qpts data_dict['n_spins'] = n_spins data_dict['n_branches'] = n_branches data_dict['fermi'] = fermi data_dict['cell_vec'] = cell_vec data_dict['recip_vec'] = reciprocal_lattice(cell_vec) data_dict['qpts'] = qpts data_dict['weights'] = weights data_dict['freqs'] = freqs data_dict['freq_down'] = freq_down # Try to get extra data (ionic species, coords) from .castep file try: n_ions, ion_r, ion_type = _read_castep_data(seedname, path) data_dict['n_ions'] = n_ions data_dict['ion_r'] = ion_r data_dict['ion_type'] = ion_type except IOError: pass # Meta information data_dict['model'] = 'CASTEP' data_dict['seedname'] = seedname data_dict['path'] = path return data_dict
def _read_interpolation_data(seedname, path): """ Reads data from a .castep_bin or .check file and returns it in a dictionary Parameters ---------- seedname : str Seedname of file(s) to read path : str Path to dir containing the file(s), if in another directory Returns ------- data_dict : dict A dict with the following keys: 'n_ions', 'n_branches', 'cell_vec', 'recip_vec', 'ion_r', 'ion_type', 'ion_mass', 'force_constants', 'sc_matrix', 'n_cells_in_sc' and 'cell_origins'. Also contains 'born' and 'dielectric' if they are present in the .castep_bin or .check file. Meta information: 'seedname', 'path' and 'model'. """ file = os.path.join(path, seedname + '.castep_bin') if not os.path.isfile(file): print( '{:s}.castep_bin file not found, trying to read {:s}.check'.format( seedname, seedname)) file = os.path.join(path, seedname + '.check') with open(file, 'rb') as f: int_type = '>i4' float_type = '>f8' header = '' first_cell_read = True while header.strip() != b'END': header = _read_entry(f) if header.strip() == b'BEGIN_UNIT_CELL': # CASTEP writes the cell twice: the first is the geometry # optimised cell, the second is the original cell. We only # want the geometry optimised cell. if first_cell_read: n_ions, cell_vec, ion_r, ion_mass, ion_type = _read_cell( f, int_type, float_type) first_cell_read = False elif header.strip() == b'FORCE_CON': sc_matrix = np.transpose( np.reshape(_read_entry(f, int_type), (3, 3))) n_cells_in_sc = int( np.rint(np.absolute(np.linalg.det(sc_matrix)))) # Transpose and reshape fc so it is indexed [nc, i, j] force_constants = np.ascontiguousarray( np.transpose( np.reshape(_read_entry(f, float_type), (n_cells_in_sc, 3 * n_ions, 3 * n_ions)), axes=[0, 2, 1])) cell_origins = np.reshape(_read_entry(f, int_type), (n_cells_in_sc, 3)) fc_row = _read_entry(f, int_type) elif header.strip() == b'BORN_CHGS': born = np.reshape(_read_entry(f, float_type), (n_ions, 3, 3)) elif header.strip() == b'DIELECTRIC': dielectric = np.transpose( np.reshape(_read_entry(f, float_type), (3, 3))) data_dict = {} data_dict['n_ions'] = n_ions data_dict['n_branches'] = 3 * n_ions data_dict['cell_vec'] = cell_vec data_dict['recip_vec'] = reciprocal_lattice(cell_vec) data_dict['ion_r'] = ion_r - np.floor(ion_r) # Normalise ion coordinates data_dict['ion_type'] = ion_type data_dict['ion_mass'] = ion_mass # Set entries relating to 'FORCE_CON' block try: data_dict['force_constants'] = force_constants data_dict['sc_matrix'] = sc_matrix data_dict['n_cells_in_sc'] = n_cells_in_sc data_dict['cell_origins'] = cell_origins except NameError: raise Exception( ('Force constants matrix could not be found in {:s}.\n Ensure ' 'PHONON_WRITE_FORCE_CONSTANTS: true has been set when running ' 'CASTEP').format(file)) # Set entries relating to dipoles try: data_dict['born'] = born data_dict['dielectric'] = dielectric except UnboundLocalError: pass # Meta information data_dict['model'] = 'CASTEP' data_dict['seedname'] = seedname data_dict['path'] = path return data_dict