Files
TomoATT/examples/utils/functions_for_data.py
2025-12-17 10:53:43 +08:00

2415 lines
87 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# %%
# Initialization, class definition, and declaration.
import os
import math
from obspy import UTCDateTime
import numpy as np
import copy
class Event(): # class of earthquake
def __init__(self):
self.name = "nan" # evname1 Earthquake name, recommended as "earthquake".
self.id = -1
self.lat = 0.0
self.lon = 0.0
self.dep = 0.0
self.mag = 0.0
self.ortime = UTCDateTime(1999,1,1,0,0,0)
self.Nt = 0 # Number of the absolute traveltime of earthquake
self.Ncs_dt = 0 # Number of the commmon source differential traveltime of earthquake
self.Ncr_dt = 0 # Number of the commmon receiver differential traveltime of earthquake
self.t = {} # stname1+phase -> (stname1, phase, time, data_weight)
self.cs_dt = {} # stname1 + stname2 + phase -> (stname1, stname2, phase, dif_time, data_weight)
self.cr_dt = {} # stname1 + evname2 + phase -> (stname1, evname2, phase, dif_time, data_weight)
self.azi_gap = 360.0 # the max azimuthal gap of each earthquake
self.misfit = {} # traveltime residual of the data, the difference between real data and synthetic data, used for evaluation. stname or stname1+stname2 or stname1+evname2 -> residual
self.tag = {} # additional tags for the earthquake, e.g., azi_gap, weight. (azimuthal gap, weight of the earthquake)
class Station():
def __init__(self):
self.name = "nan" # stname1, recommend: network.stname
self.id = -1
self.lat = 0.0
self.lon = 0.0
self.ele = 0.0
self.tag = {} # additional tags for the station, e.g., wright
# %% [markdown]
# Functions: some basic auxiliary functions for processing data
# %%
# function cal_dis(lat1, lon1,lat2, lon2) (in kilometers) cal_azimuth(lat1, lon1, lat2, lon2) (degree) calculate epicentral distance (km) and azimuth (degree)
def cal_dis(lat1, lon1,lat2, lon2, R = 6371):
latitude1 = (math.pi/180)*lat1
latitude2 = (math.pi/180)*lat2
longitude1 = (math.pi/180)*lon1
longitude2= (math.pi/180)*lon2
# Therefore, the spherical distance between points A and B is:{arccos[sinb*siny+cosb*cosy*cos(a-x)]}*R
# Radius of the earth
if((lat1-lat2)**2+(lon1-lon2)**2<0.000001):
return 0
d = math.acos(math.sin(latitude1)*math.sin(latitude2)+ math.cos(latitude1)*math.cos(latitude2)*math.cos(longitude2-longitude1))/math.pi*180
return d * 2 * math.pi * R / 360
def cal_azimuth(lat1, lon1, lat2, lon2):
lat1_rad = lat1 * math.pi / 180
lon1_rad = lon1 * math.pi / 180
lat2_rad = lat2 * math.pi / 180
lon2_rad = lon2 * math.pi / 180
y = math.sin(lon2_rad - lon1_rad) * math.cos(lat2_rad)
x = math.cos(lat1_rad) * math.sin(lat2_rad) - math.sin(lat1_rad) * math.cos(lat2_rad) * math.cos(lon2_rad - lon1_rad)
brng = math.atan2(y, x) * 180 / math.pi
if((lat1-lat2)**2+(lon1-lon2)**2<0.0001):
return 0
return float((brng + 360.0) % 360.0)
# %%
# Function: Coordinate rotation rotate_src_rec(ev_info, st_info, theta0, phi0, psi): rotate to the new coordinate system, satisfying the center point transformation r0, t0, p0 -> r0, 0, 0 and an anticlockwise rotation angle psi.
# Satisfying the center point transformation r0, t0, p0 -> r0, 0, 0 and an anticlockwise rotation angle psi.
import numpy as np
RAD2DEG = 180/np.pi
DEG2RAD = np.pi/180
R_earth = 6371.0
# Spherical coordinates to Cartesian coordinate
def rtp2xyz(r,theta,phi):
x = r * np.cos(theta*DEG2RAD) * np.cos(phi*DEG2RAD)
y = r * np.cos(theta*DEG2RAD) * np.sin(phi*DEG2RAD)
z = r * np.sin(theta*DEG2RAD)
return (x,y,z)
# Cartesian coordinates to Spherical coordinate
def xyz2rtp(x,y,z):
# theta: -90~90; phi: -180~180
r = np.sqrt(x**2+y**2+z**2)
theta = np.arcsin(z/r)
phi = np.arcsin(y/r/np.cos(theta))
idx = np.where((phi > 0) & (x*y < 0))
phi[idx] = np.pi - phi[idx]
idx = np.where((phi < 0) & (x*y > 0))
phi[idx] = -np.pi - phi[idx]
# for i in range(phi.size):
# if(phi[i] > 0 and x[i]*y[i] < 0):
# phi[i] = np.pi - phi[i]
# if(phi[i] < 0 and x[i]*y[i] > 0):
# phi[i] = -np.pi - phi[i]
return (r,theta*RAD2DEG,phi*RAD2DEG)
# anti-clockwise rotation along x-axis
def rotate_x(x,y,z,theta):
new_x = x
new_y = y * np.cos(theta*DEG2RAD) + z * -np.sin(theta*DEG2RAD)
new_z = y * np.sin(theta*DEG2RAD) + z * np.cos(theta*DEG2RAD)
return (new_x,new_y,new_z)
# anti-clockwise rotation along y-axis
def rotate_y(x,y,z,theta):
new_x = x * np.cos(theta*DEG2RAD) + z * np.sin(theta*DEG2RAD)
new_y = y
new_z = x * -np.sin(theta*DEG2RAD) + z * np.cos(theta*DEG2RAD)
return (new_x,new_y,new_z)
# anti-clockwise rotation along z-axis
def rotate_z(x,y,z,theta):
new_x = x * np.cos(theta*DEG2RAD) + y * -np.sin(theta*DEG2RAD)
new_y = x * np.sin(theta*DEG2RAD) + y * np.cos(theta*DEG2RAD)
new_z = z
return (new_x,new_y,new_z)
# spherical Rotation
# rotate to the new coordinate, satisfying the center r0,t0,p0 -> r0,0,0 and a anticlockwise angle psi
def rtp_rotation(t,p,theta0,phi0,psi):
# step 1: r,t,p -> x,y,z
(x,y,z) = rtp2xyz(1.0,t,p)
# step 2: anti-clockwise rotation with -phi0 along z-axis: r0,t0,p0 -> r0,t0,0
(x,y,z) = rotate_z(x,y,z,-phi0)
# step 3: anti-clockwise rotation with theta0 along y-axis: r0,t0,0 -> r0,0,0
(x,y,z) = rotate_y(x,y,z,theta0)
# # step 4: anti-clockwise rotation with psi along x-axis
(x,y,z) = rotate_x(x,y,z,psi)
# step 5: x,y,z -> r,t,p
(new_r,new_t,new_p) = xyz2rtp(x,y,z)
return (new_t,new_p)
def rtp_rotation_reverse(new_t,new_p,theta0,phi0,psi):
# step 1: r,t,p -> x,y,z
(x,y,z) = rtp2xyz(1.0,new_t,new_p)
# step 2: anti-clockwise rotation with -psi along x-axis
(x,y,z) = rotate_x(x,y,z,-psi)
# step 3: anti-clockwise rotation with -theta0 along y-axis: r0,0,0 -> r0,t0,0
(x,y,z) = rotate_y(x,y,z,-theta0)
# step 4: anti-clockwise rotation with phi0 along z-axis: r0,t0,0 -> r0,t0,p0
(x,y,z) = rotate_z(x,y,z,phi0)
# step 5: x,y,z -> r,t,p
(r,t,p) = xyz2rtp(x,y,z)
return (t,p)
def rotate_src_rec(ev_info,st_info,theta0,phi0,psi):
ev_info_rotate = {}
st_info_rotate = {}
# rotate earthquakes
for key_ev in ev_info:
ev = ev_info[key_ev]
ev_lat = np.array([ev.lat]); ev_lon = np.array([ev.lon])
(ev_lat,ev_lon,) = rtp_rotation(ev_lat,ev_lon,theta0,phi0,psi)
ev.lat = ev_lat[0]; ev.lon = ev_lon[0]
ev_info_rotate[key_ev] = ev
# rotate stations
for key_st in st_info:
st = st_info[key_st]
st_lat = np.array([st.lat]); st_lon = np.array([st.lon])
(st_lat,st_lon) = rtp_rotation(st_lat,st_lon,theta0,phi0,psi)
st.lat = st_lat[0]; st.lon = st_lon[0]
st_info_rotate[key_st] = st
return (ev_info_rotate,st_info_rotate)
def rotate_src_rec_reverse(ev_info_rotate,st_info_rotate,theta0,phi0,psi):
ev_info = {}
st_info = {}
# rotate earthquakes
for key_ev in ev_info_rotate:
ev = ev_info_rotate[key_ev]
ev_lat = np.array([ev.lat]); ev_lon = np.array([ev.lon])
(ev_lat,ev_lon,) = rtp_rotation_reverse(ev_lat,ev_lon,theta0,phi0,psi)
ev.lat = ev_lat[0]; ev.lon = ev_lon[0]
ev_info[key_ev] = ev
# rotate stations
for key_st in st_info_rotate:
st = st_info_rotate[key_st]
st_lat = np.array([st.lat]); st_lon = np.array([st.lon])
(st_lat,st_lon) = rtp_rotation_reverse(st_lat,st_lon,theta0,phi0,psi)
st.lat = st_lat[0]; st.lon = st_lon[0]
st_info[key_st] = st
return (ev_info,st_info)
# %%
# # Function: Coordinate rotation rotate_src_rec(ev_info, st_info, theta0, phi0, psi): rotate to the new coordinate system, satisfying the center point transformation r0, t0, p0 -> r0, 0, 0 and an anticlockwise rotation angle psi.
# # Satisfying the center point transformation r0, t0, p0 -> r0, 0, 0 and an anticlockwise rotation angle psi.
# import numpy as np
# RAD2DEG = 180/np.pi
# DEG2RAD = np.pi/180
# R_earth = 6371.0
# # Spherical coordinates to Cartesian coordinate
# def rtp2xyz(r,theta,phi):
# x = r * np.cos(theta*DEG2RAD) * np.cos(phi*DEG2RAD)
# y = r * np.cos(theta*DEG2RAD) * np.sin(phi*DEG2RAD)
# z = r * np.sin(theta*DEG2RAD)
# return (x,y,z)
# # Cartesian coordinates to Spherical coordinate
# def xyz2rtp(x,y,z):
# # theta: -90~90; phi: -180~180
# r = np.sqrt(x**2+y**2+z**2)
# theta = np.arcsin(z/r)
# phi = np.arcsin(y/r/np.cos(theta))
# if(phi > 0 and x*y < 0):
# phi = np.pi - phi
# if(phi < 0 and x*y > 0):
# phi = -np.pi - phi
# return (r,theta*RAD2DEG,phi*RAD2DEG)
# # anti-clockwise rotation along x-axis
# def rotate_x(x,y,z,theta):
# new_x = x
# new_y = y * np.cos(theta*DEG2RAD) + z * -np.sin(theta*DEG2RAD)
# new_z = y * np.sin(theta*DEG2RAD) + z * np.cos(theta*DEG2RAD)
# return (new_x,new_y,new_z)
# # anti-clockwise rotation along y-axis
# def rotate_y(x,y,z,theta):
# new_x = x * np.cos(theta*DEG2RAD) + z * np.sin(theta*DEG2RAD)
# new_y = y
# new_z = x * -np.sin(theta*DEG2RAD) + z * np.cos(theta*DEG2RAD)
# return (new_x,new_y,new_z)
# # anti-clockwise rotation along z-axis
# def rotate_z(x,y,z,theta):
# new_x = x * np.cos(theta*DEG2RAD) + y * -np.sin(theta*DEG2RAD)
# new_y = x * np.sin(theta*DEG2RAD) + y * np.cos(theta*DEG2RAD)
# new_z = z
# return (new_x,new_y,new_z)
# # spherical Rotation
# # rotate to the new coordinate, satisfying the center r0,t0,p0 -> r0,0,0 and a anticlockwise angle psi
# def rtp_rotation(t,p,theta0,phi0,psi):
# # step 1: r,t,p -> x,y,z
# (x,y,z) = rtp2xyz(1.0,t,p)
# # step 2: anti-clockwise rotation with -phi0 along z-axis: r0,t0,p0 -> r0,t0,0
# (x,y,z) = rotate_z(x,y,z,-phi0)
# # step 3: anti-clockwise rotation with theta0 along y-axis: r0,t0,0 -> r0,0,0
# (x,y,z) = rotate_y(x,y,z,theta0)
# # # step 4: anti-clockwise rotation with psi along x-axis
# (x,y,z) = rotate_x(x,y,z,psi)
# # step 5: x,y,z -> r,t,p
# (new_r,new_t,new_p) = xyz2rtp(x,y,z)
# return (new_t,new_p)
# def rtp_rotation_reverse(new_t,new_p,theta0,phi0,psi):
# # step 1: r,t,p -> x,y,z
# (x,y,z) = rtp2xyz(1.0,new_t,new_p)
# # step 2: anti-clockwise rotation with -psi along x-axis
# (x,y,z) = rotate_x(x,y,z,-psi)
# # step 3: anti-clockwise rotation with -theta0 along y-axis: r0,0,0 -> r0,t0,0
# (x,y,z) = rotate_y(x,y,z,-theta0)
# # step 4: anti-clockwise rotation with phi0 along z-axis: r0,t0,0 -> r0,t0,p0
# (x,y,z) = rotate_z(x,y,z,phi0)
# # step 5: x,y,z -> r,t,p
# (r,t,p) = xyz2rtp(x,y,z)
# return (t,p)
# def rotate_src_rec(ev_info,st_info,theta0,phi0,psi):
# ev_info_rotate = {}
# st_info_rotate = {}
# # rotate earthquakes
# for key_ev in ev_info:
# ev = ev_info[key_ev]
# (ev.lat,ev.lon,) = rtp_rotation(ev.lat,ev.lon,theta0,phi0,psi)
# ev_info_rotate[key_ev] = ev
# # rotate stations
# for key_st in st_info:
# st = st_info[key_st]
# (st.lat,st.lon) = rtp_rotation(st.lat,st.lon,theta0,phi0,psi)
# st_info_rotate[key_st] = st
# return (ev_info_rotate,st_info_rotate)
# def rotate_src_rec_reverse(ev_info_rotate,st_info_rotate,theta0,phi0,psi):
# ev_info = {}
# st_info = {}
# # rotate earthquakes
# for key_ev in ev_info_rotate:
# ev = ev_info_rotate[key_ev]
# (ev.lat,ev.lon) = rtp_rotation_reverse(ev.lat,ev.lon,theta0,phi0,psi)
# ev_info[key_ev] = ev
# # rotate stations
# for key_st in st_info_rotate:
# st = st_info_rotate[key_st]
# (st.lat,st.lon) = rtp_rotation_reverse(st.lat,st.lon,theta0,phi0,psi)
# st_info[key_st] = st
# return (ev_info,st_info)
# %%
# linear_regression(X,Y)
def linear_regression(X,Y):
slope,intercept = np.polyfit(X,Y,deg=1)
fitted_values = slope * X + intercept
residual = Y - fitted_values
SEE = np.std(residual)
return (slope,intercept,SEE)
# %% [markdown]
#
# Functions: obtain target information from ev_info and st_info
# %%
# function: output the [lon,lat,dep,weight] of the earthquake
def data_lon_lat_dep_wt_ev(ev_info):
lat = []
lon = []
dep = []
weight = []
for key in ev_info:
lat.append(ev_info[key].lat)
lon.append(ev_info[key].lon)
dep.append(ev_info[key].dep)
try:
weight.append(ev_info[key].tag["weight"])
except:
weight.append(1.0)
return [np.array(lon),np.array(lat),np.array(dep),np.array(weight)]
# %%
# function: output the [lon, lat, dep, ortime] of the earthquake
def data_ev_loc(ev_info):
lat = []
lon = []
dep = []
ortime = []
for key in ev_info:
lat.append(ev_info[key].lat)
lon.append(ev_info[key].lon)
dep.append(ev_info[key].dep)
ortime.append(ev_info[key].ortime.timestamp)
return [np.array(lon),np.array(lat),np.array(dep),np.array(ortime)]
# %%
# function: output the [lon,lat,dep,weight] of the station
def data_lon_lat_ele_wt_st(ev_info,st_info):
names = {}
lat = []
lon = []
ele = []
weight = []
for key_ev in ev_info:
for key_t in ev_info[key_ev].t: # absolute traveltime data
name_st = ev_info[key_ev].t[key_t][0]
names[name_st] = name_st
for key_t in ev_info[key_ev].cs_dt: # common source differential traveltime data
name_st = ev_info[key_ev].cs_dt[key_t][0]
names[name_st] = name_st
name_st = ev_info[key_ev].cs_dt[key_t][1]
names[name_st] = name_st
for key_t in ev_info[key_ev].cr_dt: # common receiver differential traveltime data
name_st = ev_info[key_ev].cr_dt[key_t][0]
names[name_st] = name_st
for name in names: # only output the station which has data
lat.append(st_info[name].lat)
lon.append(st_info[name].lon)
ele.append(st_info[name].ele)
try:
weight.append(st_info[name].tag["weight"])
except:
weight.append(1.0)
return [np.array(lon),np.array(lat),np.array(ele),np.array(weight)]
# %%
# function: output the [dis,time] of all data
def data_dis_time(ev_info,st_info):
all_dis = []
all_time = []
for key_ev in ev_info:
lat_ev = ev_info[key_ev].lat
lon_ev = ev_info[key_ev].lon
dep_ev = ev_info[key_ev].dep
for key_t in ev_info[key_ev].t:
all_time.append(ev_info[key_ev].t[key_t][2])
lat_st = st_info[ev_info[key_ev].t[key_t][0]].lat
lon_st = st_info[ev_info[key_ev].t[key_t][0]].lon
ele_st = st_info[ev_info[key_ev].t[key_t][0]].ele
dis = math.sqrt(cal_dis(lat_ev,lon_ev,lat_st,lon_st)**2 + (dep_ev+ele_st/1000)**2)
all_dis.append(dis)
return [np.array(all_dis),np.array(all_time)]
# %%
# function: output the [epidis,time] of all data
def data_epidis_time(ev_info,st_info):
all_dis = []
all_time = []
for key_ev in ev_info:
lat_ev = ev_info[key_ev].lat
lon_ev = ev_info[key_ev].lon
for key_t in ev_info[key_ev].t:
all_time.append(ev_info[key_ev].t[key_t][2])
lat_st = st_info[ev_info[key_ev].t[key_t][0]].lat
lon_st = st_info[ev_info[key_ev].t[key_t][0]].lon
dis = cal_dis(lat_ev,lon_ev,lat_st,lon_st)**2
all_dis.append(dis)
return [np.array(all_dis),np.array(all_time)]
# %%
# function: output the [cs_dt] of all data
def data_cs_dt(ev_info):
all_time = []
for key_ev in ev_info:
for key_dt in ev_info[key_ev].cs_dt:
all_time.append(ev_info[key_ev].cs_dt[key_dt][3])
return np.array(all_time)
# %%
# Function: data_dis_time_phase(ev_info, st_info, phase_list) Given a list of seismic phases, output the [epicentral distance, arrival time] for each phase.
def data_dis_time_phase(ev_info,st_info,phase_list):
all_dis = {}
all_time = {}
for phase in phase_list:
all_dis[phase] = []
all_time[phase] = []
for key_ev in ev_info:
lat_ev = ev_info[key_ev].lat
lon_ev = ev_info[key_ev].lon
dep_ev = ev_info[key_ev].dep
for key_t in ev_info[key_ev].t:
phase = key_t.split("+")[1]
if (not phase in phase_list):
continue
all_time[phase].append(ev_info[key_ev].t[key_t][2])
lat_st = st_info[ev_info[key_ev].t[key_t][0]].lat
lon_st = st_info[ev_info[key_ev].t[key_t][0]].lon
ele_st = st_info[ev_info[key_ev].t[key_t][0]].ele
dis = math.sqrt(cal_dis(lat_ev,lon_ev,lat_st,lon_st)**2 + (dep_ev+ele_st/1000)**2)
all_dis[phase].append(dis)
for phase in phase_list:
all_dis[phase] = np.array(all_dis[phase])
all_time[phase] = np.array(all_time[phase])
return [all_dis,all_time]
# %%
# Function: data_lon_lat_dep_wt_ev(ev_info) Outputs the lines connecting station and earthquake for traveltime data as [line_x, line_y].
def data_line(ev_info,st_info):
line_x = []
line_y = []
for key_ev in ev_info:
lat_ev = ev_info[key_ev].lat
lon_ev = ev_info[key_ev].lon
for key_t in ev_info[key_ev].t:
lat_st = st_info[ev_info[key_ev].t[key_t][0]].lat
lon_st = st_info[ev_info[key_ev].t[key_t][0]].lon
line_x.append([lon_ev,lon_st])
line_y.append([lat_ev,lat_st])
return [line_x,line_y]
# %% [markdown]
# Functions: discard some data in ev_info and st_info based on selection criteria
# %%
# Function: limit_ev_region(ev_info, lat1, lat2, lon1, lon2, dep1, dep2) Delete the earthquakes that are out of the specified region.
def limit_ev_region(ev_info,lat_min,lat_max,lon_min,lon_max,dep_min,dep_max):
count_delete = 0
del_key_ev = []
for key_ev in ev_info:
ev = ev_info[key_ev]
lat = ev.lat
lon = ev.lon
dep = ev.dep
if (lat < min(lat_min,lat_max) or lat > max(lat_min,lat_max) \
or lon < min(lon_min,lon_max) or lon > max(lon_min,lon_max) \
or dep < min(dep_min,dep_max) or dep > max(dep_min,dep_max)):
del_key_ev.append(key_ev)
count_delete += 1
del_key_t = []
for key_t in ev_info[key_ev].cr_dt:
name_ev2 = ev_info[key_ev].cr_dt[key_t][1]
lat2 = ev_info[name_ev2].lat
lon2 = ev_info[name_ev2].lon
dep2 = ev_info[name_ev2].dep
if (lat2 < min(lat_min,lat_max) or lat2 > max(lat_min,lat_max) \
or lon2 < min(lon_min,lon_max) or lon2 > max(lon_min,lon_max) \
or dep2 < min(dep_min,dep_max) or dep2 > max(dep_min,dep_max)):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev_info[key_ev].cr_dt[key_t]
ev_info[key_ev].Ncr_dt = len(ev_info[key_ev].cr_dt)
for key_ev in del_key_ev:
del ev_info[key_ev]
print("delete %d events out of the region, now %d earthquakes are retained within the study region"%(count_delete,len(ev_info)))
return ev_info
# %%
# Function: limit_st_region(ev_info, st_info, lat1, lat2, lon1, lon2) Delete the stations that are out of the specified region.
def limit_st_region(ev_info,st_info,lat1,lat2,lon1,lon2):
for key_ev in ev_info:
# delete the station out of the region in the absolute traveltime data
del_key_t = []
for key_t in ev_info[key_ev].t:
name_st = ev_info[key_ev].t[key_t][0]
lat_st = st_info[name_st].lat
lon_st = st_info[name_st].lon
if(lat_st < min(lat1,lat2) or lat_st > max(lat1,lat2) or lon_st < min(lon1,lon2) or lon_st > max(lon1,lon2)):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev_info[key_ev].t[key_t]
ev_info[key_ev].Nt = len(ev_info[key_ev].t)
# delete the station out of the region in the common source differential traveltime data
del_key_t = []
for key_t in ev_info[key_ev].cs_dt:
name_st1 = ev_info[key_ev].cs_dt[key_t][0]
lat_st1 = st_info[name_st1].lat
lon_st1 = st_info[name_st1].lon
name_st2 = ev_info[key_ev].cs_dt[key_t][1]
lat_st2 = st_info[name_st2].lat
lon_st2 = st_info[name_st2].lon
if(lat_st1 < min(lat1,lat2) or lat_st1 > max(lat1,lat2) or lon_st1 < min(lon1,lon2) or lon_st1 > max(lon1,lon2) \
or lat_st2 < min(lat1,lat2) or lat_st2 > max(lat1,lat2) or lon_st2 < min(lon1,lon2) or lon_st2 > max(lon1,lon2)):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev_info[key_ev].cs_dt[key_t]
ev_info[key_ev].Ncs_dt = len(ev_info[key_ev].cs_dt)
# delete the station out of the region in the common receiver differential traveltime data
del_key_st = []
for key_t in ev_info[key_ev].cr_dt:
name_st = ev_info[key_ev].cr_dt[key_t][0]
lat_st = st_info[name_st].lat
lon_st = st_info[name_st].lon
if(lat_st < min(lat1,lat2) or lat_st > max(lat1,lat2) or lon_st < min(lon1,lon2) or lon_st > max(lon1,lon2)):
del_key_st.append(key_t)
for key_t in del_key_st:
del ev_info[key_ev].cr_dt[key_t]
ev_info[key_ev].Ncr_dt = len(ev_info[key_ev].cr_dt)
return ev_info
# %%
# Function: limit_epi_dis(ev_info, st_info, epi_dis1, epi_dis2) Delete the stations with epicentral distance in the range from epi_dis1 to epi_dis2.
def limit_epi_dis(ev_info,st_info,epi_dis1,epi_dis2):
for key_ev in ev_info:
ev = ev_info[key_ev]
lat_ev = ev.lat
lon_ev = ev.lon
# delete the absolute traveltime data
del_key_t = []
for key_t in ev.t:
stname = ev.t[key_t][0]
lat_st = st_info[stname].lat
lon_st = st_info[stname].lon
dis = cal_dis(lat_ev, lon_ev, lat_st, lon_st)
if (dis > epi_dis1 and dis < epi_dis2):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev.t[key_t]
ev.Nt = len(ev.t)
# delete the common source differential traveltime data
del_key_t = []
for key_t in ev.cs_dt:
for i in range(2):
stname = ev.t[key_t][i]
lat_st = st_info[stname].lat
lon_st = st_info[stname].lon
dis = cal_dis(lat_ev, lon_ev, lat_st, lon_st)
if (dis > epi_dis1 and dis < epi_dis2):
del_key_t.append(key_t)
break
for key_t in del_key_t:
del ev.cs_dt[key_t]
ev.Ncs_dt = len(ev.cs_dt)
# delete the common receiver differential traveltime data
del_key_t = []
for key_t in ev.cr_dt:
stname = ev.cr_dt[key_t][0]
lat_st = st_info[stname].lat
lon_st = st_info[stname].lon
dis = cal_dis(lat_ev, lon_ev, lat_st, lon_st)
if (dis > epi_dis1 and dis < epi_dis2):
del_key_t.append(key_t)
lat_ev2 = ev_info[ev.cr_dt[key_t][1]].lat
lon_ev2 = ev_info[ev.cr_dt[key_t][1]].lon
dis = cal_dis(lat_ev2, lon_ev2, lat_st, lon_st)
if (dis > epi_dis1 and dis < epi_dis2):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev.cr_dt[key_t]
ev.Ncr_dt = len(ev.cr_dt)
ev_info[key_ev] = ev
return ev_info
# %%
# Function: limit_data_residual(ev_info, st_info, slope, intercept, up, down) Limit the data within the range defined by the line time = dis * slope + intercept and the bounds up and down.
# remove outliers, only retain data satisfying: slope * dis + intercept + down < time < slope * dis + intercept + up
def limit_data_residual(ev_info,st_info,slope,intercept,up,down):
for key_ev in ev_info:
lat_ev = ev_info[key_ev].lat
lon_ev = ev_info[key_ev].lon
dep_ev = ev_info[key_ev].dep
del_key_t = []
for key_t in ev_info[key_ev].t:
name_st = ev_info[key_ev].t[key_t][0]
lat_st = st_info[name_st].lat
lon_st = st_info[name_st].lon
ele_st = st_info[name_st].ele
dis = math.sqrt(cal_dis(lat_ev,lon_ev,lat_st,lon_st)**2 + (dep_ev+ele_st/1000)**2)
residual = ev_info[key_ev].t[key_t][2] - (slope*dis+intercept)
if (residual < down or residual > up):
del_key_t.append(key_t)
for key_t in del_key_t:
del ev_info[key_ev].t[key_t]
for key_ev in ev_info:
ev_info[key_ev].Nt = len(ev_info[key_ev].t)
return ev_info
# %%
# Function: limit_data_phase(ev_info, phase_list) Retain only the specified seismic phases.
def limit_data_phase(ev_info,phase_list):
for key_ev in ev_info:
# process the absolute traveltime data
new_t = {}
for key_t in ev_info[key_ev].t:
phase = ev_info[key_ev].t[key_t][1]
if phase in phase_list:
new_t[key_t] = ev_info[key_ev].t[key_t]
ev_info[key_ev].t = new_t
ev_info[key_ev].Nt = len(ev_info[key_ev].t)
# process the common source differential traveltime data
new_t = {}
for key_t in ev_info[key_ev].cs_dt:
phase = ev_info[key_ev].cs_dt[key_t][2]
phase = phase.split(",")[0]
if phase in phase_list:
new_t[key_t] = ev_info[key_ev].cs_dt[key_t]
ev_info[key_ev].cs_dt = new_t
ev_info[key_ev].Ncs_dt = len(ev_info[key_ev].cs_dt)
# process the common receiver differential traveltime data
new_t = {}
for key_t in ev_info[key_ev].cr_dt:
phase = ev_info[key_ev].cr_dt[key_t][2]
phase = phase.split(",")[0]
if phase in phase_list:
new_t[key_t] = ev_info[key_ev].cr_dt[key_t]
ev_info[key_ev].cr_dt = new_t
ev_info[key_ev].Ncr_dt = len(ev_info[key_ev].cr_dt)
return ev_info
# %%
# Function: limit_min_Nt(min_Nt_thd, ev_info) Delete the earthquakes with the number of data less than min_Nt_thd.
def limit_min_Nt(min_Nt_thd, ev_info):
Nev = len(ev_info)
del_key_ev = []
for key_ev in ev_info:
if(ev_info[key_ev].Nt < min_Nt_thd):
del_key_ev.append(key_ev)
for key_ev in del_key_ev:
del ev_info[key_ev]
print("Original data set has %d earthquakes, %d earthquakes are deleted, %d earthquakes are retained"%(Nev,len(del_key_ev),len(ev_info)))
return ev_info
# %%
# Function: limit_azi_gap(gap_thd) Calculate the azimuthal gap for all events and delete events with a gap greater than gap_thd.
def limit_azi_gap(gap_thd,ev_info,st_info):
Nev = len(ev_info)
del_key_ev = []
for key_ev in ev_info:
ev = ev_info[key_ev]
gap = cal_azi_gap(ev,st_info)
if (gap > gap_thd):
del_key_ev.append(key_ev)
else:
ev_info[key_ev].tag["azi_gap"] = gap
for key_ev in del_key_ev:
del ev_info[key_ev]
print("Original data set has %d earthquakes, %d earthquakes are deleted, %d earthquakes are retained"%(Nev,len(del_key_ev),len(ev_info)))
return ev_info
# Function: cal_azi_gap(ev, st_info) Calculate the azimuthal gap of a single earthquake.
def cal_azi_gap(ev,st_info):
azi_all = []
lat_ev = ev.lat
lon_ev = ev.lon
stlist = {}
for key in ev.t:
stname = ev.t[key][0]
if (not stname in stlist):
lat_st = st_info[stname].lat
lon_st = st_info[stname].lon
azi = cal_azimuth(lat_ev, lon_ev, lat_st, lon_st)
azi_all.append(azi)
stlist[stname] = 1
azi_all.sort()
if(len(azi_all) < 2):
return 360.0
else:
gap = 0.0
for i in range(len(azi_all)-1):
gap = max(gap,azi_all[i+1] - azi_all[i])
gap = max(gap,azi_all[0] + 360 - azi_all[-1])
return gap
# %%
# Function: limit_earthquake_decluster_Nt(ev_info, dlat, dlon, ddep, Top_N) Divide the region into several subdomains, sort by the number of arrival times, and retain only the top Top_N earthquakes with the most arrival times in each box.
# option 3, declustering. Divide the region into several subdomains, retain the Top N earthquakes in terms of the number of arrival times in each subdomain.
def limit_earthquake_decluster_Nt(ev_info,dlat,dlon,ddep,Top_N):
# subdivide earthquakes into different subdomains
[ev_info,tag2name] = tag_event_cluster(dlat,dlon,ddep,ev_info)
# sort earthquakes in the same subdomain
# Sort the quality of earthquakes within each tag based on the number of arrivals.
tag2name = sort_cluster_Nt(ev_info, tag2name)
# only retain Top_N earthquakes in each subdomain
# Within each tag, prioritize selecting the top Top_N earthquakes.
[ev_info,tag2name] = limit_decluster(ev_info, tag2name,Top_N)
return ev_info
# Function: tag_event_cluster(size_lat, size_lon, size_dep, ev_info) Subdivide the study area, assign each earthquake to a subregion, and place it in a tag.
def tag_event_cluster(size_lat,size_lon,size_dep,ev_info):
tag2name = {}
for key_ev in ev_info:
name = ev_info[key_ev].name
lat = ev_info[key_ev].lat
lon = ev_info[key_ev].lon
dep = ev_info[key_ev].dep
tag = "%d_%d_%d"%(math.floor(lon/size_lon),math.floor(lat/size_lat),math.floor(dep/size_dep))
ev_info[key_ev].tag["cluster"] = tag
if (tag in tag2name):
tag2name[tag].append(name)
else:
tag2name[tag] = []
tag2name[tag].append(name)
return [ev_info,tag2name]
# Function: sort_cluster_Nt(ev_info, tag2name) Sort the quality of earthquakes within each tag based on the number of arrivals.
def sort_cluster_Nt(ev_info, tag2name):
for key_tag in tag2name:
names_ev = tag2name[key_tag]
Nt = []
for key_ev in names_ev:
Nt.append(len(ev_info[key_ev].t))
# Sort the earthquakes within each tag based on the number of arrivals.
sorted_Nt = sorted(enumerate(Nt), key=lambda x: x[1], reverse=True)
tag2name[key_tag] = []
for index, Nt in sorted_Nt:
tag2name[key_tag].append(names_ev[index])
return tag2name
# Function: limit_cluster(ev_info, tag2name, Max) Prioritize selecting the top Max earthquakes within each tag.
def limit_decluster(ev_info, tag2name, Max):
del_key_ev = []
for key_tag in tag2name:
names_ev = tag2name[key_tag]
if(len(names_ev) > Max):
tag2name[key_tag] = names_ev[0:Max]
for i in range(Max,len(names_ev)): # Delete earthquakes that exceed the threshold in the sorted list.
del_key_ev.append(names_ev[i])
for key_ev in del_key_ev:
del ev_info[key_ev]
return [ev_info,tag2name]
# %% [markdown]
# Functions: assign weights to earthquakes, stations, and data
# %%
# Function: box_weighting_ev(ev_info, dlat, dlon, ddep) Assign box-weight to the earthquakes.
def box_weighting_ev(ev_info,dlon,dlat,ddep):
# categorization
distribute = {}
all_tag_wt = {}
for key_ev in ev_info:
lat_id = math.floor((ev_info[key_ev].lat) / dlat)
lon_id = math.floor((ev_info[key_ev].lon) / dlon)
dep_id = math.floor((ev_info[key_ev].dep) / ddep)
tag = '%d_%d_%d'%(lat_id,lon_id,dep_id)
if (tag in distribute):
distribute[tag] += 1
else:
distribute[tag] = 1
max_weight = 0
for tag in distribute:
all_tag_wt[tag] = 1.0/math.sqrt(distribute[tag])
max_weight = max(max_weight,all_tag_wt[tag])
for key_ev in ev_info:
lat_id = math.floor((ev_info[key_ev].lat) / dlat)
lon_id = math.floor((ev_info[key_ev].lon) / dlon)
dep_id = math.floor((ev_info[key_ev].dep) / ddep)
tag = '%d_%d_%d'%(lat_id,lon_id,dep_id)
ev_info[key_ev].tag["weight"] = all_tag_wt[tag]/max_weight
return ev_info
# %%
# Function: geographical_weighting_ev_rough(ev_info, dlat, dlon, ddep) Assign geographical weighting to the earthquakes roughly.
def geographical_weighting_ev_rough(ev_info,dlat,dlon,ddep,coefficient = 0.5):
# categorization
distribute = {}
all_tag_wt = {}
for key_ev in ev_info:
lat_id = int(ev_info[key_ev].lat/dlat)
lon_id = int(ev_info[key_ev].lon/dlat)
dep_id = int(ev_info[key_ev].dep/ddep)
tag = '%d_%d_%d'%(lat_id,lon_id,dep_id)
if (tag in distribute):
distribute[tag] += 1
else:
distribute[tag] = 1
# Calculate the weight of each category.
delta0 = 0
for tag1 in distribute:
tmp1 = tag1.split('_')
# evlat1 = float(tmp1[0])*dlat; evlon1 = float(tmp1[1])*dlon; evdep1 = float(tmp1[2])*ddep
for tag2 in distribute:
tmp2 = tag2.split('_')
# evlat2 = float(tmp2[0])*dlat; evlon2 = float(tmp2[1])*dlon; evdep2 = float(tmp2[2])*ddep
# distance of id
delta_tp = math.sqrt((int(tmp1[0]) - int(tmp2[0]))**2 + (int(tmp1[1]) - int(tmp2[1]))**2 + (int(tmp1[2]) - int(tmp2[2]))**2)
delta0 = delta0 + distribute[tag1] * distribute[tag2] * delta_tp
delta0 = delta0/(len(ev_info)**2) * coefficient
max_weight = 0.0
for tag1 in distribute:
tmp1 = tag1.split('_')
# evlat1 = float(tmp1[0])*dlat; evlon1 = float(tmp1[1])*dlon; evdep1 = float(tmp1[2])*ddep
weight = 0
for tag2 in distribute:
tmp2 = tag2.split('_')
# evlat2 = float(tmp2[0])*dlat; evlon2 = float(tmp2[1])*dlon; evdep2 = float(tmp2[2])*ddep
delta_tp = math.sqrt((int(tmp1[0]) - int(tmp2[0]))**2 + (int(tmp1[1]) - int(tmp2[1]))**2 + (int(tmp1[2]) - int(tmp2[2]))**2)
weight = weight + math.exp(-(delta_tp/delta0)**2) * distribute[tag2]
all_tag_wt[tag1] = (1.0/weight)
max_weight = max(max_weight,1.0/weight)
# Assign weights to each earthquake based on its tag.
for key_ev in ev_info:
lat_id = int(ev_info[key_ev].lat/dlat)
lon_id = int(ev_info[key_ev].lon/dlon)
dep_id = int(ev_info[key_ev].dep/ddep)
tag = '%d_%d_%d'%(lat_id,lon_id,dep_id)
ev_info[key_ev].tag["weight"] = all_tag_wt[tag]/max_weight
return ev_info
# %%
# Function: box_weighting_st(ev_info, st_info, dlat, dlon) Assign geographical weighting to the stations roughly.
def box_weighting_st(ev_info,st_info,dlon,dlat):
[lon_ev,lat_ev,dep_ev,wt_ev] = data_lon_lat_dep_wt_ev(ev_info)
# Integrate all involved stations.
wt_st = {}
name_st = {}
for key_ev in ev_info:
for key_t in ev_info[key_ev].t:
name_rec = ev_info[key_ev].t[key_t][0]
wt_st[name_rec] = -1.0
name_st[name_rec] = 1
# categorization
distribute = {}
all_tag_wt = {}
# Count the number of stations in each subdomain.
for key_st in name_st:
lat_id = math.floor((st_info[key_st].lat) / dlat)
lon_id = math.floor((st_info[key_st].lon) / dlon)
tag = '%d_%d'%(lat_id,lon_id)
if (tag in distribute):
distribute[tag] += 1
else:
distribute[tag] = 1
max_weight = 0
for tag in distribute:
all_tag_wt[tag] = 1.0/math.sqrt(distribute[tag])
max_weight = max(max_weight,all_tag_wt[tag])
# Assign weights to each station based on its tag.
for key_st in name_st:
lat_id = math.floor((st_info[key_st].lat) / dlat)
lon_id = math.floor((st_info[key_st].lon) / dlon)
tag = '%d_%d'%(lat_id,lon_id)
wt_st[key_st] = all_tag_wt[tag]/max_weight
# modify weight tag in st_info
for key_t in wt_st:
st_info[key_t].tag["weight"] = wt_st[key_t]
# modify weight of abs data ev_info
for key_ev in ev_info:
for key_t in ev_info[key_ev].t:
name_rec = ev_info[key_ev].t[key_t][0]
ev_info[key_ev].t[key_t][3] = wt_st[name_rec]
return [ev_info,st_info]
# %%
# Function: geographical_weighting_st(ev_info,st_info) Assign geographical weighting to the stations roughly.
def geographical_weighting_st(ev_info,st_info,coefficient = 0.5):
# Integrate all involved stations.
wt_st = {}
name_st = {}
for key_ev in ev_info:
for key_t in ev_info[key_ev].t:
name_rec = ev_info[key_ev].t[key_t][0]
wt_st[name_rec] = -1.0
name_st[name_rec] = 1
# Calculate the weight of each station.
delta0 = 0
for key_st1 in name_st:
stlat1 = st_info[key_st1].lat
stlon1 = st_info[key_st1].lon
for key_st2 in name_st:
stlat2 = st_info[key_st2].lat
stlon2 = st_info[key_st2].lon
delta_tp = cal_dis(stlat1,stlon1,stlat2,stlon2)
delta0 = delta0 + delta_tp
delta0 = delta0/(len(wt_st)**2)*coefficient
max_weight = 0.0
for key_st1 in name_st:
stlat1 = st_info[key_st1].lat
stlon1 = st_info[key_st1].lon
weight = 0
for key_st2 in name_st:
stlat2 = st_info[key_st2].lat
stlon2 = st_info[key_st2].lon
delta_tp = cal_dis(stlat1,stlon1,stlat2,stlon2)
weight = weight + math.exp(-(delta_tp/delta0)**2)
wt_st[key_st1] = (1.0/weight)
max_weight = max(max_weight,1.0/weight)
for key_st1 in wt_st:
wt_st[key_st1] = wt_st[key_st1]/max_weight
# Add weight to each data point in the earthquakes.
for key_ev in ev_info:
for key_t in ev_info[key_ev].t:
name_rec = ev_info[key_ev].t[key_t][0]
if (not name_rec in wt_st):
ValueError("The station of the data is not in the calculation list")
if (len(ev_info[key_ev].t[key_t])==3):
ev_info[key_ev].t[key_t].append(wt_st[name_rec])
elif (len(ev_info[key_ev].t[key_t])==4):
ev_info[key_ev].t[key_t][3] = wt_st[name_rec]
else:
ValueError("Error in the weight information of the absolute traveltime data")
# modify weight tag in st_info
for key_t in wt_st:
st_info[key_t].tag["weight"] = wt_st[key_t]
# modify weight of abs data ev_info
for key_ev in ev_info:
for key_t in ev_info[key_ev].t:
name_rec = ev_info[key_ev].t[key_t][0]
ev_info[key_ev].t[key_t][3] = wt_st[name_rec]
return [ev_info,st_info]
# %% [markdown]
# Function: add noise into data
# %%
# Functionassign_gaussian_noise():
def assign_gaussian_noise(ev_info,sigma):
# Record which seismic phases correspond to each station.
st2phase = {} # Station name -> [Keys of absolute arrival time data related to this station]
for key_ev in ev_info:
# Absolute arrival time noise
for key_t in ev_info[key_ev].t:
stname = ev_info[key_ev].t[key_t][0]
ev_info[key_ev].t[key_t][2] = ev_info[key_ev].t[key_t][2] + np.random.normal(0,sigma)
if(stname in st2phase):
st2phase[stname].append(key_t)
else:
st2phase[stname] = [key_t]
for key_ev in ev_info:
# Double-difference arrival time noise
for key_dt in ev_info[key_ev].cs_dt:
stname1 = ev_info[key_ev].cs_dt[key_dt][0]
stname2 = ev_info[key_ev].cs_dt[key_dt][1]
t1 = -999
t2 = -999
# Search for the arrival time of the data.
if (stname1 in st2phase):
for key_t in st2phase[stname1]:
if (key_t in ev_info[key_ev].t):
t1 = ev_info[key_ev].t[key_t][2]
break
if (stname2 in st2phase):
for key_t in st2phase[stname2]:
if (key_t in ev_info[key_ev].t):
t2 = ev_info[key_ev].t[key_t][2]
break
if (t1 == -999 or t2 == -999):
# If there is no absolute arrival time data, the double-difference data residuals increase by a factor of sqrt(2) in noise.
ev_info[key_ev].cs_dt[key_dt][3] = ev_info[key_ev].cs_dt[key_dt][3] + np.random.normal(0,sigma*np.sqrt(2))
print('no data: ', key_ev, key_dt)
else:
# If there is absolute arrival time data, the double-difference data is obtained by subtraction.
ev_info[key_ev].cs_dt[key_dt][3] = t1 - t2
# Common station double-difference arrival time
for key_dt in ev_info[key_ev].cr_dt:
stname = ev_info[key_ev].cr_dt[key_dt][0]
key_ev2 = ev_info[key_ev].cr_dt[key_dt][1]
t1 = -999
t2 = -999
# Search for the arrival time of the data.
if (stname in st2phase):
for key_t in st2phase[stname]:
if (key_t in ev_info[key_ev].t):
t1 = ev_info[key_ev].t[key_t][2]
break
else:
print('not found 1: ', key_ev, key_t)
for key_t in st2phase[stname]:
if (key_t in ev_info[key_ev2].t):
t2 = ev_info[key_ev2].t[key_t][2]
break
else:
print('not found 2: ', key_ev, key_t)
if (t1 == -999 or t2 == -999):
# If there is no absolute arrival time data, the double-difference data residuals increase by a factor of sqrt(2) in noise.
ev_info[key_ev].cr_dt[key_dt][3] = ev_info[key_ev].cr_dt[key_dt][3] + np.random.normal(0,sigma*np.sqrt(2))
print('no data: ', key_ev, key_dt)
else:
# If there is absolute arrival time data, the double-difference data is obtained by subtraction.
ev_info[key_ev].cr_dt[key_dt][3] = t1 - t2
return ev_info
# %%
# Functionassign_uniform_noise_to_ev():
def assign_uniform_noise_to_ev(ev_info, range_lat, range_lon, range_dep, range_time):
# Loop through all earthquakes and assign noise to them.
ev_noise = {} # Name of the earthquake -> noise of [lat,lon,dep,ortime]
# loop list of earthquakes
for key_ev in ev_info:
evname = key_ev
if (evname in ev_noise):
print("error: repeated earthquake name")
exit()
else:
# generate noise
ev_noise[evname] = np.random.uniform(-1,1,4) * np.array([range_lat,range_lon,range_dep,range_time])
# Add noise to each data point.
for key_ev in ev_info:
# Absolute arrival time noise
for key_t in ev_info[key_ev].t:
ev_info[key_ev].t[key_t][2] = ev_info[key_ev].t[key_t][2] - ev_noise[key_ev][3]
# Double-difference arrival time noise (double-difference arrival time remains unchanged)
# Common station double-difference arrival time
for key_dt in ev_info[key_ev].cr_dt:
key_ev2 = ev_info[key_ev].cr_dt[key_dt][1]
if (key_ev2 in ev_noise):
ev_info[key_ev].cr_dt[key_dt][3] = ev_info[key_ev].cr_dt[key_dt][3] - ev_noise[key_ev][3] + ev_noise[key_ev2][3]
else:
print("earthquake %s is not included in ev_list"%(key_ev2))
ev_noise[key_ev2] = np.random.uniform(-1,1,4) * np.array([range_lat,range_lon,range_dep,range_time])
ev_info[key_ev].cr_dt[key_dt][3] = ev_info[key_ev].cr_dt[key_dt][3] - ev_noise[key_ev][3] + ev_noise[key_ev2][3]
# Add noise to each earthquake.
for key_ev in ev_noise:
ev_info[key_ev].lat = ev_info[key_ev].lat + ev_noise[key_ev][0]
ev_info[key_ev].lon = ev_info[key_ev].lon + ev_noise[key_ev][1]
ev_info[key_ev].dep = abs(ev_info[key_ev].dep + ev_noise[key_ev][2])
ev_info[key_ev].ortime = ev_info[key_ev].ortime + ev_noise[key_ev][3]
return ev_info
# %% [markdown]
# Functions: generate differential traveltime
# %%
# Function: generate_cs_dif(ev_info, st_info, dis_thd, azi_thd) Generate double-difference arrival times from absolute arrival times, with inter-station distance less than dis_thd and azimuthal difference less than azi_thd.
# function: generate common source differential traveltime data from absolute traveltime data, the stations separation is less than dis_thd, the azimuth difference is less than azi_thd
def generate_cs_dif(ev_info,st_info,dis_thd,azi_thd):
count_t = 0
count_cs_dt = 0
for key_ev in ev_info:
ev = ev_info[key_ev]
lat_ev = ev.lat
lon_ev = ev.lon
# traverse all arrival times
name_st_list = [] # names of stations
t_list = [] # traveltime
wt_list = [] # weight
for key_t in ev.t:
name_st_list.append(ev.t[key_t][0])
t_list.append(ev.t[key_t][2])
wt_list.append(ev.t[key_t][3])
count_t += 1
# search for possible double-difference arrival times
for id_st1 in range(len(name_st_list)-1):
name_st1 = name_st_list[id_st1]
lat_st1 = st_info[name_st1].lat
lon_st1 = st_info[name_st1].lon
t_st1 = t_list[id_st1]
wt_st1 = wt_list[id_st1]
for id_st2 in range(id_st1+1,len(name_st_list)):
name_st2 = name_st_list[id_st2]
lat_st2 = st_info[name_st2].lat
lon_st2 = st_info[name_st2].lon
t_st2 = t_list[id_st2]
wt_st2 = wt_list[id_st2]
dis = cal_dis(lat_st1,lon_st1,lat_st2,lon_st2)
azi_st1 = cal_azimuth(lat_ev,lon_ev,lat_st1,lon_st1)
azi_st2 = cal_azimuth(lat_ev,lon_ev,lat_st2,lon_st2)
azi_dif = abs(azi_st1 - azi_st2)
if(dis < dis_thd and (azi_dif < azi_thd or (360-azi_dif) < azi_thd )):
ev.cs_dt["%s+%s+%s"%(name_st1,name_st2,"P,cs")] = [name_st1,name_st2,"P,cs",t_st1-t_st2,(wt_st1+wt_st2)/2]
count_cs_dt += 1
ev_info[key_ev].Ncs_dt = len(ev.cs_dt)
print('we generate %d common source differential traveltimes from %s absolute traveltimes'%(count_cs_dt,count_t))
return ev_info
# %%
# Function: generate_cr_dif(ev_info, st_info, dis_thd) Generate common station double-difference arrival times from absolute arrival times, with inter-event distance less than dis_thd.
# Function: generate common receiver differential traveltime data from absolute traveltime data, the earthquake separation is less than dis_thd
def generate_cr_dif(ev_info,st_info,dis_thd,azi_thd):
# Construct mappingrec2src[name_ev] -> {name_st: [name_ev, name_st, t, wt]; name_st: [name_ev, name_st, t, wt]; ...}
rec2src = build_rec_src_map(ev_info,dis_thd)
print("rec to src map generation finished")
# Construct double-difference data association mappingrec2src_pair[key_t]
rec2src_pair = build_rec_src_pair_map(rec2src)
print("rec to src_pair map generation finished")
for key_t in rec2src_pair:
name_st = key_t.split('+')[0]
lat_st = st_info[name_st].lat
lon_st = st_info[name_st].lon
for ev_tag in rec2src_pair[key_t]:
name_ev1 = rec2src_pair[key_t][ev_tag][0]
lat_ev1 = ev_info[name_ev1].lat
lon_ev1 = ev_info[name_ev1].lon
dep_ev1 = ev_info[name_ev1].dep
name_ev2 = rec2src_pair[key_t][ev_tag][1]
lat_ev2 = ev_info[name_ev2].lat
lon_ev2 = ev_info[name_ev2].lon
dep_ev2 = ev_info[name_ev2].dep
dis_xy = cal_dis(lat_ev1,lon_ev1,lat_ev2,lon_ev2)
dis_z = abs(dep_ev1 - dep_ev2)
dis = math.sqrt(dis_xy**2 + dis_z**2)
if(dis > dis_thd): # limit of the distance between two earthquakes
continue
azi1 = cal_azimuth(lat_ev1,lon_ev1,lat_st,lon_st)
azi2 = cal_azimuth(lat_ev2,lon_ev2,lat_st,lon_st)
azi_dif = abs(azi1 - azi2)
if(azi_dif > azi_thd and (360-azi_dif) > azi_thd): # limit of the azimuth difference between two earthquakes
continue
t_ev1 = ev_info[name_ev1].t[key_t][2]
t_ev2 = ev_info[name_ev2].t[key_t][2]
wt_ev1 = ev_info[name_ev1].t[key_t][3] * ev_info[name_ev1].tag["weight"]
wt_ev2 = ev_info[name_ev2].t[key_t][3] * ev_info[name_ev2].tag["weight"]
# The actual data weight is wt_ev1 + wt_ev2, but in TomoATT calculations, we need to divide it by ev_info[name_ev1].tag["weight"].
wt = (wt_ev1 + wt_ev2)/2/ev_info[name_ev1].tag["weight"]
ev_info[name_ev1].cr_dt["%s+%s+%s"%(name_st,name_ev2,"P,cr")] = [name_st,name_ev2,"P,cr",t_ev1-t_ev2,wt]
# Count the number of double-difference data points.
count_cr_dt = 0
count_t = 0
for key_ev in ev_info:
ev_info[key_ev].Ncr_dt = len(ev_info[key_ev].cr_dt)
count_cr_dt += ev_info[key_ev].Ncr_dt
count_t += ev_info[key_ev].Nt
print('we generate %d common receiver differential traveltimes from %s absolute traveltimes'%(count_cr_dt,count_t))
return ev_info
# Construct mapping: rec2src = {key_t: dict_tag; key_t: dict_tag; ...}
# dict_tag = {tag: list_name_ev; tag: list_name_ev; ...}
# list_name_ev = [name_ev1, name_ev2, ...]
# Assign earthquakes to different subregions based on their locations. The subregion size is dlat * dlon * ddep. When performing common station double-difference calculations, only earthquake pairs within the same subregion or adjacent subregions will be considered.
def build_rec_src_map(ev_info,dis_thd):
rec2src = {}
for key_ev in ev_info:
name_ev = ev_info[key_ev].name
lat = ev_info[key_ev].lat
lon = ev_info[key_ev].lon
dep = ev_info[key_ev].dep
tag_dep = math.floor(dep/dis_thd)
tag_lat = math.floor(lat/180*math.pi*R_earth/dis_thd)
tag_lon = math.floor(lon/180*math.pi*R_earth*math.cos(lat)/dis_thd)
tag = "%d_%d_%d"%(tag_lon,tag_lat,tag_dep)
for key_t in ev_info[key_ev].t:
# create dictionary
if (not key_t in rec2src):
rec2src[key_t] = {tag:[]}
elif (not tag in rec2src[key_t]):
rec2src[key_t][tag] = []
# Add data
rec2src[key_t][tag].append(name_ev)
return rec2src
# Function: generate_adjacent_tag(tag) Generate tags surrounding the given tag.
def generate_adjacent_tag(tag): # Excluding the tag itself.
adjacent_tag_list = []
tmp = tag.split('_')
tag_lon = int(tmp[0])
tag_lat = int(tmp[1])
tag_dep = int(tmp[2])
for i in range(-1,2):
for j in range(-1,2):
for k in range(-1,2):
if(i == 0 and j == 0 and k == 0):
continue
adjacent_tag_list.append("%d_%d_%d"%(tag_lon+i,tag_lat+j,tag_dep+k))
return adjacent_tag_list
# construct mappingrec2src_pair
def build_rec_src_pair_map(rec2src):
rec2src_pair = {}
for key_t in rec2src:
rec2src_pair[key_t] = {}
for tag in rec2src[key_t]:
name_ev_list1 = rec2src[key_t][tag]
name_ev_list2 = rec2src[key_t][tag]
adjacent_tag_list = generate_adjacent_tag(tag)
for adjacent_tag in adjacent_tag_list:
if (adjacent_tag in rec2src[key_t]): # If the surrounding tag's region has earthquakes, add them to the earthquake list.
name_ev_list2 = name_ev_list2 + rec2src[key_t][adjacent_tag]
# Find possible earthquake pairs.
for id_ev1 in range(len(name_ev_list1)-1):
name_ev1 = name_ev_list1[id_ev1]
for id_ev2 in range(id_ev1+1,len(name_ev_list2)): # Starting from id_ev1 + 1 already excludes duplicate earthquakes within the tag.
name_ev2 = name_ev_list2[id_ev2]
ev_tag1 = "%s+%s"%(name_ev1,name_ev2)
ev_tag2 = "%s+%s"%(name_ev2,name_ev1)
if(ev_tag1 in rec2src_pair[key_t] or ev_tag2 in rec2src_pair[key_t]):
continue
rec2src_pair[key_t][ev_tag1] = [name_ev1,name_ev2]
return rec2src_pair
# %% [markdown]
# Functions: read and write src_rec.dat file
# %%
# Function: reorder_src(ev) Reorder the earthquake IDs. If the earthquake has no data, the ID is -999.
def reorder_src(ev_info):
ev_id = 0
for key_ev in ev_info:
ev = ev_info[key_ev]
if(ev.Nt + ev.Ncs_dt + ev.Ncr_dt == 0):
ev.id = -999
else:
ev_info[key_ev].id = ev_id
ev_id += 1
return ev_info
# %%
# Function: read_src_rec_file(fname) Read the src_rec.dat file.
#
def read_src_rec_file(fname):
ev_info = {}
st_info = {}
tmp_ev_info = {}
doc = open(fname,'r')
doc_input = doc.readlines()
doc.close()
cc = 0
for info in doc_input:
tmp=info.split()
if (cc == 0): # event line
ev = Event()
# 1 2000 1 2 20 28 37.270 38.2843 39.0241 11.00 3.60 8 1725385
# id_ev = int(tmp[0])
ev.id = int(tmp[0])
year = int(tmp[1])
month = int(tmp[2])
day = int(tmp[3])
hour = int(tmp[4])
minute = int(tmp[5])
second = float(tmp[6])
ev.ortime = UTCDateTime(year,month,day,hour,minute,0) + second
ev.lat = float(tmp[7])
ev.lon = float(tmp[8])
ev.dep = float(tmp[9])
ev.mag = float(tmp[10])
ev.Nt = 0
ev.Ncs_dt = 0
ev.Ncr_dt = 0
ev.t = {}
ev.cs_dt = {}
ev.cr_dt = {}
ndata = int(tmp[11])
name_ev = tmp[12]
ev.name = name_ev
cc += 1
try:
ev.tag["weight"] = float(tmp[13])
except:
pass
if (ndata == 0):
cc = 0
ev_info[name_ev] = ev
else: # data line
# 1 1 MYA 38.3261 38.4253 1050.0000 P 52.46 6.630 weight
if(len(tmp) < 10): # absolue traveltime data
name_st = tmp[2]
phase = tmp[6]
if (phase == "PG"):
phase = "Pg"
if (phase == "PB"):
phase = "Pb"
if (phase == "PN"):
phase = "Pn"
if (not name_st in st_info):
st = Station()
st.name = name_st
st.id = float(tmp[1])
st.lat = float(tmp[3])
st.lon = float(tmp[4])
st.ele = float(tmp[5])
st_info[name_st] = st
time = float(tmp[7])
if(len(tmp) == 9):
weight = float(tmp[8])
else:
weight = 1.0
ev.t["%s+%s"%(name_st,phase)] = [name_st,phase,time,weight]
ev.Nt += 1
else: # differential traveltime data
phase = tmp[11]
if (phase.__contains__("cr")): # common receiver differential traveltime
# evid stid1 stname1 lat1 lon1 eve1 evid2 evname2 lat2 lon2 dep2 phase,cr diftime weight
name_st1 = tmp[2]
if (not name_st1 in st_info): # add station to the station list
st = Station()
st.name = name_st1
st.id = float(tmp[1])
st.lat = float(tmp[3])
st.lon = float(tmp[4])
st.ele = float(tmp[5])
st_info[name_st1] = st
name_ev2 = tmp[7]
# add earthquake to the temp earthquake list
ev2 = Event()
ev2.name = name_ev2
ev2.id = float(tmp[6])
ev2.lat = float(tmp[8])
ev2.lon = float(tmp[9])
ev2.dep = float(tmp[10])
tmp_ev_info[name_ev2] = ev2
dif_time = float(tmp[12])
if(len(tmp) == 14):
weight = float(tmp[13])
else:
weight = 1.0
ev.cr_dt["%s+%s+%s"%(name_st1,name_ev2,phase)] = [name_st1,name_ev2,phase,dif_time,weight]
ev.Ncr_dt += 1
else: # common source differential traveltime
# evid stid1 stname1 lat1 lon1 eve1 stid2 stname2 lat2 lon2 ele2 phase,cs diftime weight
name_st1 = tmp[2]
if (not name_st1 in st_info):
st = Station()
st.name = name_st1
st.id = len(st_info)
st.lat = float(tmp[3])
st.lon = float(tmp[4])
st.ele = float(tmp[5])
st_info[name_st1] = st
name_st2 = tmp[7]
if (not name_st2 in st_info):
st = Station()
st.name = name_st2
st.id = float(tmp[6])
st.lat = float(tmp[8])
st.lon = float(tmp[9])
st.ele = float(tmp[10])
st_info[name_st2] = st
dif_time = float(tmp[12])
if(len(tmp) == 14):
weight = float(tmp[13])
else:
weight = 1.0
ev.cs_dt["%s+%s+%s"%(name_st1,name_st2,phase)] = [name_st1,name_st2,phase,dif_time,weight]
ev.Ncs_dt += 1
if (cc == ndata): # end of the event data
cc = 0
ev_info[name_ev] = ev
else:
cc += 1
# Add earthquakes from the temporary earthquake list to the main earthquake list.
for key_ev in tmp_ev_info:
if (not key_ev in ev_info):
ev_info[key_ev] = tmp_ev_info[key_ev]
return [ev_info,st_info]
# %%
# Function: write_src_rec_file(fname, ev_info, st_info) Output the src_rec.dat file.
def write_src_rec_file(fname,ev_info,st_info):
ev_info = reorder_src(ev_info)
doc_src_rec = open(fname,'w')
min_lat = 9999
max_lat = -9999
min_lon = 9999
max_lon = -9999
min_dep = 9999
max_dep = -9999
record_ev = {}
record_st = {}
Nt_total = 0
Ncs_dt_total = 0
Ncr_dt_total = 0
for key_ev in ev_info:
ev = ev_info[key_ev]
evid = ev.id
year = ev.ortime.year
month = ev.ortime.month
day = ev.ortime.day
hour = ev.ortime.hour
minute = ev.ortime.minute
second = ev.ortime.second
msec = ev.ortime.microsecond
lat_ev = ev.lat
lon_ev = ev.lon
dep_ev = ev.dep
mag = ev.mag
ndata = ev.Nt + ev.Ncs_dt + ev.Ncr_dt
name_ev = ev.name
try:
weight_ev = ev.tag["weight"]
except:
weight_ev = 1.0
if(ndata == 0): # if the earthquake has no data, do not output it
continue
doc_src_rec.write('%7d %6d %2d %2d %2d %2d %5.2f %9.4f %9.4f %9.4f %5.2f %7d %s %7.3f\n'%(\
evid,year,month,day,hour,minute,second+msec/1000000,lat_ev,lon_ev,dep_ev,mag,ndata,name_ev,weight_ev))
min_lat = min(min_lat, lat_ev)
max_lat = max(max_lat, lat_ev)
min_lon = min(min_lon, lon_ev)
max_lon = max(max_lon, lon_ev)
min_dep = min(min_dep, dep_ev)
max_dep = max(max_dep, dep_ev)
record_ev[name_ev] = 1 # record this earthquake
Nt_total += ev.Nt
Ncs_dt_total += ev.Ncs_dt
Ncr_dt_total += ev.Ncr_dt
for key_t in ev.t:
data = ev.t[key_t]
st = st_info[data[0]]
stid = st.id
name_st = st.name
lat_st = st.lat
lon_st = st.lon
ele_st = st.ele
phase = data[1]
time = data[2]
try:
weight_data = data[3]
except:
weight_data = 1.0
doc_src_rec.write('%7d %7d %6s %9.4f %9.4f %9.4f %s %8.4f %7.3f \n'%(evid,stid,name_st,lat_st,lon_st,ele_st,phase,time,weight_data))
min_lat = min(min_lat, lat_st)
max_lat = max(max_lat, lat_st)
min_lon = min(min_lon, lon_st)
max_lon = max(max_lon, lon_st)
min_dep = min(min_dep, -ele_st/1000)
max_dep = max(max_dep, -ele_st/1000)
record_st[name_st] = 1 # record this station
for key_t in ev.cs_dt:
data = ev.cs_dt[key_t]
st1 = st_info[data[0]]
stid1 = st1.id
name_st1= st1.name
lat_st1 = st1.lat
lon_st1 = st1.lon
ele_st1 = st1.ele
st2 = st_info[data[1]]
stid2 = st2.id
name_st2= st2.name
lat_st2 = st2.lat
lon_st2 = st2.lon
ele_st2 = st2.ele
phase = data[2]
time = data[3]
try:
weight_data = data[4]
except:
weight_data = 1.0
doc_src_rec.write('%7d %7d %6s %9.4f %9.4f %9.4f %7d %6s %9.4f %9.4f %9.4f %s %8.4f %7.3f \n'%(\
evid,stid1,name_st1,lat_st1,lon_st1,ele_st1,stid2,name_st2,lat_st2,lon_st2,ele_st2,phase,time,weight_data))
min_lat = min(min_lat, lat_st1)
max_lat = max(max_lat, lat_st1)
min_lon = min(min_lon, lon_st1)
max_lon = max(max_lon, lon_st1)
min_dep = min(min_dep, -ele_st1/1000)
max_dep = max(max_dep, -ele_st1/1000)
min_lat = min(min_lat, lat_st2)
max_lat = max(max_lat, lat_st2)
min_lon = min(min_lon, lon_st2)
max_lon = max(max_lon, lon_st2)
min_dep = min(min_dep, -ele_st2/1000)
max_dep = max(max_dep, -ele_st2/1000)
record_st[name_st1] = 1 # record this station
record_st[name_st2] = 1 # record this station
for key_t in ev.cr_dt:
data = ev.cr_dt[key_t]
st = st_info[data[0]]
stid = st.id
name_st = st.name
lat_st = st.lat
lon_st = st.lon
ele_st = st.ele
ev2 = ev_info[data[1]]
evid2 = ev2.id
name_ev2= ev2.name
lat_ev2 = ev2.lat
lon_ev2 = ev2.lon
dep_ev2 = ev2.dep
phase = data[2]
time = data[3]
try:
weight_data = data[4]
except:
weight_data = 1.0
doc_src_rec.write('%7d %7d %6s %9.4f %9.4f %9.4f %7d %6s %9.4f %9.4f %9.4f %s %8.4f %7.3f \n'%(\
evid,stid,name_st,lat_st,lon_st,ele_st,evid2,name_ev2,lat_ev2,lon_ev2,dep_ev2,phase,time,weight_data))
min_lat = min(min_lat, lat_st)
max_lat = max(max_lat, lat_st)
min_lon = min(min_lon, lon_st)
max_lon = max(max_lon, lon_st)
min_dep = min(min_dep, -ele_st/1000)
max_dep = max(max_dep, -ele_st/1000)
min_lat = min(min_lat, lat_ev2)
max_lat = max(max_lat, lat_ev2)
min_lon = min(min_lon, lon_ev2)
max_lon = max(max_lon, lon_ev2)
min_dep = min(min_dep, dep_ev2)
max_dep = max(max_dep, dep_ev2)
record_ev[name_ev2] = 1 # record this station
record_st[name_st] = 1 # record this station
doc_src_rec.close()
print("src_rec.dat has been outputed: %d events, %d stations, %d abs traveltime, %d cs_dif traveltime, %d cr_dif traveltime. " \
%(len(record_ev),len(record_st),Nt_total,Ncs_dt_total,Ncr_dt_total))
print("earthquake and station region, lat: %6.1f - %6.1f, lon: %6.1f - %6.1f, dep: %6.1f - %6.1f"%(min_lat,max_lat,min_lon,max_lon,min_dep,max_dep) )
# %%
# Function: write_src_list_file(fname, ev_info) Output the event list file.
def write_src_list_file(fname,ev_info):
doc_ev_list = open(fname,'w')
for key_ev in ev_info:
ev = ev_info[key_ev]
evid = ev.id
lat_ev = ev.lat
lon_ev = ev.lon
dep_ev = ev.dep
mag = ev.mag
name_ev = ev.name
if (ev.id == -999): # if the earthquake has no data, do not output it
continue
doc_ev_list.write("%7d %s %s %9.4f %9.4f %9.4f %5.2f \n"%(evid,name_ev,ev.ortime,lat_ev,lon_ev,dep_ev,mag))
doc_ev_list.close()
# %%
# Function: write_rec_list_file(fname, ev_info, st_info) Output the station list file.
def write_rec_list_file(fname,ev_info,st_info):
doc_st_list = open(fname,'w')
st_list = {}
for key_ev in ev_info:
ev = ev_info[key_ev]
for key_t in ev.t:
data = ev.t[key_t]
st = st_info[data[0]]
name_st = st.name
lat_st = st.lat
lon_st = st.lon
ele_st = st.ele
if(not name_st in st_list):
doc_st_list.write("%6s %9.4f %9.4f %10.4f \n"%(name_st,lat_st,lon_st,ele_st))
st_list[name_st] = 1
for key_t in ev.cs_dt:
data = ev.cs_dt[key_t]
st1 = st_info[data[0]]
name_st1= st1.name
lat_st1 = st1.lat
lon_st1 = st1.lon
ele_st1 = st1.ele
st2 = st_info[data[0]]
name_st2= st2.name
lat_st2 = st2.lat
lon_st2 = st2.lon
ele_st2 = st2.ele
if(not name_st1 in st_list):
doc_st_list.write("%6s %9.4f %9.4f %10.4f \n"%(name_st1,lat_st1,lon_st1,ele_st1))
st_list[name_st1] = 1
if(not name_st2 in st_list):
doc_st_list.write("%6s %9.4f %9.4f %10.4f \n"%(name_st2,lat_st2,lon_st2,ele_st2))
st_list[name_st2] = 1
for key_t in ev.cr_dt:
data = ev.cr_dt[key_t]
st = st_info[data[0]]
name_st = st.name
lat_st = st.lat
lon_st = st.lon
ele_st = st.ele
if(not name_st in st_list):
doc_st_list.write("%6s %9.4f %9.4f %10.4f \n"%(name_st,lat_st,lon_st,ele_st))
st_list[name_st] = 1
doc_st_list.close()
# %% [markdown]
# Functions: read objective function file
# %%
# Function: read_objective_function_file(path)
def read_objective_function_file(path):
full_curve = []
location_curve = []
model_curve = []
with open('%s/objective_function.txt'%(path)) as f:
for i,line in enumerate(f):
tmp = line.split(',')
if (tmp[0].__contains__("#")):
continue # skip the comment line
iter = int(tmp[0])
tag = tmp[1]
obj = float(tmp[2])
obj_abs = float(tmp[3])
obj_cs = float(tmp[4])
obj_cr = float(tmp[5])
obj_tele = float(tmp[6])
tmp2 = tmp[7].split('/')
mean = float(tmp2[0])
std = float(tmp2[1])
tmp2 = tmp[8].split('/')
mean_abs = float(tmp2[0])
std_abs = float(tmp2[1])
tmp2 = tmp[9].split('/')
mean_cs = float(tmp2[0])
std_cs = float(tmp2[1])
tmp2 = tmp[10].split('/')
mean_cr = float(tmp2[0])
std_cr = float(tmp2[1])
tmp2 = tmp[11].split('/')
mean_tele = float(tmp2[0])
std_tele = float(tmp2[1])
full_curve.append([obj,obj_abs,obj_cs,obj_cr,obj_tele,mean,std,mean_abs,std_abs,mean_cs,std_cs,mean_cr,std_cr,mean_tele,std_tele])
if tag.__contains__("relocation"):
location_curve.append([obj,obj_abs,obj_cs,obj_cr,obj_tele,mean,std,mean_abs,std_abs,mean_cs,std_cs,mean_cr,std_cr,mean_tele,std_tele])
if tag.__contains__("model"):
model_curve.append([obj,obj_abs,obj_cs,obj_cr,obj_tele,mean,std,mean_abs,std_abs,mean_cs,std_cs,mean_cr,std_cr,mean_tele,std_tele])
return np.array(full_curve),np.array(location_curve),np.array(model_curve)
# %% [markdown]
# Functions: read inversion grid file
# %%
# Function: read the inversion grid file
def read_inversion_grid_file(path):
inv_grid_vel = []
inv_grid_ani = []
switch = False
igrid = -1
with open('%s/inversion_grid.txt'%(path)) as f:
tmp_inv_grid = []
for i,line in enumerate(f):
# read the number of inversion grid in dep, lat, lon directions
if(i==0):
tmp = line.split()
ndep = int(tmp[1])
nlines = 3*ndep+1 # The number of rows for each inversion grid is 3*ndep+1
iline = i % nlines
if(iline == 0): # info: number of inversion grid
tmp = line.split()
if (int(tmp[0]) > igrid):
igrid = int(tmp[0])
else: # change from vel to ani
switch = True
igrid = int(tmp[0])
else: # info location of inversion grid
iline_sub = (iline-1) % 3
if(iline_sub == 0): # dep
tmp = line.split()
dep = float(tmp[0])
if(iline_sub == 1): # list of lat
lat_list = line.split()
if(iline_sub == 2): # list of lon
lon_list = line.split()
# add inversion grid
for lat in lat_list:
for lon in lon_list:
tmp_inv_grid.append([float(lon), float(lat), dep])
if(iline == nlines-1): # the last line of inversion grid
if(switch):
inv_grid_ani.append(tmp_inv_grid)
else:
inv_grid_vel.append(tmp_inv_grid)
tmp_inv_grid = []
return [np.array(inv_grid_vel),np.array(inv_grid_ani)]
# %% [markdown]
# Functions: for plotting
# %%
# Function: fig_ev_st_distribution_dep(ev_info, st_info) Plot the distribution of the earthquakes and stations, color-coded by earthquake depth.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def fig_ev_st_distribution_dep(ev_info,st_info):
[lon_ev,lat_ev,dep_ev,wt_ev] = data_lon_lat_dep_wt_ev(ev_info)
[lon_st,lat_st,ele_st,wt_st] = data_lon_lat_ele_wt_st(ev_info,st_info)
min_lon = min(min(lon_ev),min(lon_st))
max_lon = max(max(lon_ev),max(lon_st))
min_lat = min(min(lat_ev),min(lat_st))
max_lat = max(max(lat_ev),max(lat_st))
max_dep = max(dep_ev)
# Insert a value that does not affect the plot to make the colorbar range look better.
lon_ev = np.insert(lon_ev, 0, 9999); lat_ev = np.insert(lat_ev, 0, 9999); dep_ev = np.insert(dep_ev, 0, 0);
fig = plt.figure(figsize=(12,12))
gridspace = GridSpec(12,12,figure = fig)
xrange = max_lon - min_lon + 1.0
yrange = max_lat - min_lat + 1.0
if (xrange > yrange):
fig_x_size = 6
fig_y_size = round(6*yrange/xrange)
else:
fig_x_size = round(6*xrange/yrange)
fig_y_size = 6
ax1 = fig.add_subplot(gridspace[0:fig_y_size,0:fig_x_size])
bar_ev = ax1.scatter(lon_ev,lat_ev,c=dep_ev,cmap="jet",label = "src",s = 3)
# ax1.plot(lon_st,lat_st,'rv',label = "rec",markersize = 6)
bar_st = ax1.scatter(lon_st,lat_st,c="red",label = "rec",s = 100,marker='v',edgecolors='white')
ax1.legend(fontsize = 14)
ax1.tick_params(axis='x',labelsize=18)
ax1.tick_params(axis='y',labelsize=18)
ax1.set_xlabel('Lon',fontsize=18)
ax1.set_ylabel('Lat',fontsize=18)
ax1.set_xlim((min_lon - (max_lon - min_lon)*0.1,max_lon + (max_lon - min_lon)*0.1))
ax1.set_ylim((min_lat - (max_lat - min_lat)*0.1,max_lat + (max_lat - min_lat)*0.1))
ax2 = fig.add_subplot(gridspace[0:fig_y_size, fig_x_size+1 : fig_x_size+3])
ax2.scatter(dep_ev,lat_ev,c=dep_ev,cmap="jet",label = "src",s = 3)
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_xlabel('Dep',fontsize=18)
ax2.set_ylabel('Lat',fontsize=18)
ax2.set_xlim((-max_dep*0.05,max_dep*1.1))
ax2.set_ylim((min_lat - (max_lat - min_lat)*0.1,max_lat + (max_lat - min_lat)*0.1))
ax3 = fig.add_subplot(gridspace[fig_y_size+1:fig_y_size+3,0:fig_x_size])
ax3.scatter(lon_ev,dep_ev,c=dep_ev,cmap="jet",label = "src",s = 3)
ax3.tick_params(axis='x',labelsize=18)
ax3.tick_params(axis='y',labelsize=18)
ax3.set_xlabel('Lon',fontsize=18)
ax3.set_ylabel('Dep',fontsize=18)
ax3.set_xlim((min_lon - (max_lon - min_lon)*0.1,max_lon + (max_lon - min_lon)*0.1))
ax3.set_ylim((-max_dep*0.05,max_dep*1.1))
ax3.invert_yaxis()
# Place the colorbar on a new axis.
ax4 = fig.add_subplot(gridspace[fig_y_size+2:fig_y_size+3,fig_x_size+1:fig_x_size+3])
cbar1 = plt.colorbar(bar_ev, ax=ax4,orientation='horizontal')
cbar1.set_label('Depth of earthquakes',fontsize=16)
cbar1.ax.tick_params(axis='x', labelsize=16) # Colorbar font size.
# Hide the borders of the axes.
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['bottom'].set_visible(False)
ax4.spines['left'].set_visible(False)
# Hide the tick values of the axes.
ax4.set_xticks([])
ax4.set_yticks([])
# %%
# Function: fig_ev_st_distribution_wt(ev_info, st_info) Plot the distribution of the earthquakes and stations, color-coded by weight.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from matplotlib.colors import ListedColormap
from mpl_toolkits.axes_grid1 import make_axes_locatable
import math
def fig_ev_st_distribution_wt(ev_info,st_info):
[lon_ev,lat_ev,dep_ev,wt_ev] = data_lon_lat_dep_wt_ev(ev_info)
[lon_st,lat_st,ele_st,wt_st] = data_lon_lat_ele_wt_st(ev_info,st_info)
# Insert a value that does not affect the plot to make the colorbar range look better.
lon_ev = np.insert(lon_ev, 0, lon_ev[0]); lat_ev = np.insert(lat_ev, 0, lat_ev[0]); dep_ev = np.insert(dep_ev, 0, dep_ev[0]); wt_ev = np.insert(wt_ev, 0, 0.0)
lon_ev = np.insert(lon_ev, 0, lon_ev[0]); lat_ev = np.insert(lat_ev, 0, lat_ev[0]); dep_ev = np.insert(dep_ev, 0, dep_ev[0]); wt_ev = np.insert(wt_ev, 0, 1.0)
lon_st = np.insert(lon_st, 0, lon_st[0]); lat_st = np.insert(lat_st, 0, lat_st[0]); ele_st = np.insert(ele_st, 0, ele_st[0]); wt_st = np.insert(wt_st, 0, 0.0)
lon_st = np.insert(lon_st, 0, lon_st[0]); lat_st = np.insert(lat_st, 0, lat_st[0]); ele_st = np.insert(ele_st, 0, ele_st[0]); wt_st = np.insert(wt_st, 0, 1.0)
min_lon = min(min(lon_ev),min(lon_st))
max_lon = max(max(lon_ev),max(lon_st))
min_lat = min(min(lat_ev),min(lat_st))
max_lat = max(max(lat_ev),max(lat_st))
max_dep = max(dep_ev)
fig = plt.figure(figsize=(12,12))
gridspace = GridSpec(12,12,figure = fig)
xrange = max_lon - min_lon + 1.0
yrange = max_lat - min_lat + 1.0
if (xrange > yrange):
fig_x_size = 6
fig_y_size = round(6*yrange/xrange)
else:
fig_x_size = round(6*xrange/yrange)
fig_y_size = 6
ax1 = fig.add_subplot(gridspace[0:fig_y_size,0:fig_x_size])
bar_ev = ax1.scatter(lon_ev,lat_ev,c=wt_ev,cmap="jet",label = "src",s = 3)
bar_st = ax1.scatter(lon_st,lat_st,c=wt_st,cmap="jet",label = "rec",s = 100,marker='^',edgecolors='white')
ax1.legend(fontsize = 14)
ax1.tick_params(axis='x',labelsize=18)
ax1.tick_params(axis='y',labelsize=18)
ax1.set_xlabel('Lon',fontsize=18)
ax1.set_ylabel('Lat',fontsize=18)
ax1.set_xlim((min_lon - (max_lon - min_lon)*0.1,max_lon + (max_lon - min_lon)*0.1))
ax1.set_ylim((min_lat - (max_lat - min_lat)*0.1,max_lat + (max_lat - min_lat)*0.1))
ax2 = fig.add_subplot(gridspace[0:fig_y_size, fig_x_size+1 : fig_x_size+3])
ax2.scatter(dep_ev,lat_ev,c=wt_ev,cmap="jet",label = "src",s = 3)
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_xlabel('Dep',fontsize=18)
ax2.set_ylabel('Lat',fontsize=18)
ax2.set_xlim((-max_dep*0.05,max_dep*1.1))
ax2.set_ylim((min_lat - (max_lat - min_lat)*0.1,max_lat + (max_lat - min_lat)*0.1))
ax3 = fig.add_subplot(gridspace[fig_y_size+1:fig_y_size+3,0:fig_x_size])
ax3.scatter(lon_ev,dep_ev,c=wt_ev,cmap="jet",label = "src",s = 3)
ax3.tick_params(axis='x',labelsize=18)
ax3.tick_params(axis='y',labelsize=18)
ax3.set_xlabel('Lon',fontsize=18)
ax3.set_ylabel('Dep',fontsize=18)
ax3.set_xlim((min_lon - (max_lon - min_lon)*0.1,max_lon + (max_lon - min_lon)*0.1))
ax3.set_ylim((-max_dep*0.05,max_dep*1.1))
ax3.invert_yaxis()
# Place the colorbar on a new axis.
ax4 = fig.add_subplot(gridspace[fig_y_size+2:fig_y_size+3,fig_x_size+1:fig_x_size+3])
cbar1 = plt.colorbar(bar_st, ax=ax4,orientation='horizontal')
cbar1.set_label('Weight of stations',fontsize=16)
cbar1.ax.tick_params(axis='x', labelsize=16) # colorbar font size.
# Hide the borders of the axes.
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['bottom'].set_visible(False)
ax4.spines['left'].set_visible(False)
# Hide the tick values of the axes.
ax4.set_xticks([])
ax4.set_yticks([])
# Place the colorbar on a new axis.
ax5 = fig.add_subplot(gridspace[fig_y_size+1:fig_y_size+2,fig_x_size+1:fig_x_size+3])
cbar1 = plt.colorbar(bar_ev, ax=ax5,orientation='horizontal')
cbar1.set_label('Weight of earthquakes',fontsize=16)
cbar1.ax.tick_params(axis='x', labelsize=16) # colorbar font size.
# Hide the borders of the axes.
ax5.spines['top'].set_visible(False)
ax5.spines['right'].set_visible(False)
ax5.spines['bottom'].set_visible(False)
ax5.spines['left'].set_visible(False)
# Hide the tick values of the axes.
ax5.set_xticks([])
ax5.set_yticks([])
# %%
# Plot and function: plot the distance-time scatter plot, remove the outliers.
# Limit the data within the range defined by the line time = dis * slope + intercept and the bounds up and down.
# Remove outliers, only retain data satisfying: slope * dis + intercept + down < time < slope * dis + intercept + up.
def fig_data_plot_remove_outliers(ev_info,st_info,slope,intercept,up,down,dis_min,dis_max):
fig = plt.figure(figsize=(10,10))
gridspace = GridSpec(6,6,figure = fig)
ax2 = fig.add_subplot(gridspace[0:6, 0:6])
# plot original data
[dis_obs,time_obs] = data_dis_time(ev_info,st_info)
ax2.plot(dis_obs,time_obs,'r.',markersize=1.5,label = "discarded")
# remove outliers, only retain data satisfying: slope * dis + intercept + down < time < slope * dis + intercept + up
ev_info = limit_data_residual(ev_info,st_info,slope,intercept,up,down)
[dis_obs,time_obs] = data_dis_time(ev_info,st_info)
ax2.plot(dis_obs,time_obs,'b.',markersize=1.5,label = "retained")
ax2.plot([dis_min,dis_max],[slope*dis_min+intercept+up,slope*dis_max+intercept+up],'b-',linewidth=2)
ax2.plot([dis_min,dis_max],[slope*dis_min+intercept+down,slope*dis_max+intercept+down],'b-',linewidth=2)
ax2.plot([dis_min,dis_max],[slope*dis_min+intercept,slope*dis_max+intercept],'k-',linewidth=2)
ax2.legend(fontsize = 14)
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_xlabel('Distance (km)',fontsize=18)
ax2.set_ylabel('Traveltime',fontsize=18)
ax2.set_xlim((dis_min,dis_max))
ax2.set_ylim((intercept+down-5,slope*dis_max+intercept+up+5))
return ev_info
# %%
# Plot: distance-time scatter plot of given phases.
def fig_data_plot_phase(ev_info,st_info,phase_list,color_list,dis_min,dis_max):
[dis_obs_phase,time_obs_phase] = data_dis_time_phase(ev_info,st_info,phase_list)
regression = {}
# Calculate the least squares y = ax+b
for key_phase in phase_list:
X = dis_obs_phase[key_phase]
Y = time_obs_phase[key_phase]
if(len(X)>20):
regression[key_phase] = linear_regression(X,Y)
else:
print("No enough data: %d, for %s"%(len(X),key_phase))
regression[key_phase] = [0,0,0]
# draw
fig = plt.figure(figsize=(10,10))
gridspace = GridSpec(6,6,figure = fig)
ax2 = fig.add_subplot(gridspace[0:6, 0:6])
y1 = 99999; y2 = -99999
# scatter plot
for iphase in range(len(phase_list)):
phase = phase_list[iphase]
color = color_list[iphase]
ax2.plot(dis_obs_phase[phase],time_obs_phase[phase],'%s.'%(color),markersize=1)
# linear regression plot
for iphase in range(len(phase_list)):
phase = phase_list[iphase]
color = color_list[iphase]
(slope,intercept,SEE)= regression[phase]
ax2.plot([dis_min,dis_max],[dis_min*slope+intercept,dis_max*slope+intercept],'%s-'%(color),linewidth=2,label = "%s: a,b,SEE=%5.2f,%5.2f,%5.2f"%(phase,slope,intercept,SEE))
y1 = min(y1,intercept-5)
y2 = max(y2,dis_max*slope+intercept+5)
ax2.legend(fontsize = 14)
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_xlabel('Distance (km)',fontsize=18)
ax2.set_ylabel('Traveltime (s)',fontsize=18)
ax2.set_xlim((dis_min,dis_max))
for iphase in range(len(phase_list)):
try:
y1 = min(y1,min(time_obs_phase[phase]))
y2 = max(y2,max(time_obs_phase[phase]))
except:
pass
ax2.set_ylim((y1,y2))
title = ""
for phase in dis_obs_phase:
title = title + "%s(%d) "%(phase,len(dis_obs_phase[phase]))
ax2.set_title(title)
print("a is slope, b is intercept, SEE is standard error of estimate")
# %% [markdown]
# Functions: results analysis and evaluation
# %%
# plot: plot the residual histogram of the initial and final model
def fig_residual_histogram(fn_syn_init,fn_syn_final,fn_obs,range_l,range_r,Nbar,tag1 = "initial",tag2 = "final"):
# read synthetic traveltime data in the initial model
[ev_info_syn_init, st_info_syn_init] = read_src_rec_file(fn_syn_init)
time_syn_init = data_dis_time(ev_info_syn_init,st_info_syn_init)[1]
# read synthetic traveltime data in the final model
[ev_info_syn_final, st_info_syn_final] = read_src_rec_file(fn_syn_final)
time_syn_final = data_dis_time(ev_info_syn_final,st_info_syn_final)[1]
# read observed traveltime data
[ev_info_obs, st_info_obs] = read_src_rec_file(fn_obs)
time_obs = data_dis_time(ev_info_obs,st_info_obs)[1]
fig = plt.figure(figsize=(6,6))
gridspace = GridSpec(6,6,figure = fig)
ax2 = fig.add_subplot(gridspace[0:6, 0:6])
bins=np.linspace(range_l,range_r,Nbar)
error_init = time_syn_init - time_obs
error_final = time_syn_final - time_obs
hist_init, _, _ = ax2.hist(error_init,bins=bins,histtype='step', edgecolor = "red", linewidth = 2,
label = "%s: std = %5.3f s, mean = %5.3f s"%(tag1,np.std(error_init),np.mean(error_init)))
hist_final, _, _ = ax2.hist(error_final,bins=bins,alpha = 0.5, color = "blue",
label = "%s: std = %5.3f s, mean = %5.3f s"%(tag2,np.std(error_final),np.mean(error_final)))
print("residual for ",tag1," model is: ","mean: ",np.mean(error_init),"sd: ",np.std(error_init))
print("residual for ",tag2," model is: ","mean: ",np.mean(error_final),"sd: ",np.std(error_final))
ax2.legend(fontsize=14)
ax2.set_xlim(range_l - abs(range_l)*0.1,range_r + abs(range_r)*0.1)
ax2.set_ylim(0,1.3*max(max(hist_init),max(hist_final)))
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_ylabel('Number of data',fontsize=18)
ax2.set_xlabel('Traveltime residuals (s)',fontsize=18)
ax2.set_title("$t_{syn} - t_{obs}$",fontsize=18)
ax2.grid()
# %%
# plot: plot the residual histogram of the initial and final model
def fig_data_difference_histogram(fn_A,fn_B,range_l,range_r,Nbar):
# read data A
[ev_info_A, st_info_A] = read_src_rec_file(fn_A)
# read data B
[ev_info_B, st_info_B] = read_src_rec_file(fn_B)
# absolute traveltime residual
error_t = []
for key_ev in ev_info_A:
for key_t in ev_info_A[key_ev].t:
data_A = ev_info_A[key_ev].t[key_t]
if (key_ev in ev_info_B and key_t in ev_info_B[key_ev].t):
data_B = ev_info_B[key_ev].t[key_t]
error_t.append(data_A[2] - data_B[2])
# common-source differential traveltime residual
error_cs_dt = []
for key_ev in ev_info_A:
for key_dt in ev_info_A[key_ev].cs_dt:
data_A = ev_info_A[key_ev].cs_dt[key_dt]
if (key_ev in ev_info_B and key_dt in ev_info_B[key_ev].cs_dt):
data_B = ev_info_B[key_ev].cs_dt[key_dt]
error_cs_dt.append(data_A[3] - data_B[3])
else:
print(key_ev,key_dt)
# common-receiver differential traveltime residual
error_cr_dt = []
for key_ev in ev_info_A:
for key_dt in ev_info_A[key_ev].cr_dt:
data_A = ev_info_A[key_ev].cr_dt[key_dt]
if (key_ev in ev_info_B and key_dt in ev_info_B[key_ev].cr_dt):
data_B = ev_info_B[key_ev].cr_dt[key_dt]
error_cr_dt.append(data_A[3] - data_B[3])
# plot
fig = plt.figure(figsize=(14,6))
gridspace = GridSpec(6,14,figure = fig)
ax2 = fig.add_subplot(gridspace[0:6, 0:6])
bins=np.linspace(range_l,range_r,Nbar)
# hist_t, _, _ = ax2.hist(error_t,bins=bins,histtype='step', edgecolor = "red", linewidth = 2,
# label = "noise: std = %5.3f s, mean = %5.3f s"%(np.std(error_t),np.mean(error_t)))
hist_t, _, _ = ax2.hist(error_t,bins=bins,alpha = 0.5, color = "blue",
label = "noise: std = %5.3f s, mean = %5.3f s"%(np.std(error_t),np.mean(error_t)))
ax2.legend(fontsize=14)
ax2.set_xlim(range_l - abs(range_l)*0.1,range_r + abs(range_r)*0.1)
try:
ax2.set_ylim(0,1.3*max(hist_t))
except:
ax2.set_ylim(0,1.0)
ax2.tick_params(axis='x',labelsize=18)
ax2.tick_params(axis='y',labelsize=18)
ax2.set_ylabel('Number of data',fontsize=18)
ax2.set_xlabel('Noise (s)',fontsize=18)
ax2.set_title("Noise of traveltime",fontsize=18)
ax3 = fig.add_subplot(gridspace[0:6,8:14])
bins=np.linspace(range_l,range_r,Nbar)
# hist_t, _, _ = ax3.hist(error_t,bins=bins,histtype='step', edgecolor = "red", linewidth = 2,
# label = "noise: std = %5.3f s, mean = %5.3f s"%(np.std(error_t),np.mean(error_t)))
hist_cs_dt, _, _ = ax3.hist(error_cs_dt,bins=bins,alpha = 0.5, color = "blue",
label = "noise: std = %5.3f s, mean = %5.3f s"%(np.std(error_cs_dt),np.mean(error_cs_dt)))
ax3.legend(fontsize=14)
ax3.set_xlim(range_l - abs(range_l)*0.1,range_r + abs(range_r)*0.1)
try:
ax3.set_ylim(0,1.3*max(hist_cs_dt))
except:
ax3.set_ylim(0,1.0)
ax3.tick_params(axis='x',labelsize=18)
ax3.tick_params(axis='y',labelsize=18)
ax3.set_ylabel('Number of data',fontsize=18)
ax3.set_xlabel('Noise (s)',fontsize=18)
ax3.set_title("Noise of differential traveltime",fontsize=18)