def __computeD(self, 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. :param i: index of segment for which d is to be computed. """ 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 = self._rup.getQuadrilaterals()[i][0:3] p1 = Vector.fromPoint(P1) # convert to ECEF p2 = Vector.fromPoint(P2) e21 = p1 - p2 e21norm = e21.norm() hp1 = p1 - hyp_ecef # convert to km (used as max later) udip_len = Vector.dot(hp1, e21norm) / 1000.0 udip_col = np.array([[e21norm.x], [e21norm.y], [e21norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # 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._lat.shape) self.d = d_raw.clip(min=1.0, max=udip_len)
def __computeD(self, 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. :param i: index of segment for which d is to be computed. """ 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.getQuadrilaterals()[i] p1 = Vector.fromPoint(P1) # convert to ECEF p2 = Vector.fromPoint(P2) e21 = p1 - p2 e21norm = e21.norm() hp1 = p1 - hyp_ecef # convert to km (used as max later) udip_len = Vector.dot(hp1, e21norm) / 1000.0 udip_col = np.array([[e21norm.x], [e21norm.y], [e21norm.z]]) # ECEF coords # Sites slat = self._lat slon = self._lon # 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._lat.shape) self.d = d_raw.clip(min=1.0, max=udip_len)
def __computeWrup(self): """ Compute the the portion (in km) of the width of the fault which ruptures up-dip from the hypocenter to the top of the fault. 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 = 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 test_ecef(): 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 _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 = q[0:3] 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 = 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 # 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._rup.getQuadrilaterals()[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._lat slon = self._lon # 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._lat.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._lat.shape)
def get_distance(methods, lat, lon, dep, source, use_median_distance=True): """ Calculate distance using any one of a number of distance measures. One of quadlist OR hypo must be specified. The following table gives the allowed distance strings and a description of each. +--------+----------------------------------------------------------+ | String | Description | +========+==========================================================+ | repi | Distance to epicenter. | +--------+----------------------------------------------------------+ | rhypo | Distance to hypocenter. | +--------+----------------------------------------------------------+ | rjb | Joyner-Boore distance; this is closest distance to the | | | surface projection of the rupture plane. | +--------+----------------------------------------------------------+ | rrup | Rupture distance; closest distance to the rupture plane. | +--------+----------------------------------------------------------+ | rx | Strike-normal distance; same as GC2 coordiante T. | +--------+----------------------------------------------------------+ | ry | Strike-parallel distance; same as GC2 coordiante U, but | | | with a shift in origin definition. See Spudich and Chiou | | | (2015) http://dx.doi.org/10.3133/ofr20151028. | +--------+----------------------------------------------------------+ | ry0 | Horizontal distance off the end of the rupture measured | | | parallel to strike. Can only be zero or positive. We | | | compute this as a function of GC2 coordinate U. | +--------+----------------------------------------------------------+ | U | GC2 coordinate U. | +--------+----------------------------------------------------------+ | T | GC2 coordinate T. | +--------+----------------------------------------------------------+ :param methods: List of strings (or just a string) of distances to compute. :param lat: A numpy array of latitudes. :param lon: A numpy array of longidues. :param dep: A numpy array of depths (km). :param source: source instance. :param use_median_distance: Boolean; only used if GMPE requests fault distances and not fault is availalbe. Default is True, meaning that point-source distances are adjusted based on magnitude to get the median fault distance. :returns: dictionary of numpy array of distances, size of lon.shape """ fault = source.getFault() hypo = source.getHypo() if fault is not None: quadlist = fault.getQuadrilaterals() else: quadlist = None # Dictionary for holding the distances distdict = dict() if not isinstance(methods, list): methods = [methods] methods_available = set( ['repi', 'rhypo', 'rjb', 'rrup', 'rx', 'ry', 'ry0', 'U', 'T']) if not set(methods).issubset(methods_available): raise NotImplementedError( 'One or more requested distance method is not ' 'valid or is not implemented yet') if (lat.shape == lon.shape) and (lat.shape == dep.shape): pass else: raise ShakeMapException('lat, lon, and dep must have the same shape.') oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) if ('rrup' in methods) or ('rjb' in methods): x, y, z = latlon2ecef(lat, lon, dep) x.shape = newshape y.shape = newshape z.shape = newshape sites_ecef = np.hstack((x, y, z)) # Define a projection that spands sites and fault if fault is None: all_lat = lat all_lon = lon else: all_lat = np.append(lat, fault.getLats()) all_lon = np.append(lon, fault.getLons()) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) # --------------------------------------------- # Distances that do not require loop over quads # --------------------------------------------- if ('repi' in methods) or \ (('rjb' in methods) and (quadlist is None)) or \ (('rrup' in methods) and (quadlist is None)) or \ (('ry0' in methods) and (quadlist is None)) or \ (('rx' in methods) and (quadlist is None)) or \ (('T' in methods) and (quadlist is None)) or \ (('U' in methods) and (quadlist is None)): if hypo is None: raise ShakeMapException('Cannot calculate epicentral distance ' 'without a point object') repidist = geodetic.distance(hypo.longitude, hypo.latitude, 0.0, lon, lat, dep) repidist = repidist.reshape(oldshape) distdict['repi'] = repidist if ('rhypo' in methods) or \ (('rrup' in methods) and (quadlist is None)): if hypo is None: raise ShakeMapException('Cannot calculate epicentral distance ' 'without a point object') rhypodist = geodetic.distance(hypo.longitude, hypo.latitude, hypo.depth, lon, lat, dep) rhypodist = rhypodist.reshape(oldshape) distdict['rhypo'] = rhypodist # -------------------------------------------------------- # Loop over quadlist for those distances that require loop # -------------------------------------------------------- if 'rrup' in methods: minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16 if 'rjb' in methods: minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16 if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) if quadlist is not None: #----------------------------------------------------------------- # For these distances, we need to sort out strike discordance and # nominal strike prior to starting the loop if there is more than # one segment. #----------------------------------------------------------------- segind = fault._getSegmentIndex() segindnp = np.array(segind) uind = np.unique(segind) nseg = len(uind) #------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is " selected from the two endpoints most # distant from each other." #------------------------------------------------------------------- if nseg > 1: # Need to get index of first and last quad # for each segment iq0 = np.zeros(nseg, dtype='int16') iq1 = np.zeros(nseg, dtype='int16') for k in uind: ii = [i for i, j in enumerate(segind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) #--------------------------------------------------------------- # This is an iterator for each possible combination of segments # including segment orientations (i.e., flipped). #--------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the segment pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 #--------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". #--------------------------------------------------------------- # Primate fixes the trend of the trial a vector. primate = -1 while primate < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(nseg) b_prime = [None] * nseg for j in range(nseg): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(nseg): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() primate = bhat.dot(ahat) if primate < 0: tmpA0 = copy.copy(A0) tmpA1 = copy.copy(A1) A0 = tmpA1 A1 = tmpA0 if quadlist is not None: # Length of prior segments s_i = 0.0 l_i = np.zeros(len(quadlist)) for i in range(len(quadlist)): P0, P1, P2, P3 = quadlist[i] if 'rrup' in methods: rrupdist = _calc_rupture_distance(P0, P1, P2, P3, sites_ecef) minrrup = np.minimum(minrrup, rrupdist) if 'rjb' in methods: S0 = copy.deepcopy(P0) S1 = copy.deepcopy(P1) S2 = copy.deepcopy(P2) S3 = copy.deepcopy(P3) S0.depth = 0.0 S1.depth = 0.0 S2.depth = 0.0 S3.depth = 0.0 rjbdist = _calc_rupture_distance(S0, S1, S2, S3, sites_ecef) minrjb = np.minimum(minrjb, rjbdist) if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): # Rx, Ry, and Ry0 are all computed if one is requested since # they all require similar information for the weights. This # isn't necessary for a single segment fault though. # Note, we are basing these calculations on GC2 coordinates U # and T as described in: # Spudich and Chiou (2015) # http://dx.doi.org/10.3133/ofr20151028. # Compute u_i and t_i for this segment t_i = __calc_t_i(P0, P1, lat, lon, proj) u_i = __calc_u_i(P0, P1, lat, lon, proj) # Quad length l_i[i] = get_quad_length(quadlist[i]) # Weight of segment, three cases # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan( (l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if nseg == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadlist))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(segindnp == segindnp[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) p1 = Vector.fromPoint(quadlist[iq0[segind[i]]][0]) s_ij_2 = ( (p1 - p_origin) * dc[segind[i]]).dot(ahat) / 1000.0 # This is implemented with GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] # Collect distances from loop into the distance dict if 'rjb' in methods: minrjb = minrjb.reshape(oldshape) distdict['rjb'] = minrjb if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): # Normalize by sum of quad weights GC2T = GC2T / totweight GC2U = GC2U / totweight distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if nseg > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 if 'rrup' in methods: minrrup = minrrup.reshape(oldshape) distdict['rrup'] = minrrup else: if 'rjb' in methods: if use_median_distance: warnings.warn( 'No fault; Replacing rjb with median rjb given M and repi.' ) cdir, tmp = os.path.split(__file__) # ------------------- # Sort out file names # ------------------- mech = source.getEventParam('mech') if not hasattr(source, '_tectonic_region'): rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv") vf = os.path.join(cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv") elif source._tectonic_region == 'Active Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Var.csv") elif source._tectonic_region == 'Stable Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Var.csv") else: warnings.warn( 'Unsupported tectonic region; using coefficients for unknown' 'tectonic region.') rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv") vf = os.path.join(cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv") # ----------------- # Start with ratios # ----------------- repi2rjb_ratios_tbl = pd.read_csv(rf, comment='#') r2rrt_cols = repi2rjb_ratios_tbl.columns[1:] mag_list = [] for column in (r2rrt_cols): if re.search('R\d+\.*\d*', column): magnitude = float( re.findall('R(\d+\.*\d*)', column)[0]) mag_list.append(magnitude) mag_list = np.array(mag_list) dist_list = np.log(np.array(repi2rjb_ratios_tbl['Repi_km'])) repi2rjb_grid = repi2rjb_ratios_tbl.values[:, 1:] repi2rjb_obj = spint.RectBivariateSpline(dist_list, mag_list, repi2rjb_grid, kx=1, ky=1) def repi2rjb_tbl(repi, M): ratio = repi2rjb_obj.ev(np.log(repi), M) rjb = repi * ratio return rjb repis = distdict['repi'] mags = np.ones_like(repis) * source.getEventParam('mag') rjb_hat = repi2rjb_tbl(repis, mags) distdict['rjb'] = rjb_hat # ------------------- # Additional Variance # ------------------- repi2rjbvar_ratios_tbl = pd.read_csv(vf, comment='#') repi2rjbvar_grid = repi2rjbvar_ratios_tbl.values[:, 1:] repi2rjbvar_obj = spint.RectBivariateSpline(dist_list, mag_list, repi2rjbvar_grid, kx=1, ky=1) rjbvar = repi2rjbvar_obj.ev(np.log(repis), mags) distdict['rjbvar'] = rjbvar else: warnings.warn('No fault; Replacing rjb with repi') distdict['rjb'] = distdict['repi'] if 'rrup' in methods: if use_median_distance: warnings.warn( 'No fault; Replacing rrup with median rrup given M and repi.' ) cdir, tmp = os.path.split(__file__) # ------------------- # Sort out file names # ------------------- rake = source._event_dict.get('rake') mech = rake_to_mech(rake) if not hasattr(source, '_tectonic_region'): rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv") elif source._tectonic_region == 'Active Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Var.csv") elif source._tectonic_region == 'Stable Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Var.csv") else: warnings.warn( 'Unsupported tectonic region; using coefficients for unknown' 'tectonic region.') rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv") # ----------------- # Start with ratios # ----------------- repi2rrup_ratios_tbl = pd.read_csv(rf, comment='#') r2rrt_cols = repi2rrup_ratios_tbl.columns[1:] mag_list = [] for column in (r2rrt_cols): if re.search('R\d+\.*\d*', column): magnitude = float( re.findall('R(\d+\.*\d*)', column)[0]) mag_list.append(magnitude) mag_list = np.array(mag_list) dist_list = np.log(np.array(repi2rrup_ratios_tbl['Repi_km'])) repi2rrup_grid = repi2rrup_ratios_tbl.values[:, 1:] repi2rrup_obj = spint.RectBivariateSpline(dist_list, mag_list, repi2rrup_grid, kx=1, ky=1) def repi2rrup_tbl(repi, M): ratio = repi2rrup_obj.ev(np.log(repi), M) rrup = repi * ratio return rrup repis = distdict['repi'] mags = np.ones_like(repis) * source.getEventParam('mag') rrup_hat = repi2rrup_tbl(repis, mags) distdict['rrup'] = rrup_hat # ------------------- # Additional Variance # ------------------- repi2rrupvar_ratios_tbl = pd.read_csv(vf, comment='#') repi2rrupvar_grid = repi2rrupvar_ratios_tbl.values[:, 1:] repi2rrupvar_obj = spint.RectBivariateSpline(dist_list, mag_list, repi2rrupvar_grid, kx=1, ky=1) rrupvar = repi2rrupvar_obj.ev(np.log(repis), mags) distdict['rrupvar'] = rrupvar else: warnings.warn('No fault; Replacing rrup with rhypo') distdict['rrup'] = distdict['rhypo'] if 'rx' in methods: warnings.warn('No fault; Setting Rx to zero.') distdict['rx'] = np.zeros_like(distdict['repi']) if 'ry0' in methods: warnings.warn('No fault; Replacing ry0 with repi') distdict['ry0'] = distdict['repi'] if 'ry' in methods: warnings.warn('No fault; Replacing ry with repi') distdict['ry'] = distdict['repi'] return distdict
def get_distance(methods, lat, lon, dep, source, use_median_distance=True): """ Calculate distance using any one of a number of distance measures. One of quadlist OR hypo must be specified. The following table gives the allowed distance strings and a description of each. +--------+----------------------------------------------------------+ | String | Description | +========+==========================================================+ | repi | Distance to epicenter. | +--------+----------------------------------------------------------+ | rhypo | Distance to hypocenter. | +--------+----------------------------------------------------------+ | rjb | Joyner-Boore distance; this is closest distance to the | | | surface projection of the rupture plane. | +--------+----------------------------------------------------------+ | rrup | Rupture distance; closest distance to the rupture plane. | +--------+----------------------------------------------------------+ | rx | Strike-normal distance; same as GC2 coordiante T. | +--------+----------------------------------------------------------+ | ry | Strike-parallel distance; same as GC2 coordiante U, but | | | with a shift in origin definition. See Spudich and Chiou | | | (2015) http://dx.doi.org/10.3133/ofr20151028. | +--------+----------------------------------------------------------+ | ry0 | Horizontal distance off the end of the rupture measured | | | parallel to strike. Can only be zero or positive. We | | | compute this as a function of GC2 coordinate U. | +--------+----------------------------------------------------------+ | U | GC2 coordinate U. | +--------+----------------------------------------------------------+ | T | GC2 coordinate T. | +--------+----------------------------------------------------------+ :param methods: List of strings (or just a string) of distances to compute. :param lat: A numpy array of latitudes. :param lon: A numpy array of longidues. :param dep: A numpy array of depths (km). :param source: source instance. :param use_median_distance: Boolean; only used if GMPE requests fault distances and not fault is availalbe. Default is True, meaning that point-source distances are adjusted based on magnitude to get the median fault distance. :returns: dictionary of numpy arrays of distances, size of lon.shape IMPORTANT: If a finite fault is not supplied, and the distance measures requested include rx, ry, ry0, U, or T, then zeros will be returned; if rjb is requested, repi will be returned; if rrup is requested, rhypo will be returned. """ fault = source.getFault() hypo = source.getHypo() if fault is not None: quadlist = fault.getQuadrilaterals() # Need a copy for GC2 since order of verticies/quads needs to be modivied. quadgc2 = copy.deepcopy(quadlist) else: quadlist = None # Dictionary for holding the distances distdict = dict() if not isinstance(methods, list): methods = [methods] methods_available = set(get_distance_measures()) if not set(methods).issubset(methods_available): raise NotImplementedError( 'One or more requested distance method is not ' 'valid or is not implemented yet') if (lat.shape == lon.shape) and (lat.shape == dep.shape): pass else: raise ShakeMapException('lat, lon, and dep must have the same shape.') oldshape = lon.shape if len(oldshape) == 2: newshape = (oldshape[0] * oldshape[1], 1) else: newshape = (oldshape[0], 1) if ('rrup' in methods) or ('rjb' in methods): x, y, z = latlon2ecef(lat, lon, dep) x.shape = newshape y.shape = newshape z.shape = newshape sites_ecef = np.hstack((x, y, z)) # Define a projection that spands sites and fault if fault is None: all_lat = lat all_lon = lon else: all_lat = np.append(lat, fault.getLats()) all_lon = np.append(lon, fault.getLons()) west = np.nanmin(all_lon) east = np.nanmax(all_lon) south = np.nanmin(all_lat) north = np.nanmax(all_lat) proj = get_orthographic_projection(west, east, north, south) # --------------------------------------------- # Distances that do not require loop over quads # --------------------------------------------- if ('repi' in methods) or \ (('rjb' in methods) and (quadlist is None)) or \ (('rrup' in methods) and (quadlist is None)) or \ (('ry0' in methods) and (quadlist is None)) or \ (('rx' in methods) and (quadlist is None)) or \ (('T' in methods) and (quadlist is None)) or \ (('U' in methods) and (quadlist is None)): # I don't think this error check makes sense any more because hypo # is assigned above with source.getHypo() that constructs it from # source._event_dict entries. if hypo is None: raise ShakeMapException('Cannot calculate epicentral distance ' 'without a point object') repidist = geodetic.distance(hypo.longitude, hypo.latitude, 0.0, lon, lat, dep) repidist = repidist.reshape(oldshape) distdict['repi'] = repidist if ('rhypo' in methods) or \ (('rrup' in methods) and (quadlist is None)): if hypo is None: raise ShakeMapException('Cannot calculate epicentral distance ' 'without a point object') rhypodist = geodetic.distance( hypo.longitude, hypo.latitude, hypo.depth, lon, lat, dep) rhypodist = rhypodist.reshape(oldshape) distdict['rhypo'] = rhypodist # -------------------------------------------------------- # Loop over quadlist for those distances that require loop # -------------------------------------------------------- if 'rrup' in methods: minrrup = np.ones(newshape, dtype=lon.dtype) * 1e16 if 'rjb' in methods: minrjb = np.ones(newshape, dtype=lon.dtype) * 1e16 if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): totweight = np.zeros(newshape, dtype=lon.dtype) GC2T = np.zeros(newshape, dtype=lon.dtype) GC2U = np.zeros(newshape, dtype=lon.dtype) if quadlist is not None: #----------------------------------------------------------------- # For these distances, we need to sort out strike discordance and # nominal strike prior to starting the loop if there is more than # one segment. #----------------------------------------------------------------- segind = fault._getSegmentIndex() segindnp = np.array(segind) uind = np.unique(segind) nseg = len(uind) #------------------------------------------------------------------- # The first thing we need to worry about is finding the coordinate # shift. U's origin is " selected from the two endpoints most # distant from each other." #------------------------------------------------------------------- if nseg > 1: # Need to get index of first and last quad # for each segment iq0 = np.zeros(nseg, dtype='int16') iq1 = np.zeros(nseg, dtype='int16') for k in uind: ii = [i for i, j in enumerate(segind) if j == uind[k]] iq0[k] = int(np.min(ii)) iq1[k] = int(np.max(ii)) #--------------------------------------------------------------- # This is an iterator for each possible combination of segments # including segment orientations (i.e., flipped). #--------------------------------------------------------------- it_seg = it.product(it.combinations(uind, 2), it.product([0, 1], [0, 1])) # Placeholder for the segment pair/orientation that gives the # largest distance. dist_save = 0 for k in it_seg: s0ind = k[0][0] s1ind = k[0][1] p0ind = k[1][0] p1ind = k[1][1] if p0ind == 0: P0 = quadlist[iq0[s0ind]][0] else: P0 = quadlist[iq1[s0ind]][1] if p1ind == 0: P1 = quadlist[iq1[s1ind]][0] else: P1 = quadlist[iq0[s1ind]][1] dist = geodetic.distance(P0.longitude, P0.latitude, 0.0, P1.longitude, P1.latitude, 0.0) if dist > dist_save: dist_save = dist A0 = P0 A1 = P1 #--------------------------------------------------------------- # A0 and A1 are the furthest two segment endpoints, but we still # need to sort out which one is the "origin". #--------------------------------------------------------------- # Goofy while-loop is to adjust the side of the fault where the # origin is located dummy = -1 while dummy < 0: A0.depth = 0 A1.depth = 0 p_origin = Vector.fromPoint(A0) a0 = Vector.fromPoint(A0) a1 = Vector.fromPoint(A1) ahat = (a1 - a0).norm() # Loop over traces e_j = np.zeros(nseg) b_prime = [None] * nseg for j in range(nseg): P0 = quadlist[iq0[j]][0] P1 = quadlist[iq1[j]][1] P0.depth = 0 P1.depth = 0 p0 = Vector.fromPoint(P0) p1 = Vector.fromPoint(P1) b_prime[j] = p1 - p0 e_j[j] = ahat.dot(b_prime[j]) E = np.sum(e_j) # List of discordancy dc = [np.sign(a) * np.sign(E) for a in e_j] b = Vector(0, 0, 0) for j in range(nseg): b.x = b.x + b_prime[j].x * dc[j] b.y = b.y + b_prime[j].y * dc[j] b.z = b.z + b_prime[j].z * dc[j] bhat = b.norm() dummy = bhat.dot(ahat) if dummy < 0: tmpA0 = copy.copy(A0) tmpA1 = copy.copy(A1) A0 = tmpA1 A1 = tmpA0 # To fix discordancy, need to flip quads and rearrange # the order of quadgc2 # 1) flip quads for i in range(len(quadgc2)): if dc[segind[i]] < 0: #***************U*UUUUUUUSDFUSDfkjjhakjsdhfljkhn quadgc2[i] = reverse_quad(quadgc2[i]) # 2) rearrange quadlist to remove discordancy qind = np.arange(len(quadgc2)) segnp = np.array(segind) for i in range(nseg): qsel = qind[segnp == uind[i]] if dc[i] < 0: qrev = qsel[::-1] qind[segnp == uind[i]] = qrev quadgc2old = copy.deepcopy(quadgc2) for i in range(len(qind)): quadgc2[i] = quadgc2old[qind[i]] if quadlist is not None: # Length of prior segments s_i = 0.0 l_i = np.zeros(len(quadlist)) for i in range(len(quadlist)): P0, P1, P2, P3 = quadlist[i] G0, G1, G2, G3 = quadgc2[i] if 'rrup' in methods: rrupdist = _calc_rupture_distance(P0, P1, P2, P3, sites_ecef) minrrup = np.minimum(minrrup, rrupdist) if 'rjb' in methods: S0 = copy.deepcopy(P0) S1 = copy.deepcopy(P1) S2 = copy.deepcopy(P2) S3 = copy.deepcopy(P3) S0.depth = 0.0 S1.depth = 0.0 S2.depth = 0.0 S3.depth = 0.0 rjbdist = _calc_rupture_distance(S0, S1, S2, S3, sites_ecef) minrjb = np.minimum(minrjb, rjbdist) if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): # Rx, Ry, and Ry0 are all computed if one is requested since # they all require similar information for the weights. This # isn't necessary for a single segment fault though. # Note, we are basing these calculations on GC2 coordinates U # and T as described in: # Spudich and Chiou (2015) # http://dx.doi.org/10.3133/ofr20151028. # Compute u_i and t_i for this quad t_i = __calc_t_i(G0, G1, lat, lon, proj) u_i = __calc_u_i(G0, G1, lat, lon, proj) # Quad length l_i[i] = get_quad_length(quadlist[i]) # Weight of segment, three cases # Case 3: t_i == 0 and 0 <= u_i <= l_i w_i = np.zeros_like(t_i) # Case 1: ix = t_i != 0 w_i[ix] = (1.0 / t_i[ix]) * (np.arctan((l_i[i] - u_i[ix]) / t_i[ix]) - np.arctan(-u_i[ix] / t_i[ix])) # Case 2: ix = (t_i == 0) & ((u_i < 0) | (u_i > l_i[i])) w_i[ix] = 1 / (u_i[ix] - l_i[i]) - 1 / u_i[ix] totweight = totweight + w_i GC2T = GC2T + w_i * t_i if nseg == 1: GC2U = GC2U + w_i * (u_i + s_i) else: if i == 0: qind = np.array(range(len(quadlist))) l_kj = 0 s_ij_1 = 0 else: l_kj = l_i[(segindnp == segindnp[i]) & (qind < i)] s_ij_1 = np.sum(l_kj) p1 = Vector.fromPoint(quadgc2[iq0[segind[i]]][0]) s_ij_2 = (p1 - p_origin).dot(np.sign(E)*ahat) / 1000.0 # Above is GC2N, for GC2T use: # s_ij_2 = (p1 - p_origin).dot(bhat) / 1000.0 s_ij = s_ij_1 + s_ij_2 GC2U = GC2U + w_i * (u_i + s_ij) s_i = s_i + l_i[i] # Collect distances from loop into the distance dict if 'rjb' in methods: minrjb = minrjb.reshape(oldshape) distdict['rjb'] = minrjb if ('rx' in methods) or ('ry' in methods) or \ ('ry0' in methods) or ('U' in methods) or ('T' in methods): # Normalize by sum of quad weights GC2T = GC2T / totweight GC2U = GC2U / totweight distdict['T'] = copy.deepcopy(GC2T).reshape(oldshape) distdict['U'] = copy.deepcopy(GC2U).reshape(oldshape) # Take care of Rx Rx = copy.deepcopy(GC2T) # preserve sign (no absolute value) Rx = Rx.reshape(oldshape) distdict['rx'] = Rx # Ry Ry = GC2U - s_i / 2.0 Ry = Ry.reshape(oldshape) distdict['ry'] = Ry # Ry0 Ry0 = np.zeros_like(GC2U) ix = GC2U < 0 Ry0[ix] = np.abs(GC2U[ix]) if nseg > 1: s_i = s_ij + l_i[-1] ix = GC2U > s_i Ry0[ix] = GC2U[ix] - s_i Ry0 = Ry0.reshape(oldshape) distdict['ry0'] = Ry0 if 'rrup' in methods: minrrup = minrrup.reshape(oldshape) distdict['rrup'] = minrrup else: if 'rjb' in methods: if use_median_distance: warnings.warn( 'No fault; Replacing rjb with median rjb given M and repi.') cdir, tmp = os.path.split(__file__) # ------------------- # Sort out file names # ------------------- mech = source.getEventParam('mech') if not hasattr(source, '_tectonic_region'): rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv") elif source._tectonic_region == 'Active Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p7_seis0_20_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechR_ar1p7_seis0_20_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechN_ar1p7_seis0_20_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechSS_ar1p7_seis0_20_Var.csv") elif source._tectonic_region == 'Stable Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechA_ar1p0_seis0_15_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechR_ar1p0_seis0_15_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechN_ar1p0_seis0_15_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_S14_mechSS_ar1p0_seis0_15_Var.csv") else: warnings.warn( 'Unsupported tectonic region; using coefficients for unknown' 'tectonic region.') rf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rjb_WC94_mechA_ar1p0_seis0_20_Var.csv") # ----------------- # Start with ratios # ----------------- repi2rjb_ratios_tbl = pd.read_csv(rf, comment='#') r2rrt_cols = repi2rjb_ratios_tbl.columns[1:] mag_list = [] for column in (r2rrt_cols): if re.search('R\d+\.*\d*', column): magnitude = float(re.findall( 'R(\d+\.*\d*)', column)[0]) mag_list.append(magnitude) mag_list = np.array(mag_list) dist_list = np.log(np.array(repi2rjb_ratios_tbl['Repi_km'])) repi2rjb_grid = repi2rjb_ratios_tbl.values[:, 1:] repi2rjb_obj = spint.RectBivariateSpline( dist_list, mag_list, repi2rjb_grid, kx=1, ky=1) def repi2rjb_tbl(repi, M): ratio = repi2rjb_obj.ev(np.log(repi), M) rjb = repi * ratio return rjb repis = distdict['repi'] mags = np.ones_like(repis) * source.getEventParam('mag') rjb_hat = repi2rjb_tbl(repis, mags) distdict['rjb'] = rjb_hat # ------------------- # Additional Variance # ------------------- repi2rjbvar_ratios_tbl = pd.read_csv(vf, comment='#') repi2rjbvar_grid = repi2rjbvar_ratios_tbl.values[:, 1:] repi2rjbvar_obj = spint.RectBivariateSpline( dist_list, mag_list, repi2rjbvar_grid, kx=1, ky=1) rjbvar = repi2rjbvar_obj.ev(np.log(repis), mags) distdict['rjbvar'] = rjbvar else: warnings.warn('No fault; Replacing rjb with repi') distdict['rjb'] = distdict['repi'].copy() if 'rrup' in methods: if use_median_distance: warnings.warn( 'No fault; Replacing rrup with median rrup given M and repi.') cdir, tmp = os.path.split(__file__) # ------------------- # Sort out file names # ------------------- rake = source._event_dict.get('rake') mech = rake_to_mech(rake) if not hasattr(source, '_tectonic_region'): rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv") elif source._tectonic_region == 'Active Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p7_seis0-20_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechR_ar1p7_seis0-20_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechN_ar1p7_seis0-20_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechSS_ar1p7_seis0-20_Var.csv") elif source._tectonic_region == 'Stable Shallow Crust': if mech == 'ALL': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechA_ar1p0_seis0-15_Var.csv") elif mech == 'RS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechR_ar1p0_seis0-15_Var.csv") elif mech == 'NM': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechN_ar1p0_seis0-15_Var.csv") elif mech == 'SS': rf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_S14_mechSS_ar1p0_seis0-15_Var.csv") else: warnings.warn( 'Unsupported tectonic region; using coefficients for unknown' 'tectonic region.') rf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Ratios.csv") vf = os.path.join( cdir, "data", "ps2ff", "Rrup_WC94_mechA_ar1p0_seis0-20_Var.csv") # ----------------- # Start with ratios # ----------------- repi2rrup_ratios_tbl = pd.read_csv(rf, comment='#') r2rrt_cols = repi2rrup_ratios_tbl.columns[1:] mag_list = [] for column in (r2rrt_cols): if re.search('R\d+\.*\d*', column): magnitude = float(re.findall( 'R(\d+\.*\d*)', column)[0]) mag_list.append(magnitude) mag_list = np.array(mag_list) dist_list = np.log(np.array(repi2rrup_ratios_tbl['Repi_km'])) repi2rrup_grid = repi2rrup_ratios_tbl.values[:, 1:] repi2rrup_obj = spint.RectBivariateSpline( dist_list, mag_list, repi2rrup_grid, kx=1, ky=1) def repi2rrup_tbl(repi, M): ratio = repi2rrup_obj.ev(np.log(repi), M) rrup = repi * ratio return rrup repis = distdict['repi'] mags = np.ones_like(repis) * source.getEventParam('mag') rrup_hat = repi2rrup_tbl(repis, mags) distdict['rrup'] = rrup_hat # ------------------- # Additional Variance # ------------------- repi2rrupvar_ratios_tbl = pd.read_csv(vf, comment='#') repi2rrupvar_grid = repi2rrupvar_ratios_tbl.values[:, 1:] repi2rrupvar_obj = spint.RectBivariateSpline( dist_list, mag_list, repi2rrupvar_grid, kx=1, ky=1) rrupvar = repi2rrupvar_obj.ev(np.log(repis), mags) distdict['rrupvar'] = rrupvar else: warnings.warn('No fault; Replacing rrup with rhypo') distdict['rrup'] = distdict['rhypo'].copy() if 'rx' in methods: warnings.warn('No fault; Setting Rx to zero.') distdict['rx'] = np.zeros_like(distdict['repi']) if 'ry0' in methods: warnings.warn('No fault; Setting ry0 to zero') distdict['ry0'] = np.zeros_like(distdict['repi']) if 'ry' in methods: warnings.warn('No fault; Setting ry to zero') distdict['ry'] = np.zeros_like(distdict['repi']) if 'U' in methods: warnings.warn('No fault; Setting U to zero') distdict['U'] = np.zeros_like(distdict['repi']) if 'T' in methods: warnings.warn('No fault; Setting T to zero') distdict['T'] = np.zeros_like(distdict['repi']) return distdict
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.getQuadrilaterals()[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._lat slon = self._lon # 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._lat.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._lat.shape)