def computeD(self, i): """ :param i: Compute d for the i-th quad/segment. Y = d/W, where d is the portion (in km) of the width of the fault which ruptures up-dip from the hypocenter to the top of the fault. """ hyp_ecef = self.phyp[i] # already in ECEF hyp_col = np.array([[hyp_ecef.x], [hyp_ecef.y], [hyp_ecef.z]]) # First compute "updip" vector P0,P1,P2,P3 = self.flt.Quadrilaterals[i] p1 = Vector.fromPoint(P1) # convert to ECEF p2 = Vector.fromPoint(P2) e21 = p1 - p2 e21norm = e21.norm() hp1 = p1 - hyp_ecef udip_len = Vector.dot(hp1, e21norm)/1000.0 # convert to km (used as max later) udip_col = np.array([[e21norm.x], [e21norm.y], [e21norm.z]]) # ECEF coords # Sites slat = self.sites[1] slon = self.sites[0] # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape) ) site_mat = np.array([np.reshape(site_ecef_x, (-1,)), np.reshape(site_ecef_y, (-1,)), np.reshape(site_ecef_z, (-1,))]) # Hypocenter-to-site matrix h2s_mat = site_mat - hyp_col # in ECEF # Dot hypocenter-to-site with updip vector d_raw = np.abs(np.sum(h2s_mat * udip_col, axis = 0))/1000.0 # convert to km d_raw = np.reshape(d_raw, self.sites[0].shape) self.d = d_raw.clip(min = 1.0, max = udip_len)
def test(): print('Testing ECEF conversion code...') lat = 32.1 lon = 118.5 dep = 10.0 x,y,z = latlon2ecef(lat,lon,dep) TESTX = -2576515.419 TESTY = 4745351.087 TESTZ = 3364516.124 np.testing.assert_almost_equal(x,TESTX,decimal=2) np.testing.assert_almost_equal(y,TESTY,decimal=2) np.testing.assert_almost_equal(z,TESTZ,decimal=2) lat2,lon2,dep2 = ecef2latlon(x,y,z) np.testing.assert_almost_equal(lat2,lat,decimal=2) np.testing.assert_almost_equal(lon2,lon,decimal=2) np.testing.assert_almost_equal(dep2,dep,decimal=2) print('Passed tests of ECEF conversion code.')
def computeWrup(self): """ Wrup is the portion (in km) of the width of the fault which ruptures up-dip from the hypocenter to the top of the fault. * This is ambiguous for faults with varible top of rupture (not allowed in NGA). For now, lets just compute this for the quad where the hypocenter is located. * Alternative is to compute max Wrup for the different quads. """ nquad = len(self._flt._quadrilaterals) #------------------------------------------- # First find which quad the hypocenter is on #------------------------------------------- x, y, z = ecef.latlon2ecef(self._hyp.latitude, self._hyp.longitude, self._hyp.depth) hyp_ecef = np.array([[x, y, z]]) qdist = np.zeros(nquad) for i in range(0, nquad): P0, P1, P2, P3 = self._flt.getQuadrilaterals()[i] qdist[i] = calc_rupture_distance(P0, P1, P2, P3, hyp_ecef) ind = int(np.where(qdist == np.min(qdist))[0]) # *** check that this doesn't break with more than one quad q = self._flt.getQuadrilaterals()[ind] #-------------------------- # Compute Wrup on that quad #-------------------------- pp0 = Vector.fromPoint( geo.point.Point(q[0].longitude, q[0].latitude, q[0].depth)) hyp_ecef = Vector.fromPoint( geo.point.Point(self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) hp0 = hyp_ecef - pp0 ddv = fault.get_quad_down_dip_vector(q) self._Wrup = Vector.dot(ddv, hp0) / 1000
def _get_quad_slip_ds_ss(q, rake, cp, p): """ Compute the DIP SLIP and STRIKE SLIP components of the unit slip vector in ECEF coords for a quad and rake angle. :param q: A quadrilateral. :param rake: Direction of motion of the hanging wall relative to the foot wall, as measured by the angle (deg) from the strike vector. :param cp: A 3x(n sub fault) array giving center points of each sub fault in ECEF coords. :param p: A 3x(n sub fault) array giving the unit vectors of the propagation vector on each sub fault in ECEF coords. :returns: List of dip slip and strike slip components (each is a matrix) of the unit slip vector in ECEF space. """ # Get quad vertices, strike, dip P0, P1, P2, P3 = q strike = P0.azimuth(P1) dip = fault.get_quad_dip(q) # Slip unit vectors in 'local' (i.e., z-up, x-east) coordinates d1_local = fault.get_local_unit_slip_vector_DS(strike, dip, rake) s1_local = fault.get_local_unit_slip_vector_SS(strike, dip, rake) # Convert to a column array d1_col = np.array([[d1_local.x], [d1_local.y], [d1_local.z]]) s1_col = np.array([[s1_local.x], [s1_local.y], [s1_local.z]]) # Define 'local' coordinate system qlats = [a.latitude for a in q] qlons = [a.longitude for a in q] proj = get_orthographic_projection(np.min(qlons), np.max(qlons), np.min(qlats), np.max(qlats)) # Convert p and cp to geographic coords p0lat, p0lon, p0z = ecef2latlon(cp[0, ], cp[1, ], cp[2, ]) p1lat, p1lon, p1z = ecef2latlon(cp[0, ] + p[0, ], cp[1, ] + p[1, ], cp[2, ] + p[2, ]) # Convert p to 'local' coords p0x, p0y = proj(p0lon, p0lat) p1x, p1y = proj(p1lon, p1lat) px = p1x - p0x py = p1y - p0y pz = p1z - p0z # Apply sign changes in 'local' coords s1mat = np.array([[np.abs(s1_col[0]) * np.sign(px)], [np.abs(s1_col[1]) * np.sign(py)], [np.abs(s1_col[2]) * np.sign(pz)]]) # [np.abs(s1_col[2])*np.ones_like(pz)]]) dipsign = -np.sign(np.sin(np.radians(rake))) d1mat = np.array([[d1_col[0] * np.ones_like(px) * dipsign], [d1_col[1] * np.ones_like(py) * dipsign], [d1_col[2] * np.ones_like(pz) * dipsign]]) # Need to track 'origin' s0 = np.array([[0], [0], [0]]) # Convert from 'local' to geographic coords s1_ll = proj(s1mat[0, ], s1mat[1, ], reverse=True) d1_ll = proj(d1mat[0, ], d1mat[1, ], reverse=True) s0_ll = proj(s0[0], s0[1], reverse=True) # And then back to ECEF: s1_ecef = latlon2ecef(s1_ll[1], s1_ll[0], s1mat[2, ]) d1_ecef = latlon2ecef(d1_ll[1], d1_ll[0], d1mat[2, ]) s0_ecef = latlon2ecef(s0_ll[1], s0_ll[0], s0[2]) s00 = s0_ecef[0].reshape(-1) s01 = s0_ecef[1].reshape(-1) s02 = s0_ecef[2].reshape(-1) d_mat = np.array([ d1_ecef[0].reshape(-1) - s00, d1_ecef[1].reshape(-1) - s01, d1_ecef[2].reshape(-1) - s02 ]) s_mat = np.array([ s1_ecef[0].reshape(-1) - s00, s1_ecef[1].reshape(-1) - s01, s1_ecef[2].reshape(-1) - s02 ]) return d_mat, s_mat
def computeXiPrime(self): """ Computes the xi' value. """ hypo_ecef = Vector.fromPoint( geo.point.Point(self._hyp.longitude, self._hyp.latitude, self._hyp.depth)) epi_ll = Vector(self._hyp.longitude, self._hyp.latitude, 0) epi_ecef = Vector.fromPoint(geo.point.Point(epi_ll.x, epi_ll.y, 0)) slat = self._lat slon = self._lon # Convert site to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape)) site_mat = np.array([ np.reshape(site_ecef_x, (-1, )), np.reshape(site_ecef_y, (-1, )), np.reshape(site_ecef_z, (-1, )) ]) xi_prime_unscaled = np.zeros_like(slat) # Normalize by total number of subfaults. For mtype == 1, the number # of subfaults will vary with site and be different for xi_s and # xi_p, so keep two variables and sum them for each quad. nsubs = np.zeros(np.product(slat.shape)) nsubp = np.zeros(np.product(slat.shape)) xi_prime_s = np.zeros(np.product(slat.shape)) xi_prime_p = np.zeros(np.product(slat.shape)) for k in range(len(self._flt._quadrilaterals)): # Select a quad q = self._flt.getQuadrilaterals()[k] # Quad mesh (ECEF coords) mesh = fault.get_quad_mesh(q, self._dx) # Rupture plane normal vector (ECEF coords) rpnv = fault.get_quad_normal(q) rpnvcol = np.array([[rpnv.x], [rpnv.y], [rpnv.z]]) # Strike vector (ECEF coords) strike_vec = fault.get_quad_strike_vector(q) strike_vec_col = np.array([[strike_vec.x], [strike_vec.y], [strike_vec.z] ]) # convert to column vector # Down dip vector (ECEF coords) ddip_vec = fault.get_quad_down_dip_vector(q) ddip_vec_col = np.array([[ddip_vec.x], [ddip_vec.y], [ddip_vec.z]]) # convert to column vector # Make 3x(i*j) matrix of cp ni, nj = mesh['llx'].shape cp_mat = np.array([ np.reshape(mesh['cpx'], (-1, )), np.reshape(mesh['cpy'], (-1, )), np.reshape(mesh['cpz'], (-1, )) ]) # Compute matrix of p vectors hypcol = np.array([[hypo_ecef.x], [hypo_ecef.y], [hypo_ecef.z]]) pmat = cp_mat - hypcol # Project pmat onto quad ndotp = np.sum(pmat * rpnvcol, axis=0) pmat = pmat - ndotp * rpnvcol mag = np.sqrt(np.sum(pmat * pmat, axis=0)) pmatnorm = pmat / mag # like r1 # According to Rowshandel: # "The choice of the +/- sign in the above equations # depends on the (along-the-strike and across-the-dip) # location of the rupturing sub-fault relative to the # location of the hypocenter." # and: # "for the along the strike component of the slip unit # vector, the choice of the sign should result in the # slip unit vector (s) being exactly the same as the # rupture unit vector (p) for a pure strike-slip case" # Strike slip and dip slip components of unit slip vector # (ECEF coords) ds_mat, ss_mat = _get_quad_slip_ds_ss(q, self._rake, cp_mat, pmatnorm) slpmat = (ds_mat + ss_mat) mag = np.sqrt(np.sum(slpmat * slpmat, axis=0)) slpmatnorm = slpmat / mag # Loop over sites for i in range(site_mat.shape[1]): sitecol = np.array([[site_mat[0, i]], [site_mat[1, i]], [site_mat[2, i]]]) qmat = sitecol - cp_mat # 3x(ni*nj), like r2 mag = np.sqrt(np.sum(qmat * qmat, axis=0)) qmatnorm = qmat / mag # Propagation dot product pdotqraw = np.sum(pmatnorm * qmatnorm, axis=0) # Slip vector dot product sdotqraw = np.sum(slpmatnorm * qmatnorm, axis=0) if self._mtype == 1: # Only sum over (+) directivity effect subfaults # xi_p_prime pdotq = pdotqraw.clip(min=0) nsubp[i] = nsubp[i] + np.sum(pdotq > 0) # xi_s_prime sdotq = sdotqraw.clip(min=0) nsubs[i] = nsubs[i] + np.sum(sdotq > 0) elif self._mtype == 2: # Sum over contributing subfaults # xi_p_prime pdotq = pdotqraw nsubp[i] = nsubp[i] + cp_mat.shape[1] # xi_s_prime sdotq = sdotqraw nsubs[i] = nsubs[i] + cp_mat.shape[1] # Normalize by n sub faults later xi_prime_s[i] = xi_prime_s[i] + np.sum(sdotq) xi_prime_p[i] = xi_prime_p[i] + np.sum(pdotq) # Apply a water level to nsubp and nsubs to avoid division by # zero. This should only occur when the numerator is also zero # and so the resulting value should be zero. nsubs = np.maximum(nsubs, 1) nsubp = np.maximum(nsubp, 1) # We are outside the 'k' loop over nquads. # o Normalize xi_prime_s and xi_prime_p # o Reshape them # o Add them together with the 'a' weights xi_prime_tmp = (self._a_weight) * (xi_prime_s/nsubs) + \ (1-self._a_weight) * (xi_prime_p/nsubp) xi_prime_unscaled = xi_prime_unscaled + \ np.reshape(xi_prime_tmp, slat.shape) # Scale so that xi_prime has range (0, 1) if self._mtype == 1: xi_prime = xi_prime_unscaled elif self._mtype == 2: xi_prime = 0.5 * (xi_prime_unscaled + 1) self._xi_prime = xi_prime
def computeThetaAndS(self, i): """ :param i: Compute d for the i-th quad/segment. """ # self.phyp is in ECEF tmp = ecef.ecef2latlon(self.phyp[i].x, self.phyp[i].y, self.phyp[i].z) epi_ecef = Vector.fromPoint(geo.point.Point(tmp[1], tmp[0], 0.0)) epi_col = np.array([[epi_ecef.x], [epi_ecef.y], [epi_ecef.z]]) # First compute along strike vector P0,P1,P2,P3 = self.flt.Quadrilaterals[i] p0 = Vector.fromPoint(P0) # convert to ECEF p1 = Vector.fromPoint(P1) e01 = p1 - p0 e01norm = e01.norm() hp0 = p0 - epi_ecef hp1 = p1 - epi_ecef strike_min = Vector.dot(hp0, e01norm)/1000.0 # convert to km strike_max = Vector.dot(hp1, e01norm)/1000.0 # convert to km strike_col = np.array([[e01norm.x],[e01norm.y],[e01norm.z]]) # ECEF coords # Sites slat = self.sites[1] slon = self.sites[0] # Convert sites to ECEF: site_ecef_x = np.ones_like(slat) site_ecef_y = np.ones_like(slat) site_ecef_z = np.ones_like(slat) # Make a 3x(#number of sites) matrix of site locations # (rows are x, y, z) in ECEF site_ecef_x, site_ecef_y, site_ecef_z = ecef.latlon2ecef( slat, slon, np.zeros(slon.shape) ) site_mat = np.array([np.reshape(site_ecef_x, (-1,)), np.reshape(site_ecef_y, (-1,)), np.reshape(site_ecef_z, (-1,))]) # Epicenter-to-site matrix e2s_mat = site_mat - epi_col # in ECEF mag = np.sqrt(np.sum(e2s_mat*e2s_mat, axis = 0)) # Avoid division by zero mag[mag == 0] = 1e-12 e2s_norm = e2s_mat/mag # Dot epicenter-to-site with along-strike vector s_raw = np.sum(e2s_mat * strike_col, axis = 0)/1000.0 # conver to km # Put back into a 2d array s_raw = np.reshape(s_raw, self.sites[0].shape) self.s = np.abs(s_raw.clip(min = strike_min, max = strike_max)).clip(min = np.exp(1)) # Compute theta sdots = np.sum(e2s_norm * strike_col, axis = 0) theta_raw = np.arccos(sdots) # But theta is defined to be the reference angle # (i.e., the equivalent angle between 0 and 90 deg) sintheta = np.abs(np.sin(theta_raw)) costheta = np.abs(np.cos(theta_raw)) theta = np.arctan2(sintheta, costheta) self.theta = np.reshape(theta, self.sites[0].shape)