# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from .libmagic import anelprof, cylSder, cylZder, phideravg, symmetrize, \
progressbar
from .plotlib import cut
from magic import MagicGraph, MagicSetup
from magic.setup import labTex, buildSo
from scipy.ndimage import map_coordinates
from scipy.interpolate import interp1d
import os
import pickle
if buildSo:
try:
from magic.cylavg import *
zavgMode = 'f2py'
except ImportError:
zavgMode = 'python'
else:
zavgMode = 'python'
[docs]def sph2cyl_plane(data, rad, ns):
r"""
This function extrapolates a phi-slice of a spherical shell on
a cylindrical grid
>>> # Read G_1.test
>>> gr = MagicGraph(ivar=1, tag='test')
>>> # phi-average v_\phi and s
>>> vpm = gr.vphi.mean(axis=0)
>>> sm = gr.entropy.mean(axis=0)
>>> # Interpolate on a cylindrical grid
>>> Z, S, outputs = sph2cyl_plane([vpm, sm], gr.radius, 512, 1024)
>>> vpm_cyl, sm_cyl = outputs
:param data: a list of 2-D arrays [(ntheta, nr), (ntheta, nr), ...]
:type data: list(numpy.ndarray)
:param rad: radius
:type rad: numpy.ndarray
:param ns: number of grid points in s direction
:type ns: int
:returns: a python tuple that contains two numpy.ndarray and a list (S,Z,output).
S[nz,ns] is a meshgrid that contains the radial coordinate.
Z[nz,ns] is a meshgrid that contains the vertical coordinate.
output=[arr1[nz,ns], ..., arrN[nz,ns]] is a list of the interpolated
array on the cylindrical grid.
:rtype: tuple
"""
ntheta, nr = data[0].shape
theta = np.linspace(0., np.pi, ntheta)
nz = 2*ns
if zavgMode == 'f2py':
cylRad = np.linspace(0., rad[0], ns)
output = []
for dat in data:
Z, dat_cyl = sph_to_cyl(dat, rad, cylRad, theta)
output.append(dat_cyl)
return cylRad, Z, output
else:
radius = rad[::-1]
theta = np.linspace(0., np.pi, ntheta)
Z, S = np.mgrid[-radius.max():radius.max():nz*1j,0:radius.max():ns*1j]
new_r = np.sqrt(S**2+Z**2).ravel()
new_theta = np.arctan2(S, Z).ravel()
ir = interp1d(radius, np.arange(len(radius)), bounds_error=False)
it = interp1d(theta, np.arange(len(theta)), bounds_error=False)
new_ir = ir(new_r)
new_it = it(new_theta)
new_ir[new_r > radius.max()] = len(radius)-1.
new_ir[new_r < radius.min()] = 0.
coords = np.array([new_it, new_ir])
output = []
for dat in data:
dat_cyl = map_coordinates(dat[:, ::-1], coords, order=3)
dat_cyl[new_r > radius.max()] = 0.
dat_cyl[new_r < radius.min()] = 0.
dat_cyl = dat_cyl.reshape((nz, ns))
output.append(dat_cyl)
return S, Z, output
if zavgMode == 'f2py':
def zavg(input, radius, ns, minc, save=True, filename='vp.pickle',
normed=True, colat=None):
"""
This function computes a z-integration of a list of input arrays
(on the spherical grid). This works well for 2-D (phi-slice) arrays.
In case of 3-D arrays, only one element is allowed (too demanding
otherwise).
:param input: a list of 2-D or 3-D arrays
:type input: list(numpy.ndarray)
:param radius: spherical radius
:type radius: numpy.ndarray
:param ns: radial resolution of the cylindrical grid (nz=2*ns)
:type ns: int
:param minc: azimuthal symmetry
:type minc: int
:param save: a boolean to specify if one wants to save the outputs into
a pickle (default is True)
:type save: bool
:param filename: name of the output pickle when save=True
:type filename: str
:param normed: a boolean to specify if ones wants to simply integrate
over z or compute a z-average (default is True: average)
:type normed: bool
:param colat: an optional array containing the colatitudes
:type colat: numpy.ndarray
:returns: a python tuple that contains two numpy.ndarray and a list
(height,cylRad,output) height[ns] is the height of the
spherical shell for all radii. cylRad[ns] is the cylindrical
radius. output=[arr1[ns], ..., arrN[ns]] contains
the z-integrated output arrays.
:rtype: tuple
"""
ro = radius[0]
ri = radius[-1]
cylRad = np.linspace(ro, 0., ns)
if len(input[0].shape) == 3:
ntheta = input[0].shape[1]
elif len(input[0].shape) == 2:
ntheta = input[0].shape[0]
if colat is None:
theta = np.linspace(0., np.pi, ntheta)
else:
theta = colat
height = np.zeros_like(cylRad)
height[cylRad >= ri] = 2.*np.sqrt(ro**2-cylRad[cylRad >= ri]**2)
height[cylRad < ri] = 2.*(np.sqrt(ro**2-cylRad[cylRad < ri]**2)
-np.sqrt(ri**2-cylRad[cylRad < ri]**2))
if len(input[0].shape) == 3:
nphi = input[0].shape[0]
phi = np.linspace(0., 2.*np.pi/minc, nphi)
output = np.zeros((nphi, ns), dtype=input[0].dtype)
for iphi in progressbar(range(nphi)):
output[iphi, :] = cylmean(input[0][iphi, ...], radius, cylRad,
theta)
if not normed:
output[iphi, :] *= height
if save:
nphi, ntheta, nr = input[0].shape
file = open(filename, 'wb')
pickle.dump([height, cylRad, phi, output], file) # cylindrical average
pickle.dump([radius, phi, input[0][:, ntheta//2, :]], file) # equatorial cut
file.close()
return height, cylRad, phi, output
elif len(input[0].shape) == 2:
output = []
for dat in input:
outIntZ = np.zeros(ns, dtype=dat.dtype)
outIntZ = cylmean(dat, radius, cylRad, theta)
if not normed:
outIntZ *= height
output.append(outIntZ)
if save:
file = open(filename, 'wb')
pickle.dump([radius, cylRad, height], file) # cylindrical average
for k,out in enumerate(output):
pickle.dump(out, file) # cylindrical average
ntheta, nr = input[k].shape
pickle.dump(input[k][ntheta//2, :], file) # equatorial cut
file.close()
return height, cylRad, output
else:
[docs] def zavg(input, radius, ns, minc, save=True, filename='vp.pickle',
normed=True):
"""
This function computes a z-integration of a list of input arrays
(on the spherical grid). This works well for 2-D (phi-slice)
arrays. In case of 3-D arrays, only one element is allowed
(too demanding otherwise).
:param input: a list of 2-D or 3-D arrays
:type input: list(numpy.ndarray)
:param radius: spherical radius
:type radius: numpy.ndarray
:param ns: radial resolution of the cylindrical grid (nz=2*ns)
:type ns: int
:param minc: azimuthal symmetry
:type minc: int
:param save: a boolean to specify if one wants to save the outputs into
a pickle (default is True)
:type save: bool
:param filename: name of the output pickle when save=True
:type filename: str
:param normed: a boolean to specify if ones wants to simply integrate
over z or compute a z-average (default is True: average)
:type normed: bool
:returns: a python tuple that contains two numpy.ndarray and a
list (height,cylRad,output) height[ns] is the height of the
spherical shell for all radii. cylRad[ns] is the cylindrical
radius. output=[arr1[ns], ..., arrN[ns]] contains
the z-integrated output arrays.
:rtype: tuple
"""
nz = 2*ns
ro = radius[0]
ri = radius[-1]
z = np.linspace(-ro, ro, nz)
cylRad = np.linspace(0., ro, ns)
cylRad = cylRad[1:-1]
height = np.zeros_like(cylRad)
height[cylRad >= ri] = 2.*np.sqrt(ro**2-cylRad[cylRad >= ri]**2)
height[cylRad < ri] = 2.*(np.sqrt(ro**2-cylRad[cylRad < ri]**2)
-np.sqrt(ri**2-cylRad[cylRad < ri]**2))
if len(input[0].shape) == 3:
nphi = input[0].shape[0]
phi = np.linspace(0., 2.*np.pi/minc, nphi)
output = np.zeros((nphi, ns-2), dtype=input[0].dtype)
for iphi in progressbar(range(nphi)):
Z, S, out2D = sph2cyl_plane([input[0][iphi, ...]], radius, ns)
S = S[:, 1:-1]
Z = Z[:, 1:-1]
output[iphi, :] = np.trapz(out2D[0][:, 1:-1], z, axis=0)
if normed:
output[iphi, :] /= height
if save:
nphi, ntheta, nr = input[0].shape
file = open(filename, 'wb')
pickle.dump([cylRad, phi, output], file) # cylindrical average
pickle.dump([radius, phi, input[0][:, ntheta//2, :]], file) # equatorial cut
file.close()
return height, cylRad, phi, output
elif len(input[0].shape) == 2:
Z, S, out2D = sph2cyl_plane(input, radius, ns)
S = S[:, 1:-1]
Z = Z[:, 1:-1]
output = []
outIntZ = np.zeros((ns-2), dtype=input[0].dtype)
for k,out in enumerate(out2D):
outIntZ = np.trapz(out[:, 1:-1], z, axis=0)
if normed:
outIntZ /= height
output.append(outIntZ)
if save:
file = open(filename, 'wb')
pickle.dump([radius, cylRad, height], file) # cylindrical average
for k,out in enumerate(output):
pickle.dump(out, file) # cylindrical average
ntheta, nr = input[k].shape
pickle.dump(input[k][ntheta//2, :], file) # equatorial cut
file.close()
return height, cylRad, output
[docs]def sph2cyl(g, ns=None, nz=None):
"""
This function interpolates the three flow (or magnetic field)
components of a :ref:`G_#.TAG <secGraphFile>` file
on a cylindrical grid of size (ns, nz).
.. warning:: This might be really slow!
:param g: input graphic output file
:type g: :py:class:`magic.MagicGraph`
:param ns: number of grid points in the radial direction
:type ns: int
:param nz: number of grid points in the vertical direction
:type nz: int
:returns: a python tuple of five numpy.ndarray (S,Z,vs,vp_cyl,vz).
S[nz,ns] is a meshgrid that contains the radial coordinate.
Z[nz,ns] is a meshgrid that contains the vertical coordinate.
vs[nz,ns] is the radial component of the velocity (or magnetic
field), vp_cyl[nz,ns] the azimuthal component and vz[nz,ns] the
vertical component.
:rtype: tuple
"""
if ns is None or nz is None:
ns = g.nr ; nz = 2*ns
theta = np.linspace(0., np.pi, g.ntheta)
radius = g.radius[::-1]
Z, S = np.mgrid[-radius.max():radius.max():nz*1j,0:radius.max():ns*1j]
new_r = np.sqrt(S**2+Z**2).ravel()
new_theta = np.arctan2(S, Z).ravel()
ir = interp1d(radius, np.arange(len(radius)), bounds_error=False)
it = interp1d(theta, np.arange(len(theta)), bounds_error=False)
new_ir = ir(new_r)
new_it = it(new_theta)
new_ir[new_r > radius.max()] = len(radius)-1.
new_ir[new_r < radius.min()] = 0.
coords = np.array([new_it, new_ir])
vr_cyl = np.zeros((g.npI, nz, ns), dtype=g.vr.dtype)
vp_cyl = np.zeros_like(vr_cyl)
vt_cyl = np.zeros_like(vr_cyl)
for k in progressbar(range(g.npI)):
dat = map_coordinates(g.vphi[k, :, ::-1], coords, order=3)
dat[new_r > radius.max()] = 0.
dat[new_r < radius.min()] = 0.
vp_cyl[k, ...] = dat.reshape((nz, ns))
dat = map_coordinates(g.vtheta[k, :, ::-1], coords, order=3)
dat[new_r > radius.max()] = 0.
dat[new_r < radius.min()] = 0.
vt_cyl[k, ...] = dat.reshape((nz, ns))
dat = map_coordinates(g.vr[k, :, ::-1], coords, order=3)
dat[new_r > radius.max()] = 0.
dat[new_r < radius.min()] = 0.
vr_cyl[k, ...] = dat.reshape((nz, ns))
th3D = np.zeros((g.npI, nz, ns), dtype=g.vr.dtype)
for i in range(g.npI):
th3D[i, ...] = np.arctan2(S, Z)
vs = vr_cyl * np.sin(th3D) + vt_cyl * np.cos(th3D)
vz = vr_cyl * np.cos(th3D) - vt_cyl * np.sin(th3D)
return S, Z, vs, vp_cyl, vz
[docs]class Cyl(MagicSetup):
r"""
This class allows to extrapolate a given :ref:`graphic file <secGraphFile>`
on a cylindrical grid. Once done, the extrapolated file is stored in
a python.pickle file. It is then possible to display 2-D cuts of the extrapolated
arrays (radial cuts, phi-averages, equatorial cuts, z-averages and phi-slices)
.. warning:: This process is actually **very demanding** and it might take a lot
of time to extrapolate the G_#.TAG file. Be careful when choosing the
input value of ns!
>>> # Extrapolate the G file to the cylindrical grid (ns=128, nz=2*ns)
>>> c = Cyl(ivar=1, ns=128)
>>> # Radial cut of v_r
>>> c.surf(field='vr', r=0.8)
>>> # Vertical average of B_\phi
>>> c.avgz(field='Bphi', cm='seismic', levels=33)
>>> # Azimuthal average of v_\phi
>>> c.avg(field='Bphi')
>>> # Equatorial cut of of v_theta
>>> c.equat(field='vtheta')
"""
[docs] def __init__(self, ivar=1, datadir='.', ns=None):
"""
:param ivar: the number of the Graphic file
:type ivar: int
:param datadir: working directory
:type datadir: str
:param ns: number of grid points in the radial direction
:type ns: int
"""
MagicSetup.__init__(self, datadir)
self.datadir = datadir
filename = '{}G_{}.{}'.format('cyl', ivar, self.tag)
if not os.path.exists(filename):
print("sph2cyl...")
gr = MagicGraph(ivar=ivar, datadir=self.datadir)
if ns is None:
self.ns = gr.nr
self.nz = 2*self.ns
else:
self.ns = ns
self.nz = 2*ns
self.nphi = gr.nphi
self.npI = gr.npI
self.minc = gr.minc
self.ro = gr.radius[0]
self.ri = gr.radius[-1]
self.S, self.Z, self.vs, self.vphi, self.vz = sph2cyl(gr,
self.ns, self.nz)
file = open(filename, 'wb')
pickle.dump([self.ns, self.nz, self.nphi, self.npI, self.minc], file)
pickle.dump([self.ro, self.ri], file)
pickle.dump([self.S, self.Z, self.vs, self.vphi, self.vz],
file)
file.close()
else:
print("read cyl file")
file = open(filename, 'r')
self.ns, self.nz, self.nphi, self.npI, self.minc = pickle.load(file)
self.ro, self.ri = pickle.load(file)
self.S, self.Z, self.vs, self.vphi, self.vz = \
pickle.load(file)
file.close()
self.radius = np.linspace(0., self.ro, self.ns)
temp0, rho0, beta0 = anelprof(np.linspace(self.ro, self.ri, self.ns),
self.strat, self.polind)
rho = np.zeros((self.nphi/2, self.ns), dtype=self.vr.dtype)
beta = np.zeros_like(rho)
for i in range(self.nphi/2):
rho[i, :] = rho0
beta[i, :] = beta0
Z, S, [rho, beta] = sph2cyl_plane([rho,beta],
np.linspace(self.ro, self.ri, self.ns),
self.ns)
self.rho = np.zeros_like(self.vs)
self.beta = np.zeros_like(self.vs)
for i in range(self.npI):
self.rho[i, ...] = rho
self.beta[i, ...] = beta
self.z = np.linspace(-self.ro, self.ro, self.nz)
[docs] def surf(self, field='Bphi', r=0.85, vmin=None, vmax=None,
levels=16, cm='RdYlBu_r', normed=True, figsize=None):
r"""
Plot the surface distribution of an input field at a given
input radius (normalised by the outer boundary radius).
>>> c = Cyl(ns=65)
>>> # Surface plot of B_\phi from -10 to 10
>>> c.surf(field='Bphi', r=0.6, vmin=-10, vmax=10, levels=65)
:param field: name of the input field
:type field: str
:param r: radial level (normalised to the outer boundary radius)
:type r: float
:param levels: number of contour levels
:type levels: int
:param cm: name of the color map
:type cm: str
:param normed: when set to True, the contours are normalised fro
-max(field), max(field)
:type normed: bool
:param vmin: truncate the contour levels to values > vmin
:type vmin: float
:param vmax: truncate the contour levels to values < vmax
:type vmax: float
"""
r /= (1-self.ri/self.ro) # as we give a normalised radius
ind = np.nonzero(np.where(abs(self.radius-r) \
== min(abs(self.radius-r)), 1, 0))
indPlot = ind[0][0]
if field in ('Vr', 'vr', 'Ur', 'ur'):
data = self.vp
label = 'Radial velocity'
elif field in ('Vphi', 'vphi', 'Uphi', 'uphi', 'up', 'Up', 'Vp', 'vp'):
data = self.vphi
if labTex:
label = r'$V_{\phi}$'
else:
label = 'vphi'
elif field in ('Vs', 'vs'):
data = self.vs
label = 'Vs'
elif field in ('Vz', 'vz'):
data = self.vz
label = 'Vz'
phi = np.linspace(0., 2.*np.pi, self.nphi)
data[..., indPlot] = cut(data[..., indPlot], vmax, vmin)
data = symmetrize(data, self.minc)
cmap = plt.get_cmap(cm)
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.contourf(phi, self.z, data[..., indPlot].T, levels, cmap=cmap,
aa=True)
rad = self.radius[indPlot] * (1. - self.ri/self.ro)
if labTex:
ax.set_xlabel(r'$\phi$', fontsize=18)
ax.set_ylabel(r'$z$', fontsize=18)
ax.set_title('{}: $r/r_o$ = {:.3f}'.format(label, rad), fontsize=24)
else:
ax.set_xlabel('phi', fontsize=18)
ax.set_ylabel('z', fontsize=18)
ax.set_title('{}: r/ro = {:.3f}'.format(label, rad), fontsize=24)
cbar = plt.colorbar(im)
if field not in ['entropy', 's', 'S'] and normed is True:
im.set_clim(-max(abs(data[..., indPlot].max()),
abs(data[..., indPlot].min())),
max(abs(data[..., indPlot].max()),
abs(data[..., indPlot].min())))
[docs] def equat(self, field='vs', levels=16, cm='RdYlBu_r', normed=True, vmax=None,
vmin=None):
r"""
Plot an input field in the equatorial plane.
>>> c = Cyl(ns=65)
>>> # Equatorial cut of v_\phi
>>> c.equat(field='vphi', cm='seismic', levels=33)
:param field: name of the input field
:type field: str
:param levels: number of contour levels
:type levels: int
:param cm: name of the color map
:type cm: str
:param normed: when set to True, the contours are normalised fro
-max(field), max(field)
:type normed: bool
:param vmin: truncate the contour levels to values > vmin
:type vmin: float
:param vmax: truncate the contour levels to values < vmax
:type vmax: float
"""
if field in ('Vr', 'vr', 'Ur', 'ur'):
data = self.vp
label = 'Radial velocity'
elif field in ('beta'):
data = self.beta
if labTex:
label = r'$\beta$'
else:
label = 'beta'
elif field in ('Vphi', 'vphi', 'Uphi', 'uphi', 'up', 'Up', 'Vp', 'vp'):
data = self.vphi
if labTex:
label = r'$v_{\phi}$'
else:
label = 'vphi'
elif field in ('Vs', 'vs'):
data = self.vs
label = r'$v_s$'
elif field in ('Vz', 'vz'):
data = self.vz
if labTex:
label = r'$v_z$'
else:
label = 'vz'
elif field in ('dvz'):
data = cylZder(self.z, self.vz)
if labTex:
label = r'$\partial v_z/\partial z$'
else:
label = 'dvzdz'
elif field in ('anel'):
betas = cylSder(self.radius, np.log(self.rho))
betaz = cylZder(self.z, np.log(self.rho))
data = self.vs * betas + self.vz * betaz
if labTex:
label = r'$\beta v_r$'
else:
label = 'beta vr'
elif field in ('Cr', 'cr'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
data = self.rho * self.vs * vp
if labTex:
label = r'$\langle \rho v_s v_\phi\rangle$'
else:
label = 'rho vs vphi'
equator = data[:, self.nz/2,:]
equator = cut(equator, vmax, vmin)
equator = symmetrize(equator, self.minc)
phi = np.linspace(0., 2.*np.pi, self.nphi)
rr, pphi = np.meshgrid(self.radius, phi)
xx = rr * np.cos(pphi)
yy = rr * np.sin(pphi)
fig = plt.figure(figsize=(8.25, 6))
ax = fig.add_subplot(111, frameon=False)
cmap = plt.get_cmap(cm)
im = ax.contourf(xx, yy, equator, levels, cmap=cmap)
ax.plot(self.ri * np.cos(phi), self.ri*np.sin(phi), 'k-')
ax.plot(self.ro * np.cos(phi), self.ro*np.sin(phi), 'k-')
ax.set_title(label, fontsize=24)
ax.axis('off')
fig.colorbar(im)
if field not in ['entropy', 's', 'S'] and normed is True:
im.set_clim(-max(abs(equator.max()), abs(equator.min())),
max(abs(equator.max()), abs(equator.min())))
[docs] def avg(self, field='Bphi', levels=16, cm='RdYlBu_r', normed=True,
vmax=None, vmin=None):
"""
Plot the azimutal average of a given field.
>>> c = Cyl(ns=65)
>>> # Azimuthal average of B_r
>>> c.avg(field='Br', cm='seismic', levels=33)
:param field: name of the input field
:type field: str
:param levels: number of contour levels
:type levels: int
:param cm: name of the color map
:type cm: str
:param normed: when set to True, the contours are normalised fro
-max(field), max(field)
:type normed: bool
:param vmin: truncate the contour levels to values > vmin
:type vmin: float
:param vmax: truncate the contour levels to values < vmax
:type vmax: float
"""
if field in ('Vr', 'vr', 'Ur', 'ur'):
data = self.vp
label = 'Radial velocity'
elif field in ('Vphi', 'vphi', 'Uphi', 'uphi', 'up', 'Up', 'Vp', 'vp'):
data = self.vphi
if labTex:
label = r'$V_{\phi}$'
else:
label = 'vphi'
elif field in ('Vs', 'vs'):
data = self.vs
label = 'Vs'
elif field in ('Vz', 'vz'):
data = self.vz
label = 'Vz'
elif field in ('rho'):
data = self.rho
if labTex:
label = r'$\rho$'
else:
label = 'rho'
elif field in ('Cr', 'cr'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
data = self.vs * vp
denom = np.sqrt(np.mean(self.vs**2, axis=0)* np.mean(vp**2, axis=0))
if labTex:
label = r'$\langle v_s v_\phi\rangle$'
else:
label = 'vs vphi'
th = np.linspace(0., np.pi, 128)
if field not in ('Cr', 'cr'):
phiavg = data.mean(axis=0)
else:
mask = np.where(denom == 0, 1, 0)
phiavg = data.mean(axis=0)/(denom+mask)
m1 = np.sqrt(self.S**2+self.Z**2) >= self.ri
m2 = np.sqrt(self.S**2+self.Z**2) <= self.ro
m3 = self.S <= self.ri
m4 = self.S >= self.ri
print('Correlation', phiavg[m1*m2].mean())
print('Correlation out TC', phiavg[m1*m2*m4].mean())
print('Correlation in TC', phiavg[m1*m2*m3].mean())
phiavg = cut(phiavg, vmax, vmin)
fig = plt.figure(figsize=(5.5, 8))
ax = fig.add_subplot(111, frameon=False)
cmap = plt.get_cmap(cm)
im = ax.contourf(self.S, self.Z, phiavg, levels, cmap=cmap)
ax.plot(self.ri*np.sin(th), self.ri*np.cos(th), 'k-')
ax.plot(self.ro*np.sin(th), self.ro*np.cos(th), 'k-')
ax.plot([0., 0], [self.ri, self.ro], 'k-')
ax.plot([0., 0], [-self.ri, -self.ro], 'k-')
ax.set_title(label, fontsize=24)
ax.axis('off')
fig.colorbar(im)
if field not in ['entropy', 's', 'S'] and normed is True:
im.set_clim(-max(abs(phiavg.max()), abs(phiavg.min())),
max(abs(phiavg.max()), abs(phiavg.min())))
[docs] def avgz(self, field='vs', levels=16, cm='RdYlBu_r', normed=True, vmin=None,
vmax=None, avg=False):
"""
Plot the vertical average of a given field.
>>> c = Cyl(ns=65)
>>> # Vertical average of v_s
>>> c.avg(field='vs', cm='seismic', levels=33)
:param field: name of the input field
:type field: str
:param levels: number of contour levels
:type levels: int
:param cm: name of the color map
:type cm: str
:param normed: when set to True, the contours are normalised fro
-max(field), max(field)
:type normed: bool
:param vmin: truncate the contour levels to values > vmin
:type vmin: float
:param vmax: truncate the contour levels to values < vmax
:type vmax: float
:param avg: when set to True, an additional figure with the phi-average
profile is also displayed
:type avg: bool
"""
phi = np.linspace(0., 2.*np.pi, self.nphi)
rr, pphi = np.meshgrid(self.radius, phi)
xx = rr * np.cos(pphi)
yy = rr * np.sin(pphi)
if field in ('Vr', 'vr', 'Ur', 'ur'):
data = self.vphi
label = 'Radial velocity'
elif field in ('betaz'):
betaz = cylZder(self.z, np.log(self.rho))
data = self.vz * betaz
data *= self.vs
if labTex:
label = r'$\beta_z u_z$'
else:
label = 'betaz uz'
elif field in ('betas'):
betas = cylSder(self.radius, np.log(self.rho))
data = self.vs * betas
data *= self.vs
if labTex:
label = r'$\beta_s u_s$'
else:
label = 'betas us'
elif field in ('rho'):
data = self.rho
if labTex:
label = r'$\rho$'
else:
label = 'rho'
elif field in ('anel'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
betas = cylSder(self.radius, np.log(self.rho))
betaz = cylZder(self.z, np.log(self.rho))
data = self.vs * betas + self.vz * betaz
data1 = cylSder(self.radius, self.vphi*self.S)-phideravg(self.vs, self.minc)
mask = np.where(self.S == 0, 1, 0)
data1 = data1/(self.S+mask)
data *= data1
if labTex:
label = r'$\beta u_r$'
else:
label = 'beta ur'
elif field in ('vortz'):
data = cylSder(self.radius, self.vphi*self.S)-phideravg(self.vs, self.minc)
mask = np.where(self.S == 0, 1, 0)
data = data/(self.S+mask)
if labTex:
label = r'$\omega_z$'
else:
label = 'omegaz'
elif field in ('vopot'):
data = cylSder(self.radius, self.vphi*self.S)-phideravg(self.vs, self.minc)
mask = np.where(self.S == 0, 1, 0)
data = data/(self.S+mask)
data = data-2./self.ek*np.log(self.rho)
label = 'vopot'
elif field in ('Vphi', 'vphi', 'Uphi', 'uphi', 'up', 'Up', 'Vp', 'vp'):
data = self.vphi
if labTex:
label = r'$V_{\phi}$'
else:
label = 'vphi'
elif field in ('Vs', 'vs'):
data = self.vs
if labTex:
label = r'$v_s$'
else:
label = 'vs'
elif field in ('Vz', 'vz'):
data = self.vz
if labTex:
label = r'$v_z$'
else:
label = 'vz'
elif field in ('vpc'):
data = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
if labTex:
label = r'$v_p$ conv'
else:
label = 'vphi conv'
elif field in ('Cr', 'cr'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
data = self.rho * self.vs * vp
denom = np.zeros((self.npI, self.ns), dtype=vp.dtype)
if labTex:
label = r'$\langle \rho v_s v_\phi\rangle$'
else:
label = 'rho vs vphi'
elif field in ('reynolds'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
phi = np.linspace(0., 2.*np.pi, self.npI)
data = self.rho * self.vs * vp
if labTex:
label = r'$\rho v_s v_\phi$'
else:
label = 'rho vs vphi'
elif field in ('vsvp'):
vp = self.vphi.copy()-self.vphi.mean(axis=0) # convective vp
phi = np.linspace(0., 2.*np.pi, self.npI)
data = self.vs * vp
if labTex:
label = r'$v_s v_\phi$'
else:
label = 'vs vphi'
elif field in ('vrvs'):
th2D = np.arctan2(self.S, self.Z)
vr = self.vs * np.sin(th2D) + self.vz * np.cos(th2D)
phi = np.linspace(0., 2.*np.pi, self.npI)
data = self.vs * vr
denom = np.zeros((self.npI, self.ns), dtype=vr.dtype)
if labTex:
label = r'$\rho v_s v_r$'
else:
label = 'rho vs vr'
elif field in ('dvz'):
data = cylZder(self.z, self.vz)
data1 = cylSder(self.radius, self.vphi*self.S)-phideravg(self.vs, self.minc)
mask = np.where(self.S == 0, 1, 0)
data1 = data1/(self.S+mask)
data *= data1
if labTex:
label = r'$\partial v_z/\partial z$'
else:
label = 'dvz/dz'
elif field in ('balance'):
if labTex:
label = r'$\partial v_z/\partial z+\beta v_r$'
else:
label = 'dvz/dz + beta*vr'
data = cylZder(self.z, self.vz)
betas = cylSder(self.radius, np.log(self.rho))
betaz = cylZder(self.z, np.log(self.rho))
data1 = self.vs * betas + self.vz * betaz
data += data1
data2 = cylSder(self.radius, self.vphi*self.S)-phideravg(self.vs, self.minc)
mask = np.where(self.S == 0, 1, 0)
data2 = data2/(self.S+mask)
data *= data2
equator = np.zeros((self.npI, self.ns), dtype=self.vs.dtype)
for i, rad in enumerate(self.radius):
if rad <= self.ri:
zo = np.sqrt(self.ro**2-rad**2)
zi = np.sqrt(self.ri**2-rad**2)
m1 = abs(self.z) <= zo
m2 = abs(self.z) >= zi
equator[:, i] = data[:, m1*m2, i].mean(axis=1)
if field in ('Cr', 'cr'):
denom[:, i] = np.sqrt( \
np.mean(self.rho[:, m1*m2, i]*self.vs[:, m1*m2, i]**2, axis=1)\
* np.mean(self.rho[:, m1*m2, i]*vp[:, m1*m2, i]**2, axis=1))
elif field in ('vrvs'):
denom[:, i] = np.sqrt( \
np.mean(vr[:, m1*m2, i]**2, axis=1)\
* np.mean(self.vs[:, m1*m2, i]**2, axis=1))
elif rad > self.ri and rad < self.ro:
zo = np.sqrt(self.ro**2-rad**2)
m1 = self.z >= -zo
m2 = self.z <= zo
equator[:, i] = data[:, m1*m2, i].mean(axis=1)
if field in ('Cr', 'cr'):
denom[:, i] = np.sqrt( \
np.mean(self.rho[:, m1*m2, i]*self.vs[:, m1*m2, i]**2, axis=1)\
* np.mean(self.rho[:, m1*m2, i]*vp[:, m1*m2, i]**2, axis=1))
elif field in ('vrvs'):
denom[:, i] = np.sqrt( \
np.mean(vr[:, m1*m2, i]**2, axis=1)\
* np.mean(self.vs[:, m1*m2, i]**2, axis=1))
if field in ('Cr', 'cr', 'vrvs'):
mask = np.where(denom == 0, 1, 0)
equator /= (denom+mask)
equator = cut(equator, vmax, vmin)
equator = symmetrize(equator, self.minc)
fig = plt.figure(figsize=(8.25, 6))
ax = fig.add_subplot(111, frameon=False)
cmap = plt.get_cmap(cm)
im = ax.contourf(xx, yy, equator, levels, cmap=cmap)
ax.plot(self.ri * np.cos(phi), self.ri*np.sin(phi), 'k-')
ax.plot(self.ro * np.cos(phi), self.ro*np.sin(phi), 'k-')
ax.set_title(label, fontsize=24)
ax.axis('off')
fig.colorbar(im)
if avg:
fig = plt.figure()
ax = fig.add_subplot(111)
if field in ('vphi'):
dat = np.mean(equator, axis=0)
dat = dat[:-1]
ax.plot(self.radius[:-1], dat)
else:
ax.plot(self.radius, np.mean(equator, axis=0))
ax.set_xlabel('Radius', fontsize=18)
ax.set_xlim(0, self.radius.max())
if field not in ['entropy', 's', 'S'] and normed is True:
im.set_clim(-max(abs(equator.max()), abs(equator.min())),
max(abs(equator.max()), abs(equator.min())))
[docs] def slice(self, field='Bphi', lon_0=0., levels=16, cm='RdYlBu_r',
normed=True):
"""
Plot an azimuthal slice of a given field.
>>> c = Cyl(ns=65)
>>> # Slices of v_r at 30 and 60 degrees
>>> c.slice(field='vr', lon_0=[30, 60])
:param field: name of the input field
:type field: str
:param lon_0: the longitude of the slice in degrees, or a list of longitudes
:type lon_0: float or list
:param levels: number of contour levels
:type levels: int
:param cm: name of the color map
:type cm: str
:param normed: when set to True, the contours are normalised fro
-max(field), max(field)
:type normed: bool
"""
if field in ('Vr', 'vr', 'Ur', 'ur'):
data = self.vp
label = 'Radial velocity'
elif field in ('Vphi', 'vphi', 'Uphi', 'uphi', 'up', 'Up', 'Vp', 'vp'):
data = self.vphi
if labTex:
label = r'$v_{phi}$'
else:
label = 'vphi'
elif field in ('Vs', 'vs'):
data = self.vs
if labTex:
label = r'$v_s$'
else:
label = 'vs'
elif field in ('Vz', 'vz'):
data = self.vz
if labTex:
label = r'$v_z$'
else:
label = 'vz'
data = symmetrize(data, self.minc)
th = np.linspace(-np.pi/2, np.pi/2, 128)
phi = np.linspace(0., 360, self.nphi)
lon_0 = np.asarray(lon_0)
cmap = plt.get_cmap(cm)
if len(lon_0) > 1:
fig = plt.figure(figsize=(3.5*len(lon_0), 5.1))
for k, lon in enumerate(lon_0):
ind = np.nonzero(np.where(abs(phi-lon) \
== min(abs(phi-lon)), 1, 0))
indPlot = ind[0][0]
phislice = data[indPlot, ...]
ax = fig.add_subplot(1,len(lon_0),k+1, frameon=False)
im = ax.contourf(self.S, self.Z, phislice, levels, cmap=cmap)
ax.plot(self.ro*np.cos(th), self.ro*np.sin(th), 'k-')
ax.plot(self.ri*np.cos(th), self.ri*np.sin(th), 'k-')
ax.plot([0., 0], [self.ri, self.ro], 'k-')
ax.plot([0., 0], [-self.ri, -self.ro], 'k-')
ax.axis('off')
ax.set_title(label+r' ${}^\circ$'.format(lon))
#fig.colorbar(im, orientation='horizontal')
else:
ind = np.nonzero(np.where(abs(phi-lon_0[0]) \
== min(abs(phi-lon_0[0])), 1, 0))
indPlot = ind[0][0]
phislice = data[indPlot, ...]
fig = plt.figure(figsize=(5.5, 8))
ax = fig.add_subplot(111, frameon=False)
im = ax.contourf(self.S, self.Z, phislice, levels, cmap=cmap)
ax.plot(self.ro*np.cos(th), self.ro*np.sin(th), 'k-')
ax.plot(self.ri*np.cos(th), self.ri*np.sin(th), 'k-')
ax.plot([0., 0], [self.ri, self.ro], 'k-')
ax.plot([0., 0], [-self.ri, -self.ro], 'k-')
ax.set_title(label, fontsize=24)
ax.axis('off')
fig.colorbar(im)
if field not in ['entropy', 's', 'S'] and normed is True:
im.set_clim(-max(abs(phislice.max()), abs(phislice.min())),
max(abs(phislice.max()), abs(phislice.min())))
if __name__ == '__main__':
c = Cyl(ivar=1)
c.equat(field='vs', normed=False)
plt.show()