stress_inversion.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. """Обратный расчёт девиатора напряжений по механизмам очагов землетрясений.
  2. Линейная схема Майкла (Michael, 1984): касательное напряжение на плоскости
  3. разрыва, вычисленное для подбираемого девиатора, должно совпадать по
  4. направлению с наблюдаемым смещением в очаге (гипотеза Уоллеса--Ботта).
  5. Условие совпадения линейно по компонентам тензора, поэтому девиатор
  6. определяется методом наименьших квадратов.
  7. Единственная внешняя библиотека -- NumPy. Случайные числа и бутстреп
  8. не используются: при фиксированных входных данных результат единственный.
  9. Каталог событий и контрольные значения -- Barth & Wenzel (2010).
  10. """
  11. import numpy as np
  12. def mech(strike, dip, rake):
  13. """Единичные векторы нормали n и смещения s по простиранию, падению, скольжению."""
  14. p, d, l = np.radians([strike, dip, rake])
  15. n = np.array([-np.sin(d)*np.sin(p), np.sin(d)*np.cos(p), -np.cos(d)])
  16. s = np.array([np.cos(l)*np.cos(p) + np.sin(l)*np.cos(d)*np.sin(p),
  17. np.cos(l)*np.sin(p) - np.sin(l)*np.cos(d)*np.cos(p),
  18. -np.sin(l)*np.sin(d)])
  19. return n/np.linalg.norm(n), s/np.linalg.norm(s)
  20. def shear(T, n):
  21. """Касательное напряжение на плоскости с нормалью n: tau = T n - (n . T n) n."""
  22. tn = T @ n
  23. return tn - np.dot(n, tn)*n
  24. def beta_deg(T, n, s):
  25. """Невязка beta: угол между смещением s и направлением касательного напряжения."""
  26. tau = shear(T, n)
  27. return np.degrees(np.arccos(np.clip(np.dot(s, tau/np.linalg.norm(tau)), -1, 1)))
  28. def swap(ns):
  29. """Узловая пара в обратном порядке: (n, s) -> (s, n)."""
  30. return (ns[1], ns[0])
  31. # Ниже выписаны пять базисных тензоров; подстановка каждого в shear() даёт столбец системы МНК.
  32. BASIS = [
  33. np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]]), # компонента T11
  34. np.array([[0, 0, 0], [0, 1, 0], [0, 0, -1]]), # компонента T22
  35. np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), # компонента T12
  36. np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]), # компонента T13
  37. np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]), # компонента T23
  38. ]
  39. def tensor_from_components(t5):
  40. """Матрица 3 x 3 девиатора из пяти независимых компонент."""
  41. T11, T22, T12, T13, T23 = t5
  42. return np.array([[T11, T12, T13], [T12, T22, T23], [T13, T23, -T11 - T22]])
  43. def solve_lstsq(data):
  44. """МНК-решение системы shear(T, n) = s по совокупности пар (n, s)."""
  45. blocks = []
  46. b = []
  47. for n, s in data:
  48. columns = []
  49. for E in BASIS:
  50. columns.append(shear(E, n)) # реакция базисного тензора на площадке
  51. blocks.append(np.column_stack(columns)) # блок 3 x 5 для одного наблюдения
  52. b.append(s)
  53. solution = np.linalg.lstsq(np.vstack(blocks), np.concatenate(b), rcond=None)
  54. t5 = solution[0] # пять независимых компонент девиатора
  55. T = tensor_from_components(t5)
  56. dots = [] # проверка знака: сжатие должно быть > 0
  57. for n, s in data:
  58. dots.append(np.dot(s, shear(T, n)))
  59. if np.mean(dots) < 0:
  60. T = -T
  61. return T
  62. def azimuth_deg(u):
  63. """Азимут горизонтальной проекции оси, ° (0...180, отсчёт от севера)."""
  64. return np.degrees(np.arctan2(u[1], u[0])) % 180
  65. def plunge_deg(u):
  66. """Угол погружения оси, ° (0...90)."""
  67. return np.degrees(np.arcsin(abs(u[2])))
  68. def invert(events, max_iter=100):
  69. """Двухступенчатый обратный расчёт; возвращает тензор, невязки и параметры."""
  70. m = []
  71. for e in events:
  72. m.append(mech(e[0], e[1], e[2]))
  73. both = []
  74. for ns in m:
  75. both.append(ns) # первая узловая плоскость события
  76. both.append(swap(ns)) # вторая (перевёрнутая пара)
  77. T = solve_lstsq(both) # 1-я ступень: обе узловые плоскости
  78. choice_prev = None
  79. for _iteration in range(max_iter): # 2-я ступень: плоскость с меньшей невязкой
  80. choice = []
  81. for ns in m:
  82. b_first = beta_deg(T, ns[0], ns[1]) # невязка первой плоскости
  83. b_second = beta_deg(T, ns[1], ns[0]) # невязка второй плоскости
  84. if b_first <= b_second:
  85. choice.append(0)
  86. else:
  87. choice.append(1)
  88. if choice_prev is not None and choice == choice_prev:
  89. break # выбор плоскостей стабилизировался
  90. choice_prev = choice
  91. chosen = []
  92. for ns, c in zip(m, choice):
  93. if c == 0:
  94. chosen.append(ns)
  95. else:
  96. chosen.append(swap(ns))
  97. T = solve_lstsq(chosen)
  98. betas = [] # каждой паре -- меньшая из двух невязок
  99. for ns in m:
  100. betas.append(min(beta_deg(T, ns[0], ns[1]), beta_deg(T, ns[1], ns[0])))
  101. w, v = np.linalg.eigh(T) # собственное разложение девиатора
  102. lam_a, lam_b, lam_c = w[2], w[1], w[0]
  103. R = (lam_a - lam_b)/(lam_a - lam_c) # = (sigma2 - sigma3)/(sigma1 - sigma3)
  104. s1, s3 = v[:, 0], v[:, 2] # оси наибольшего сжатия и растяжения
  105. return T, betas, dict(SH=azimuth_deg(s1), R=R, mean_beta=np.mean(betas),
  106. sigma1=(azimuth_deg(s1), plunge_deg(s1)),
  107. sigma3=(azimuth_deg(s3), plunge_deg(s3)))
  108. def beta_event(T, event):
  109. """Невязка события относительно готового тензора (минимум по двум плоскостям)."""
  110. n, s = mech(event[0], event[1], event[2])
  111. return min(beta_deg(T, n, s), beta_deg(T, s, n))
  112. def misfit_with_event(events, event):
  113. """Невязка события относительно тензора, полученного с его участием.
  114. Так в публикации оценены невязки исключённых выбросов: тензор строится
  115. по выборке, содержащей проверяемое событие (обе его плоскости), затем для
  116. каждого механизма оставляется плоскость с меньшей невязкой и тензор
  117. перевычисляется один раз.
  118. """
  119. m = []
  120. for e in list(events) + [event]:
  121. m.append(mech(e[0], e[1], e[2]))
  122. both = []
  123. for ns in m:
  124. both.append(ns)
  125. both.append(swap(ns))
  126. T = solve_lstsq(both)
  127. chosen = []
  128. for ns in m:
  129. if beta_deg(T, ns[0], ns[1]) <= beta_deg(T, ns[1], ns[0]):
  130. chosen.append(ns)
  131. else:
  132. chosen.append(swap(ns))
  133. T = solve_lstsq(chosen)
  134. n, s = mech(event[0], event[1], event[2])
  135. return min(beta_deg(T, n, s), beta_deg(T, s, n))
  136. # Каталог механизмов Barth & Wenzel (2010), таблица 1:
  137. # номер: (широта, долгота, простирание, падение, скольжение)
  138. CATALOG = {
  139. 1:(53.5,112.2,148,47,3), 2:(47.9,130.7,33,81,-172), 3:(47.5,117.0,128,57,20),
  140. 4:(51.6,133.4,122,32,52), 5:(49.0,129.9,200,33,131), 6:(44.7,112.6,135,44,69),
  141. 7:(55.0,124.0,217,54,136), 8:(48.9,131.5,127,65,-5), 9:(48.9,131.2,9,71,-174),
  142. 10:(43.4,117.5,111,45,-15),11:(44.7,115.7,40,55,-164),12:(54.5,124.2,325,42,149),
  143. 13:(48.5,128.6,152,29,66), 14:(49.0,130.4,263,41,-1), 15:(49.4,119.4,125,54,17),
  144. 16:(49.0,130.0,115,50,14), 17:(47.4,116.3,207,43,177),18:(44.6,117.5,225,72,162),
  145. 19:(51.1,124.8,107,48,8), 20:(47.7,117.0,35,22,-144),21:(54.0,134.3,192,60,178),
  146. 22:(55.4,124.2,114,43,-21),23:(43.8,119.7,313,66,4), 24:(53.9,134.4,91,51,-11),
  147. 25:(43.4,120.2,212,65,-178),26:(53.2,128.9,97,65,5), 27:(45.4,118.3,157,27,68),
  148. 28:(54.4,125.5,120,53,-3), 29:(48.9,131.4,300,63,-8), 30:(51.8,122.6,207,57,-173),
  149. 31:(55.2,122.9,114,47,-17),32:(48.7,132.5,101,63,-3), 33:(48.5,131.8,191,39,127),
  150. 34:(46.9,124.9,154,47,63), 35:(51.8,116.3,127,73,5), 36:(44.6,124.2,156,32,48),
  151. 37:(43.5,119.6,298,69,-5), 38:(48.8,133.4,180,46,162),39:(53.6,132.4,132,56,24),
  152. 40:(49.2,122.4,198,57,162),41:(54.1,128.0,108,64,-1),
  153. 42:(44.7,126.9,318,48,-10),43:(47.9,130.6,104,74,-10),44:(43.8,125.1,42,64,156),
  154. 45:(51.1,135.3,188,39,32), 46:(54.3,126.5,25,80,-179),47:(54.4,125.4,247,35,-20),
  155. 48:(53.6,132.2,132,24,96), 49:(43.9,114.2,286,59,-17),50:(54.1,122.0,316,21,141),
  156. 51:(54.2,128.9,110,84,0), 52:(55.2,124.8,112,27,90), 53:(48.7,121.0,130,54,6),
  157. 54:(48.6,126.1,116,63,-12),
  158. }
  159. def mechanism(nr):
  160. """Механизм события №nr из каталога: (простирание, падение, скольжение)."""
  161. return CATALOG[nr][2:] # первые два числа -- координаты очага
  162. def mechanisms(numbers):
  163. """Механизмы событий с указанными номерами каталога."""
  164. result = []
  165. for nr in numbers:
  166. result.append(mechanism(nr))
  167. return result
  168. BIN3 = [2, 5, 8, 9, 13, 16, 29, 32, 33, 38, 43] # Буреинский блок
  169. BIN2 = [7, 12, 21, 22, 24, 26, 28, 31, 39, 41, 46, 48, 50, 51, 52] # Становой район
  170. EV2013 = (303, 75, -5) # C201307221508A
  171. if __name__ == '__main__':
  172. print('1. Буреинский блок, N = 11')
  173. print(' публикация: SH = 70.2, R = 0.40, средняя невязка = 12.2')
  174. T3, b3, o3 = invert(mechanisms(BIN3))
  175. print(f' расчёт: SH = {o3["SH"]:.1f}, R = {o3["R"]:.2f}, '
  176. f'средняя невязка = {o3["mean_beta"]:.1f}')
  177. print('2. Становой район, N = 15')
  178. print(' публикация: SH = 59.1, R = 0.47, средняя = 15.0, наибольшая < 32')
  179. T2, b2, o2 = invert(mechanisms(BIN2))
  180. print(f' расчёт: SH = {o2["SH"]:.1f}, R = {o2["R"]:.2f}, '
  181. f'средняя = {o2["mean_beta"]:.1f}, наибольшая = {max(b2):.1f}')
  182. print('3. Контрольные невязки; протокол публикации:')
  183. print(' тензор получен с участием проверяемого события')
  184. b47 = misfit_with_event(mechanisms(BIN2), mechanism(47))
  185. b45 = misfit_with_event(mechanisms(BIN2 + [4]), mechanism(45))
  186. print(f' событие 47 (исключено из становой выборки): '
  187. f'публикация 96, расчёт {b47:.1f}')
  188. print(f' событие 45 (становая выборка с переходной зоной B): '
  189. f'публикация 63, расчёт {b45:.1f}')
  190. print(f' событие 28 (в становой выборке): '
  191. f'публикация 9.1, расчёт {beta_event(T2, mechanism(28)):.1f}')
  192. print('4. Рабочая выборка: Буреинский блок + событие 2013 г., N = 12')
  193. T, b, o = invert(mechanisms(BIN3) + [EV2013])
  194. print(f' SH = {o["SH"]:.1f}, R = {o["R"]:.2f}, средняя = {o["mean_beta"]:.1f}, '
  195. f'наибольшая = {max(b):.1f}')
  196. print(f' невязка события 2013 г. относительно буреинского тензора: '
  197. f'{beta_event(T3, EV2013):.1f}')
  198. T2p, b2p, o2p = invert(mechanisms(BIN2) + [EV2013])
  199. print(f' относительно станового тензора: {beta_event(T2, EV2013):.1f}, '
  200. f'при включении в становую выборку: {b2p[-1]:.1f}')