Source code for magic.butterfly

# -*- coding: utf-8 -*-
import glob
import re
import os
import copy
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as S
from magic.plotlib import cut
from magic.setup import labTex
from .npfile import *
try:
    from scipy.integrate import simps
except:
    from scipy.integrate import simpson as simps


[docs]class Butterfly: """ This class can be used to display the time evolution of the magnetic field for various latitudes (i.e. the well-known butterfly diagrams). These diagrams are usually constructed using MagIC's :ref:`movie files <secMovieFile>`: either radial cuts (like Br_CMB_mov.TAG) or azimuthal-average (like AB_mov.TAG). >>> # Read Br_CMB_mov.ccondAnelN3MagRa2e7Pm2ggg >>> t1 = Butterfly(file='Br_CMB_mov.ccondAnelN3MagRa2e7Pm2ggg', step=1, iplot=False) >>> # Plot it >>> t1.plot(levels=33, cm='seismic', cut=0.6) """
[docs] def __init__(self, file=None, step=1, iplot=True, rad=0.8, lastvar=None, nvar='all', levels=20, cm='RdYlBu_r', precision=np.float32, cut=0.8): """ :param file: when specified, the constructor reads this file, otherwise a list with the possible options is displayed :type file: str :param rad: radial level (normalised to the outer boundary radius) :type rad: float :param iplot: display/hide the plots (default is True) :type iplot: bool :param nvar: the number of time steps (lines) of the movie file one wants to plot starting from the last line :type nvar: int :param lastvar: the number of the last time step to be read :type lastvar: int :param step: the stepping between two lines :type step: int :param levels: the number of contour levels :type levels: int :param cm: the name of the color map :type cm: str :param cut: adjust the contour extrema to max(abs(data))*cut :type cut: float :param precision: precision of the input file, np.float32 for single precision, np.float64 for double precision :type precision: bool """ self.precision = precision if file is None: dat = glob.glob('*_mov.*') str1 = 'Which movie do you want ?\n' for k, movie in enumerate(dat): str1 += ' {}) {}\n'.format(k+1, movie) index = input(str1) try: filename = dat[int(index)-1] except IndexError: print('Non valid index: {} has been chosen instead'.format(dat[0])) filename = dat[0] else: filename = file mot = re.compile(r'.*_mov\.(.*)') end = mot.findall(filename)[0] # DETERMINE THE NUMBER OF LINES BY READING THE LOG FILE logfile = open('log.{}'.format(end), 'r') mot = re.compile(r' ! WRITING MOVIE FRAME NO\s*(\d*).*') for line in logfile.readlines(): if mot.match(line): nlines = int(mot.findall(line)[0]) logfile.close() if lastvar is None: self.var2 = nlines else: self.var2 = lastvar if str(nvar) == 'all': self.nvar = nlines self.var2 = nlines else: self.nvar = nvar # READ the movie file infile = npfile(filename, endian='B') # HEADER version = infile.fort_read('|S64') n_type, n_surface, const, n_fields = infile.fort_read(self.precision) movtype = infile.fort_read(self.precision) self.movtype = int(movtype) n_surface = int(n_surface) # RUN PARAMETERS runid = infile.fort_read('|S64') n_r_mov_tot, n_r_max, n_theta_max, n_phi_tot, minc, self.ra, \ self.ek, self.pr, self.prmag, self.radratio, tScale = infile.fort_read(self.precision) self.n_r_max = int(n_r_max) self.n_theta_max = int(n_theta_max) self.n_phi_tot = int(n_phi_tot) # GRID self.rad = rad self.radius = infile.fort_read(self.precision) ind = np.nonzero(np.where(abs(self.radius-self.rad) \ == min(abs(self.radius-self.rad)), 1, 0)) self.indPlot = ind[0][0] self.theta = infile.fort_read(self.precision) self.phi = infile.fort_read(self.precision) if n_surface == 0: self.surftype = '3d volume' shape = (self.n_r_max, self.n_theta_max, self.n_phi_tot) elif n_surface == 1: self.surftype = 'r_constant' shape = (self.n_theta_max, self.n_phi_tot) self.data = np.zeros((self.n_theta_max, self.nvar), self.precision) elif n_surface == 2: self.surftype = 'theta_constant' shape = (self.n_r_max, self.n_phi_tot) elif n_surface == 3: self.surftype = 'phi_constant' shape = (self.n_r_max, self.n_theta_max) if self.movtype in [8, 9]: shape = (int(n_r_mov_tot)+2, self.n_theta_max) else: shape = (self.n_r_max, self.n_theta_max) self.data = np.zeros((self.n_theta_max, self.nvar), self.precision) self.cmap = plt.get_cmap(cm) self.time = np.zeros(self.nvar, self.precision) for i in range(self.var2-self.nvar): n_frame, t_movieS, omega_ic, omega_ma, movieDipColat, \ movieDipLon, movieDipStrength, \ movieDipStrengthGeo = infile.fort_read(self.precision) data = infile.fort_read(self.precision, shape=shape) for k in range(self.nvar): n_frame, t_movieS, omega_ic, omega_ma, movieDipColat, \ movieDipLon, movieDipStrength, \ movieDipStrengthGeo = infile.fort_read(self.precision) if k % step == 0: self.time[k] = t_movieS #print(k+self.var2-self.nvar) if self.surftype == 'r_constant': data = infile.fort_read(self.precision, shape=shape) self.data[:, k] = data.mean(axis=1) elif self.surftype == 'phi_constant': data = infile.fort_read(self.precision, shape=shape) self.data[:, k] = data[self.indPlot, :] else: # Nevertheless read data = infile.fort_read(self.precision, shape=shape) if step != 1: self.time = self.time[::step] self.data = self.data[:, ::step] if iplot: self.plot(levels=levels,cm=self.cmap,cut=cut)
[docs] def __add__(self, new): """ Overload of the addition operator >>> # Read 2 files >>> b1 = Butterfly(file='AB_mov.test1', iplot=False) >>> b2 = Butterfly(file='AB_mov.test2', iplot=False) >>> # Stack them and display the whole thing >>> b = b1+b2 >>> b.plot(levels=33, contour=True, cut=0.8, cm='seismic') """ out = copy.deepcopy(new) out.time = np.concatenate((self.time, new.time), axis=0) out.data = np.concatenate((self.data, new.data), axis=1) return out
[docs] def plot(self, levels=12, contour=False, renorm=False, cut=0.5, mesh=3, cm='RdYlBu_R'): """ Plotting function :param cm: name of the colormap :type cm: str :param levels: the number of contour levels (only used when iplot=True and contour=True) :type levels: int :param contour: when set to True, display contour levels (pylab.contourf), when set to False, display an image (pylab.imshow) :type contour: bool :param renorm: when set to True, it re-bins the time series in case of irregularly time-spaced data :type renorm: bool :param mesh: when renorm=True, factor of regriding: NewTime = mesh*OldTime :type mesh: int :param cut: adjust the contour extrema to max(abs(data))*cut :type cut: float """ fig = plt.figure() ax = fig.add_subplot(111) vmax = max(abs(self.data.max()), abs(self.data.min())) if contour: im = ax.contourf(self.time, self.theta*180/np.pi, self.data[::-1,:], levels, cmap=cm, aa=True) else: extent = self.time.min(), self.time.max(), -90, 90 if renorm: nx = mesh*len(self.time) x = np.linspace(self.time.min(), self.time.max(), nx) data = np.zeros((len(self.theta), nx), self.precision) for i in range(self.data.shape[0]): tckp = S.splrep(self.time, self.data[i, :]) data[i, :] = S.splev(x, tckp) im = ax.imshow(data, origin='upper', aspect='auto', cmap=cm, extent=extent, interpolation='bicubic') #im = ax.contourf(zi[:-1, :], cmap=cm) #vmax = max(abs(zi.max()), abs(zi.min())) else: im = ax.imshow(self.data, origin='upper', aspect='auto', cmap=cm, extent=extent, interpolation='bicubic') im.set_clim(-cut*vmax, cut*vmax) ax.set_xlabel('Time') ax.set_ylabel('Latitude') if self.surftype == 'phi_constant': if labTex: txt = r'$r = {:.1f}\ r_o$'.format(self.rad) else: txt = r'r = {:.1f} ro'.format(self.rad) ax.text(0.8, 0.07, txt, transform =ax.transAxes) #ax.text(0.05, 0.9, 'a)', transform =ax.transAxes) ax.set_yticks([-90,-45,0,45,90]) if labTex: ax.set_yticklabels([r'$-90^\circ$', r'$-45^\circ$', r'$0^\circ$', r'$45^\circ$', r'$90^\circ$']) else: ax.set_yticklabels(['-90', '-45', '0', '45', '90'])
[docs] def fourier2D(self, renorm=False): """ This function allows to conduct some basic Fourier analysis on the data. It displays two figures: the first one is a contour levels in the (Frequency, Latitude) plane, the second one is integrated over latitudes (thus a simple, power vs Frequency plot) >>> # Load the data without plotting >>> b1 = Butterfly(file='AB_mov.test1', iplot=False) >>> # Fourier analysis >>> b1.fourier2D() :param renorm: when set to True, it rebins the time series in case of irregularly spaced data :type renorm: bool """ if renorm: nx = 3*len(self.time) x = np.linspace(self.time.min(), self.time.max(), nx) data = np.zeros((len(self.theta), nx), self.precision) for i in range(self.data.shape[0]): tckp = S.splrep(self.time, self.data[i, :]) data[i, :] = S.splev(x, tckp) self.data = data self.time = x print("####################") print("Warning time and data have been replaced by the extrapolated values !!!") print("####################") nt = self.time.shape[0] w1 = np.fft.fft(self.data, axis=1) self.amp = np.abs(w1[:, 1:nt//2+1]) dw = 2.*np.pi/(self.time[-1]-self.time[0]) w = dw*np.arange(nt) self.omega = w[1:nt//2+1] self.amp1D = np.zeros_like(self.omega) for i in range(len(self.omega)): self.amp1D[i] = simps(self.amp[:, i], x=self.theta) fig = plt.figure() ax = fig.add_subplot(211) extent = self.omega.min(), self.omega.max(), -90, 90 ax.imshow(self.amp, extent=extent, aspect='auto', origin='upper') ax.set_xlabel('Frequency') ax.set_ylabel('Latitude') ax = fig.add_subplot(212) ax.semilogy(self.omega, self.amp1D) ax.set_xlabel('Frequency') ax.set_ylabel('Spectrum') ax.set_xlim(self.omega.min(), self.omega.max()) print('Fourier frequency:{:.2f}'.format(self.omega[self.amp1D==self.amp1D.max()]))
if __name__ == '__main__': t1 = Butterfly(file='Br_CMB_mov.ccondAnelN3MagRa2e7Pm2ggg', step=1, iplot=False) t1.plot() plt.show()