def _plot_radiation_pattern_quiver(ax3d, ned_mt, type): """ Private routine that plots the wave farfield into an :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D` object :type ax3d: :class:`mpl_toolkits.mplot3d.axes3d.Axes3D` :param ax3d: matplotlib Axes3D object :param ned_mt: the 6 comp moment tensor in NED orientation :type type: str :param type: 'P' or 'S' (P or S wave). """ import matplotlib.pyplot as plt if MATPLOTLIB_VERSION < [1, 4]: msg = ("Matplotlib 3D quiver plot needs matplotlib version >= 1.4.") raise ImportError(msg) type = type.upper() if type not in ("P", "S"): msg = ("type must be 'P' or 'S'") raise ValueError(msg) is_p_wave = type == "P" # precompute even spherical grid and directional cosine array points = _equalarea_spherical_grid(nlat=14) if is_p_wave: # get radiation pattern disp = farfield(ned_mt, points, type="P") # normalized magnitude: magn = np.sum(disp * points, axis=0) magn /= np.max(np.abs(magn)) cmap = get_cmap('bwr') else: # get radiation pattern disp = farfield(ned_mt, points, type="S") # normalized magnitude (positive only): magn = np.sqrt(np.sum(disp * disp, axis=0)) magn /= np.max(np.abs(magn)) cmap = get_cmap('Greens') # plot # there is a mlab3d bug that quiver vector colors and lengths # can only be changed if we plot each arrow independently for loc, vec, mag in zip(points.T, disp.T, magn.T): norm = plt.Normalize(-1., 1.) color = cmap(norm(mag)) if is_p_wave: loc *= (1. + mag / 2.) length = abs(mag) / 2.0 else: length = abs(mag) / 5.0 ax3d.quiver(loc[0], loc[1], loc[2], vec[0], vec[1], vec[2], length=length, color=color) ax3d.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), zlim=(-1.5, 1.5), xticks=[-1, 1], yticks=[-1, 1], zticks=[-1, 1], xticklabels=['South', 'North'], yticklabels=['West', 'East'], zticklabels=['Up', 'Down'], title='{} wave farfield'.format(type)) ax3d.view_init(elev=-110., azim=0.)
def _plot_radiation_pattern_quiver(ax3d, ned_mt, type): """ Private routine that plots the wave farfield into an :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D` object :type ax3d: :class:`mpl_toolkits.mplot3d.axes3d.Axes3D` :param ax3d: matplotlib Axes3D object :param ned_mt: the 6 comp moment tensor in NED orientation :type type: str :param type: 'P' or 'S' (P or S wave). """ import matplotlib.pyplot as plt if MATPLOTLIB_VERSION < [1, 4]: msg = ("Matplotlib 3D quiver plot needs matplotlib version >= 1.4.") raise ImportError(msg) type = type.upper() if type not in ("P", "S"): msg = ("type must be 'P' or 'S'") raise ValueError(msg) is_p_wave = type == "P" # precompute even spherical grid and directional cosine array points = _equalarea_spherical_grid(nlat=14) if is_p_wave: # get radiation pattern disp = farfield(ned_mt, points, type="P") # normalized magnitude: magn = np.sum(disp * points, axis=0) magn /= np.max(np.abs(magn)) cmap = get_cmap('bwr') else: # get radiation pattern disp = farfield(ned_mt, points, type="S") # normalized magnitude (positive only): magn = np.sqrt(np.sum(disp * disp, axis=0)) magn /= np.max(np.abs(magn)) cmap = get_cmap('Greens') # plot # there is a mlab3d bug that quiver vector colors and lengths # can only be changed if we plot each arrow independently for loc, vec, mag in zip(points.T, disp.T, magn.T): norm = plt.Normalize(-1., 1.) color = cmap(norm(mag)) if is_p_wave: loc *= (1. + mag/2.) length = abs(mag) / 2.0 else: length = abs(mag) / 5.0 ax3d.quiver(loc[0], loc[1], loc[2], vec[0], vec[1], vec[2], length=length, color=color) ax3d.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), zlim=(-1.5, 1.5), xticks=[-1, 1], yticks=[-1, 1], zticks=[-1, 1], xticklabels=['South', 'North'], yticklabels=['West', 'East'], zticklabels=['Up', 'Down'], title='{} wave farfield'.format(type)) ax3d.view_init(elev=-110., azim=0.)
def test_farfield_2xn_input(self): """ Tests to compute P/S wave farfield radiation pattern using (theta,phi) pairs as input """ # Peru 2001/6/23 20:34:23: # RTP system: [2.245, -0.547, -1.698, 1.339, -3.728, 1.444] # NED system: [-0.547, -1.698, 2.245, -1.444, 1.339, 3.728] mt = [-0.547, -1.698, 2.245, -1.444, 1.339, 3.728] theta = np.arange(0, 360, 60) phi = np.zeros(len(theta)) rays = np.array([theta, phi]) * np.pi / 180.0 result = farfield(mt, rays, 'P') ref = np.array([[0., 1.13501984, -0.873480164, 2.749332e-16, -1.13501984, 0.873480164], [0, 0, -0, 0, -0, 0], [2.245, 0.655304008, 0.504304008, -2.245, -0.655304008, -0.504304008]]) np.testing.assert_allclose(result, ref, rtol=1e-5, atol=1e-8)
def test_farfield_2xn_input(self): """ Tests to compute P/S wave farfield radiation pattern using (theta,phi) pairs as input """ # Peru 2001/6/23 20:34:23: mt = [2.245, -0.547, -1.698, 1.339, -3.728, 1.444] theta = np.arange(0, 360, 60) phi = np.zeros(len(theta)) rays = np.array([theta, phi]) result = farfield(mt, rays, 'P') ref = np.array([[ -0., 1.06567166, -2.26055006, 2.19679324, -0.44435406, -2.07785517 ], [-0., 0., -0., 0., -0., -0.], [ -1.698, 3.32980367, -3.16993006, 1.64100194, -0.15311543, -0.04592479 ]]) np.testing.assert_allclose(result, ref)
def test_farfield_2xn_input(self): """ Tests to compute P/S wave farfield radiation pattern using (theta,phi) pairs as input """ # Peru 2001/6/23 20:34:23: mt = [2.245, -0.547, -1.698, 1.339, -3.728, 1.444] theta = np.arange(0, 360, 60) phi = np.zeros(len(theta)) rays = np.array([theta, phi]) result = farfield(mt, rays, "P") ref = np.array( [ [-0.0, 1.06567166, -2.26055006, 2.19679324, -0.44435406, -2.07785517], [-0.0, 0.0, -0.0, 0.0, -0.0, -0.0], [-1.698, 3.32980367, -3.16993006, 1.64100194, -0.15311543, -0.04592479], ] ) np.testing.assert_allclose(result, ref)
def _write_radiation_pattern_vtk(ned_mt, fname_rpattern='rpattern.vtk', fname_beachlines='beachlines.vtk'): # output a vtkfile that can for exampled be displayed by ParaView mtensor = MomentTensor(ned_mt, system='NED') bb = BeachBall(mtensor, npoints=200) bb._setup_BB(unit_circle=False) # extract the coordinates of the nodal lines neg_nodalline = bb._nodalline_negative pos_nodalline = bb._nodalline_positive # plot radiation pattern and nodal lines points = _equalarea_spherical_grid() ndim, npoints = points.shape dispp = farfield(ned_mt, points, type="P") disps = farfield(ned_mt, points, type="S") # write vector field with open(fname_rpattern, 'w') as vtk_file: vtk_header = '# vtk DataFile Version 2.0\n' + \ 'radiation pattern vector field\n' + \ 'ASCII\n' + \ 'DATASET UNSTRUCTURED_GRID\n' + \ 'POINTS {:d} float\n'.format(npoints) vtk_file.write(vtk_header) # write point locations for x, y, z in np.transpose(points): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write vector field vtk_file.write('POINT_DATA {:d}\n'.format(npoints)) vtk_file.write('VECTORS s_radiation float\n') for x, y, z in np.transpose(disps): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) vtk_file.write('VECTORS p_radiation float\n'.format(npoints)) for x, y, z in np.transpose(dispp): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write nodal lines with open(fname_beachlines, 'w') as vtk_file: npts_neg = neg_nodalline.shape[1] npts_pos = pos_nodalline.shape[1] npts_tot = npts_neg + npts_pos vtk_header = '# vtk DataFile Version 2.0\n' + \ 'beachball nodal lines\n' + \ 'ASCII\n' + \ 'DATASET UNSTRUCTURED_GRID\n' + \ 'POINTS {:d} float\n'.format(npts_tot) vtk_file.write(vtk_header) # write point locations for x, y, z in np.transpose(neg_nodalline): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) for x, y, z in np.transpose(pos_nodalline): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write line segments vtk_file.write('\nCELLS 2 {:d}\n'.format(npts_tot + 4)) ipoints = list(range(0, npts_neg)) + [0] vtk_file.write('{:d} '.format(npts_neg + 1)) for ipoint in ipoints: if ipoint % 30 == 29: vtk_file.write('\n') vtk_file.write('{:d} '.format(ipoint)) vtk_file.write('\n') ipoints = list(range(0, npts_pos)) + [0] vtk_file.write('{:d} '.format(npts_pos + 1)) for ipoint in ipoints: if ipoint % 30 == 29: vtk_file.write('\n') vtk_file.write('{:d} '.format(ipoint + npts_neg)) vtk_file.write('\n') # cell types. 4 means cell type is a poly_line vtk_file.write('\nCELL_TYPES 2\n') vtk_file.write('4\n4')
def _plot_radiation_pattern_mayavi(ned_mt): """ Plot the radiation pattern using MayaVi. This private function uses the mayavi (vtk) library to plot the radiation pattern to screen. Note that you might have to set the QT_API environmental variable to e.g. export QT_API=pyqt that mayavi works properly. :param ned_mt: moment tensor in NED convention """ # use mayavi if possible. try: from mayavi import mlab except Exception as err: print(err) msg = ("ObsPy failed to import MayaVi. " "You need to install the mayavi module " "(e.g. 'conda install mayavi', 'pip install mayavi'). " "If it is installed and still doesn't work, " "try setting the environmental variable QT_API to " "pyqt (e.g. export QT_API=pyqt) before running the " "code. Another option is to avoid mayavi and " "directly use kind='vtk' for vtk file output of the " "radiation pattern that can be used by external " "software like ParaView") raise ImportError(msg) # get mopad moment tensor mopad_mt = MomentTensor(ned_mt, system='NED') bb = BeachBall(mopad_mt, npoints=200) bb._setup_BB(unit_circle=False) # extract the coordinates of the nodal lines neg_nodalline = bb._nodalline_negative pos_nodalline = bb._nodalline_positive # add the first point to the end to close the nodal line neg_nodalline = np.hstack((neg_nodalline, neg_nodalline[:, 0][:, None])) pos_nodalline = np.hstack((pos_nodalline, pos_nodalline[:, 0][:, None])) # plot radiation pattern and nodal lines points = _equalarea_spherical_grid(nlat=20) dispp = farfield(ned_mt, points, type="P") disps = farfield(ned_mt, points, type="S") # get vector lengths normp = np.sum(dispp * points, axis=0) normp /= np.max(np.abs(normp)) norms = np.sqrt(np.sum(disps * disps, axis=0)) norms /= np.max(np.abs(norms)) # make sphere to block view to the other side of the beachball rad = 0.8 pi = np.pi cos = np.cos sin = np.sin phi, theta = np.mgrid[0:pi:101j, 0:2 * pi:101j] x = rad * sin(phi) * cos(theta) y = rad * sin(phi) * sin(theta) z = rad * cos(phi) # p wave radiation pattern mlab.figure(size=(800, 800), bgcolor=(0, 0, 0)) pts1 = mlab.quiver3d(points[0], points[1], points[2], dispp[0], dispp[1], dispp[2], scalars=normp, vmin=-1., vmax=1.) pts1.glyph.color_mode = 'color_by_scalar' mlab.plot3d(*neg_nodalline, color=(0, 0.5, 0), tube_radius=0.01) mlab.plot3d(*pos_nodalline, color=(0, 0.5, 0), tube_radius=0.01) mlab.mesh(x, y, z, color=(0, 0, 0)) # s wave radiation pattern mlab.figure(size=(800, 800), bgcolor=(0, 0, 0)) pts2 = mlab.quiver3d(points[0], points[1], points[2], disps[0], disps[1], disps[2], scalars=norms, vmin=-0., vmax=1.) pts2.glyph.color_mode = 'color_by_scalar' mlab.plot3d(*neg_nodalline, color=(0, 0.5, 0), tube_radius=0.01) mlab.plot3d(*pos_nodalline, color=(0, 0.5, 0), tube_radius=0.01) mlab.mesh(x, y, z, color=(0, 0, 0)) mlab.show()
def _plot_radiation_pattern_sphere(ax3d, ned_mt, type, p_sphere_direction='inwards'): """ Private function that plots a radiation pattern sphere into an :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D`. :type ax3d: :class:`mpl_toolkits.mplot3d.axes3d.Axes3D` :param ax3d: matplotlib Axes3D object :param ned_mt: moment tensor in NED convention :param p_sphere_direction: If this is 'inwards', the tension regions of the beachball deform the radiation sphere inwards. If 'outwards' it deforms outwards. :param type: 'P' or 'S' (P or S wave). """ import matplotlib.pyplot as plt type = type.upper() if type not in ("P", "S"): msg = ("type must be 'P' or 'S'") raise ValueError(msg) is_p_wave = type == "P" # generate spherical mesh that is aligned with the moment tensor null # axis. MOPAD should use NED coordinate system to avoid internal # coordinate transformations mtensor = MomentTensor(ned_mt, system='NED') # use the most isolated eigenvector as axis of symmetry evecs = mtensor.get_eigvecs() evals = np.abs(mtensor.get_eigvals())**2 evals_dev = np.abs(evals - np.mean(evals)) if is_p_wave: if p_sphere_direction == 'outwards': evec_max = evecs[np.argmax(evals_dev)] elif p_sphere_direction == 'inwards': evec_max = evecs[np.argmax(evals)] else: evec_max = evecs[np.argmax(evals_dev)] orientation = np.ravel(evec_max) # get a uv sphere that is oriented along the moment tensor axes ntheta, nphi = 100, 100 points = _oriented_uv_sphere(ntheta=ntheta, nphi=nphi, orientation=orientation) sshape = (ntheta, nphi) # get radiation pattern if is_p_wave: disp = farfield(ned_mt, points, type="P") magn = np.sum(disp * points, axis=0) cmap = get_cmap('bwr') norm = plt.Normalize(-1, 1) else: disp = farfield(ned_mt, points, type="S") magn = np.sqrt(np.sum(disp * disp, axis=0)) cmap = get_cmap('Greens') norm = plt.Normalize(0, 1) magn /= np.max(np.abs(magn)) # compute colours and displace points along normal if is_p_wave: if p_sphere_direction == 'outwards': points *= (1. + np.abs(magn) / 2.) elif p_sphere_direction == 'inwards': points *= (1. + magn / 2.) else: points *= (1. + magn / 2.) colors = np.array([cmap(norm(val)) for val in magn]) colors = colors.reshape(ntheta, nphi, 4) x = points[0].reshape(sshape) y = points[1].reshape(sshape) z = points[2].reshape(sshape) # plot 3d radiation pattern ax3d.plot_surface(x, y, z, rstride=4, cstride=4, facecolors=colors) ax3d.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), zlim=(-1.5, 1.5), xticks=[-1, 1], yticks=[-1, 1], zticks=[-1, 1], xticklabels=['South', 'North'], yticklabels=['West', 'East'], zticklabels=['Up', 'Down'], title='{} wave farfield'.format(type)) ax3d.view_init(elev=-110., azim=0.)
def _write_radiation_pattern_vtk( ned_mt, fname_rpattern='rpattern.vtk', fname_beachlines='beachlines.vtk'): # output a vtkfile that can for exampled be displayed by ParaView mtensor = MomentTensor(ned_mt, system='NED') bb = BeachBall(mtensor, npoints=200) bb._setup_BB(unit_circle=False) # extract the coordinates of the nodal lines neg_nodalline = bb._nodalline_negative pos_nodalline = bb._nodalline_positive # plot radiation pattern and nodal lines points = _equalarea_spherical_grid() ndim, npoints = points.shape dispp = farfield(ned_mt, points, type="P") disps = farfield(ned_mt, points, type="S") # write vector field with open(fname_rpattern, 'w') as vtk_file: vtk_header = '# vtk DataFile Version 2.0\n' + \ 'radiation pattern vector field\n' + \ 'ASCII\n' + \ 'DATASET UNSTRUCTURED_GRID\n' + \ 'POINTS {:d} float\n'.format(npoints) vtk_file.write(vtk_header) # write point locations for x, y, z in np.transpose(points): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write vector field vtk_file.write('POINT_DATA {:d}\n'.format(npoints)) vtk_file.write('VECTORS s_radiation float\n') for x, y, z in np.transpose(disps): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) vtk_file.write('VECTORS p_radiation float\n'.format(npoints)) for x, y, z in np.transpose(dispp): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write nodal lines with open(fname_beachlines, 'w') as vtk_file: npts_neg = neg_nodalline.shape[1] npts_pos = pos_nodalline.shape[1] npts_tot = npts_neg + npts_pos vtk_header = '# vtk DataFile Version 2.0\n' + \ 'beachball nodal lines\n' + \ 'ASCII\n' + \ 'DATASET UNSTRUCTURED_GRID\n' + \ 'POINTS {:d} float\n'.format(npts_tot) vtk_file.write(vtk_header) # write point locations for x, y, z in np.transpose(neg_nodalline): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) for x, y, z in np.transpose(pos_nodalline): vtk_file.write('{:.3e} {:.3e} {:.3e}\n'.format(x, y, z)) # write line segments vtk_file.write('\nCELLS 2 {:d}\n'.format(npts_tot + 4)) ipoints = list(range(0, npts_neg)) + [0] vtk_file.write('{:d} '.format(npts_neg + 1)) for ipoint in ipoints: if ipoint % 30 == 29: vtk_file.write('\n') vtk_file.write('{:d} '.format(ipoint)) vtk_file.write('\n') ipoints = list(range(0, npts_pos)) + [0] vtk_file.write('{:d} '.format(npts_pos + 1)) for ipoint in ipoints: if ipoint % 30 == 29: vtk_file.write('\n') vtk_file.write('{:d} '.format(ipoint + npts_neg)) vtk_file.write('\n') # cell types. 4 means cell type is a poly_line vtk_file.write('\nCELL_TYPES 2\n') vtk_file.write('4\n4')
def _plot_radiation_pattern_sphere( ax3d, ned_mt, type, p_sphere_direction='inwards'): """ Private function that plots a radiation pattern sphere into an :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D`. :type ax3d: :class:`mpl_toolkits.mplot3d.axes3d.Axes3D` :param ax3d: matplotlib Axes3D object :param ned_mt: moment tensor in NED convention :param p_sphere_direction: If this is 'inwards', the tension regions of the beachball deform the radiation sphere inwards. If 'outwards' it deforms outwards. :param type: 'P' or 'S' (P or S wave). """ import matplotlib.pyplot as plt type = type.upper() if type not in ("P", "S"): msg = ("type must be 'P' or 'S'") raise ValueError(msg) is_p_wave = type == "P" # generate spherical mesh that is aligned with the moment tensor null # axis. MOPAD should use NED coordinate system to avoid internal # coordinate transformations mtensor = MomentTensor(ned_mt, system='NED') # use the most isolated eigenvector as axis of symmetry evecs = mtensor.get_eigvecs() evals = np.abs(mtensor.get_eigvals())**2 evals_dev = np.abs(evals - np.mean(evals)) if is_p_wave: if p_sphere_direction == 'outwards': evec_max = evecs[np.argmax(evals_dev)] elif p_sphere_direction == 'inwards': evec_max = evecs[np.argmax(evals)] else: evec_max = evecs[np.argmax(evals_dev)] orientation = np.ravel(evec_max) # get a uv sphere that is oriented along the moment tensor axes ntheta, nphi = 100, 100 points = _oriented_uv_sphere(ntheta=ntheta, nphi=nphi, orientation=orientation) sshape = (ntheta, nphi) # get radiation pattern if is_p_wave: disp = farfield(ned_mt, points, type="P") magn = np.sum(disp * points, axis=0) cmap = get_cmap('bwr') norm = plt.Normalize(-1, 1) else: disp = farfield(ned_mt, points, type="S") magn = np.sqrt(np.sum(disp * disp, axis=0)) cmap = get_cmap('Greens') norm = plt.Normalize(0, 1) magn /= np.max(np.abs(magn)) # compute colours and displace points along normal if is_p_wave: if p_sphere_direction == 'outwards': points *= (1. + np.abs(magn) / 2.) elif p_sphere_direction == 'inwards': points *= (1. + magn / 2.) else: points *= (1. + magn / 2.) colors = np.array([cmap(norm(val)) for val in magn]) colors = colors.reshape(ntheta, nphi, 4) x = points[0].reshape(sshape) y = points[1].reshape(sshape) z = points[2].reshape(sshape) # plot 3d radiation pattern ax3d.plot_surface(x, y, z, rstride=4, cstride=4, facecolors=colors) ax3d.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), zlim=(-1.5, 1.5), xticks=[-1, 1], yticks=[-1, 1], zticks=[-1, 1], xticklabels=['South', 'North'], yticklabels=['West', 'East'], zticklabels=['Up', 'Down'], title='{} wave farfield'.format(type)) ax3d.view_init(elev=-110., azim=0.)
def radiation_pattern(mt, takeoff_angle, azimuth, wavetype='P', system='RTP', normalize=False): """Wrapper of obspy.core.event.source.farfield. Parameters ---------- mt: list(float) Six component moment tensor ([Mxx, Myy, Mzz, Mxy, Mxz, Myz]) takeoff_angle: float or list(float) Takeoff angle of seismic ray (in degree) azimuth: float or list(float) Azimuth of seismic ray (in degree) wavetype: str Specify wave type, 'P' or 'S' system: str Coordinate system of mt, NED|USE|RTP normalize: bool Return normalized radiation pattern Returns ------- float Magnitude of radiation pattern Examples -------- (1) Explosion source: >>> mt = [1, 1, 1, 0, 0, 0] >>> mag = radiation_pattern(mt, 35, 44, wavetype='P', system='RTP') >>> print(mag) 1.0 (2) Event 2001/6/23 20:34:23 >>> mt = [2.245, -0.547, -1.698, 1.339, -3.728, 1.444] >>> mag_P = radiation_pattern(mt, 80, 30, wavetype='P', system='RTP') >>> print(mag_P) 0.92058159502 >>> mag_S = radiation_pattern(mt, 80, 30, wavetype='S', system='RTP') >>> print(mag_S) 3.66100655525 """ from obspy import __version__ as obspy_version if obspy_version == '1.0.2': msg = ("farfield in ObsPy version {} has known issues." "(see issue #1499 and PR #1553).").format(obspy_version) warnings.warn(msg) if system in ('RTP', 'USE'): ned_mt = mt_converter(mt, system_in='RTP', system_out='NED') elif system == 'NED': ned_mt = mt else: raise ValueError("Wrong coordinate system of moment tensor") ray = np.array([[takeoff_angle], [azimuth]]) / 180.0 * np.pi # farfield return three component displacement disp = farfield(ned_mt, ray, wavetype) mag = np.sqrt(np.sum(disp * disp, axis=0))[0] if normalize: mag /= get_scalar_moment(ned_mt) return mag