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