Физическая программная археология

от автора

Предыстория

Очень давно не брал в руки шашек и почти ничего не писал на хабре. В основном активность в последние годы здесь ограничивается простановкой плюсов и минусов под статьями и комментариями. Поэтому коротко о себе. По образу жизни и работы я — физик‑теоретик, доцент кафедры теоретической же физики в Пермском классическом университете, и немного эфирно‑паяльный радиолюбитель. Поскольку физик‑теоретик — понятие очень абстрактное, размытое и растяжимое, заниматься по научной тематике доводилось различными задачами. Гидродинамика, нелинейные процессы, спектральный и вейвлет‑анализ, немного биомедицинских работ. Это всё немного пылится в прошлом, а сейчас основной темой стали спиновая динамика и всяческие волны намагниченности в твёрдых телах.

Давняя история магнетизма, ЯМР/ЭПР и спиновой динамики у нас здесь богатая (современность вот, увы, не очень). О ней напоминают, в частности, сборники «Радиоспектроскопия», выходившие в университете чуть реже, чем раз в год, начиная с 1962. И вот, при пролистывании одного из этих сборников, а именно за 1976 год, попалась на глаза статья наших уважаемых профессоров — Евгения Карловича Хеннера и его наставника, Ивана Григорьевича Шапошникова, — с неярким, но ёмким названием «Об одном численном методе статистической теории магнитного резонанса в твердых телах». Казалось бы, обычная заметка на 8 страниц об идее и особенностях реализации и тестирования расчётного метода. Но почти половину её объёма составил полный листинг для одной из реализаций языка АЛГОЛ-60, который в Перми, скорее всего, крутился на М-220 в нашем вычислительном центре (да простят меня мехматовцы, если в эту строку вкралась фактическая ошибка). Как это выглядит на бумаге, показано на фотографии. Для экономии места ликвидировано всё возможное форматирование, но, хвала точкам с запятыми и коротким комментариям, код вполне читаем и более‑менее структурирован.

Свитки программистов прошлого. Кусочек листинга как он есть. Карандашные пометки поверх уже мои.

Свитки программистов прошлого. Кусочек листинга как он есть. Карандашные пометки поверх уже мои.

Немножко квантовой магии

Коротко, что же этот «один численный метод» делает. Иван Григорьевич в 1994 г. собственноручно составил список из ста, на тот момент, начиная с 1936 г., своих публикаций, к каждой из которых дал краткую и ёмкую аннотацию. Оригинал списка хранится у нас на кафедре среди других артефактов. Да, было время, когда не количеством публикаций и цитируемостью мерялись учёные. Конкретно к описываемой здесь статье аннотация такова:

С целью изучения некоторых вопросов магнитного резонанса в магниторазведённых кристаллах, предложен и использован численный метод точного учёта многочастичных взаимодействий (без привлечения теории возмущений) между близко расположенными магнитными ионами, дополненный статистическим усреднением по случайной выборке из полной совокупности возможных для данной концентрации распределений магнитных ионов по решётке.

Что ж, давайте пройдёмся по пунктам.

Моделирование магнитного резонанса в магниторазведëнном кристалле. Или, как принято говорить в современных текстах, в магниторазбавленном. Это обширный пласт материалов, хотя в большинстве статей упоминается только арсенид галлия, допированный марганцем, Mnx: Ga(1-x) As, где доля Mn x — процентов 5–8. Это вроде и полупроводник, но очень хорошо чувствующий магнитное поле. Именно это и интересно. Или, прости господи, графен с сидящими на нём атомами чего‑то более магнитного, чем углерод. Тут получается примерно то же самое, что и с арсенидом галлия, с поправкой на необычное поведение электронов.

Примерно так может выглядеть сигнал в эксперименте. Только колебания происходят намного быстрее - десятки и сотни МГц и даже выше. Типичное время спада - десятки мкс

Примерно так может выглядеть сигнал в эксперименте. Только колебания происходят намного быстрее — десятки и сотни МГц и даже выше. Типичное время спада — десятки мкс

Как нам залезть в этот материал и узнать его структуру и энергетический спектр? Много способов. Один из них — это магнитный резонанс. Помещаем образец в сильное постоянное поле, и каждый магнитный центр начинает крутиться со своей собственной частотой, определяемой его окружением. «Стукаем» импульсом перпендикулярного поля или их серией, магнитные моменты частично «падают набок» и начинают возвращаться к исходной прецессии. Тут‑то мы их и ловим — записывается сигнал намагниченности во времени, откуда находится спектр магнитного резонанса и сопутствующие характеристики. Выглядят они примерно как на картинке. Вообще, спектр можно и напрямую измерить, просто плавно меняя параметры полей и регистрируя поглощение энергии образцом. Исторически это был первый метод, но он, как минимум, менее точный и более долгий.

И всё бы ничего, и даже теорию возмущений для свойств материала можно написать, но случайное распределение магнитных центров — в данном случае примеси марганца — всё портит. Где‑то его много, где‑то поменьше получилось при синтезе, а от окружения плывёт и спектр энергии, и возможные состояния. Вместо упорядоченной структуры у нас месиво кластеров из 2, 3, 4… и т. д. магнитных центров, и все кластеры имеют случайный размер и форму. В результате уровни энергии всего кристалла размазаны в почти что энергетические зоны, а резонансы совершенно не острые. Именно эти данные и генерирует алгоритм, моделируя спектр сигнала от разбавленного материала.

Как это происходит? Создаётся случайный кластер атомов, рассчитываются его свойства, записываются. Затем ещё кластер, ещё, ещё и пока не наберётся приемлемая статистика, и наконец вычисляется осреднëнный спектр магнитного резонанса образца, составленного из этих самых кластеров в предположении, что друг друга они не замечают. Для низких концентраций примеси это отлично работает. Весь расчёт выполняется из первых принципов — честно строится оператор (матрица) гамильтониана, итерационно, методом вращений, вычисляются её собственные значения — уровни энергии кластера, и вероятности переходов между ними под действием поперечного поля. Это весьма ресурсозатратно — если частицы имеют спин 1/2, и кластер содержит N частиц, то приходится строить и диагонализовать матрицу размерами 2N×2N, которая вдобавок ещё и не сильно разрежена. Но в результате программа отрабатывает крайне быстро, 100 случайных конфигураций с 8 частицами от силы за полминуты.

От древнего языка… к древнему языку

Шёл август 2018 года. Время было тихое. Очередная заявка на грант была подана и ждала подведения итогов, статья в ЖЭТФ принята, учебный год ещё не начался, отпуск не закончился, а другие глобальные задачи не очень-то клеились. Чем ещё заниматься учёному-преподавателю при таком раскладе, если не пить? Мгновенно возникло настойчивое желание вдохнуть жизнь в код, напечатанный на тот момент 42 (о, эта магия чисел) года назад. И программа медленно, оператор за оператором, начала воплощаться в окне Visual Studio.

Наверное, спросите, на каком же языке. Яблоко от яблони недалеко падает, а яблоня от яблока — тем более. На тот момент я уверенно работал только с FORTRAN-90, которому у нас долгое время учили на курсе численных методов, и, чего греха таить, научили! Игорь Геннадьевич Семакин гонял всех нещадно, особенно доставалось как раз теоретикам. Думаю, что не зря.

Программа в целом, в том числе в обозначениях переменных, соответствует оригиналу. Немного использованы встроенные средства работы с массивами вместо явного прописывания поэлементной обработки. Вообще, в оригинале весь код написан без особых изысков, практически в лоб, в отличие от нынешних рекомендаций/традиций/правил/стандартов (нужное подчеркнуть). Все переменные — глобальные, и все те несколько процедур, что написаны вместо блоков с метками, не имеют формальных параметров. В исходнике, напомню, их нет, а вместо подпрограмм — вынесенные блоки кода под метками, переход на которые происходит по goto, но по выполнению условия. Эдакий условно‑безусловный переход.

Ну и здесь интенсивно задействована динамическая память — в оригинале предполагалось, что массивы статичны, а их размеры и количество (при вычислении произведения Кронекера) меняются по необходимости прямо в коде (т.е. на перфокартах). Здесь же в процедуре p34 видно, как динамические массивы постоянно стираются и выделяются заново — это происходит формирование полного размера нужных матриц.

Вот оно, что получилось — под спойлером.

.f90
program NMR_rarefied implicit none real(8), dimension(:,:,:), allocatable :: dip, a1 ! dipolar interaction and additional real(8), dimension(:,:), allocatable :: sx, sy, sz, sI, sp, sm, o, u, v, w, & ! spin matrices and additional                                         x, y, z, r, & ! r = {x, y, z}, distances between magnetic ions (MI)                                         J0, & ! exchange interactions                                         ReH, ImH, b, q, q2, & ! Hamiltonian parts and additional                                         d0, d1 ! for Kronecker product procedure real(8), dimension(:), allocatable :: x1, y1, z1, & ! coordinates of MI                                       RT2, RT3, RT4, RT1, sum_sqr, & ! for rotation method                                       histp, hist ! partial and averaged histogram logical, dimension(:), allocatable :: mask1 real(8), dimension(1:3) :: e1, e2, e3, d ! lattice basis real(8), dimension(1) :: i11, k11 real(8) S, eps, & ! spin, precision         alpha, beta, pd, & ! parameters of exchange (A*exp(-B*r)) and dipolar interaction (pd = (hbar*gamma)^2)         res, & ! histogram resolution         s1, s2, c1, c2, & ! rotation parameters         m, e, c integer N, N1, N2, N3, NN, Nres, & ! total number of MI and their concentration         i, j, k, i1, j1, k1, t, m3, ms, l, r1, ires, & ! counters         f, fn, & ! 2S+1, (2S+1)^N         nh ! number of histogram bars logical noconv          !---------------------------------------------------- ! read and set the initial parameters !---------------------------------------------------- open(1, file="init.txt", form="formatted", action="read") read(1,*) eps, S, N, N1, N2, N3, nh, alpha, beta, pd, &           e1, e2, e3, res, Nres close(1)  f = 2*S + 1; fn = f**N call RANDOM_SEED()  !---------------------------------------------------- ! memory alloc !---------------------------------------------------- allocate(sx(1:f,1:f)); allocate(sy(1:f,1:f)); allocate(sz(1:f,1:f)) allocate(sI(1:f,1:f)); allocate(sp(1:f,1:f)); allocate(sm(1:f,1:f)) allocate(o(1:f,1:f)); allocate(v(1:f,1:f)); allocate(w(1:f,1:f)) allocate(dip(1:6,1:N,1:N)); allocate(J0(1:N,1:N)); allocate(u(1:N,1:N)) allocate(x(1:N,1:N)); allocate(y(1:N,1:N)); allocate(z(1:N,1:N)) allocate(r(1:N,1:N)) allocate(x1(1:N)); allocate(y1(1:N)); allocate(z1(1:N)) allocate(ReH(1:fn,1:fn)); allocate(ImH(1:fn,1:fn)) allocate(b(1:fn,1:fn)); allocate(q(1:fn,1:fn)); allocate(q2(1:fn,1:fn)) allocate(mask1(1:fn)) allocate(a1(1:N,1:f,1:f)) allocate(RT1(1:fn)); allocate(RT2(1:fn)) allocate(RT3(1:fn)); allocate(RT4(1:fn)) allocate(sum_sqr(1:fn)) allocate(histp(1:nh)); allocate(hist(1:nh))  hist = 0.0d0  open(1, file="MI_coords.txt", form="formatted", action="write") open(2,file="E.txt", form="formatted", action="write")  !---------------------------------------------------- ! spin matrices initialization !---------------------------------------------------- sx = 0.0d0; sy = 0.0d0; sz = 0.0d0; sp = 0.0d0; sm = 0.0d0; sI = 0.0d0 do i = 1, f, 1   sI(i,i) = 1.0d0   sz(i,i) = S - (i-1)   if(i <= f-1) sp(i,i+1) = dsqrt(dabs(S*(S+1.0d0) - real(i*(i+1)))) enddo sm = transpose(sp) sx = 0.5d0*(sp + sm) sy = 0.5d0*(sm - sp)  !---------------------------------------------------- ires = 1 REALIZATION: do while(ires <= Nres) !---------------------------------------------------- ! generation of MI distribution in lattice !----------------------------------------------------   x = 0.0d0; y = 0.0d0; z = 0.0d0; r = 0.0d0   COORDS: do   ! MI coordinates     do i = 1, N, 1       call RANDOM_NUMBER(d)       d(1) = floor(d(1)*N1); d(2) = floor(d(2)*N2); d(3) = floor(d(3)*N3)       x1(i) = d(1)*e1(1) + d(2)*e2(1) + d(3)*e3(1)       y1(i) = d(1)*e1(2) + d(2)*e2(2) + d(3)*e3(2)       z1(i) = d(1)*e1(3) + d(2)*e2(3) + d(3)*e3(3)     enddo      ! MI distances     do i = 1, N, 1       do j = 1, N, 1         if(i /= j)then           x(i,j) = x1(i) - x1(j)           y(i,j) = y1(i) - y1(j)           z(i,j) = z1(i) - z1(j)           ! full interaction           r(i,j) = dsqrt( x(i,j)**2 + y(i,j)**2 + z(i,j)**2 )         endif       enddo       r(i,i) = 1.0d0     enddo      if(minval(r) > 0.5d0)exit   enddo COORDS    ! write MI coordinated   write(1,'(A,I8)') "Realization ", ires   do i = 1, N, 1     write(1,'(I6, 3E20.6E3)') i, x1(i), y1(i), z1(i)   enddo   write(1,*)    !---------------------------------------------------- ! pair interaction evaluation !----------------------------------------------------   ! isotropic part   J0 = alpha*dexp(-beta*r)   dip(1,:,:) = J0 + pd * (1.0d0 - 3.0d0*x**2 / r**2) / r**3   dip(2,:,:) = J0 + pd * (1.0d0 - 3.0d0*y**2 / r**2) / r**3   dip(3,:,:) = J0 + pd * (1.0d0 - 3.0d0*z**2 / r**2) / r**3   ! anisotropic part   dip(4,:,:) = -3.0d0*pd*x*y / r**5   dip(5,:,:) = -3.0d0*pd*x*z / r**5   dip(6,:,:) = -3.0d0*pd*y*z / r**5  !---------------------------------------------------- ! real part of the Hamiltonian matrix !----------------------------------------------------   o = sz; call p78()   ReH = b   u = dip(1,:,:); v = sx; w = sx; call p56()   u = dip(2,:,:); v = sy; w = -sy; call p56()   u = dip(3,:,:); v = sz; w = sz; call p56()   u = dip(5,:,:); v = sx; w = sz; call p56()   q = ReH; ReH = 0.0d0  !---------------------------------------------------- ! imaginary part of the Hamiltonian matrix !----------------------------------------------------   u = dip(4,:,:); v = sx; w = sy; call p56()   u = dip(6,:,:); v = sy; w = sz; call p56()   ImH = ReH; ReH = q  !---------------------------------------------------- ! Diagonalization of the Hamiltonian matrix !----------------------------------------------------   t = 0; q = 0.0d0; q2 = 0.0d0   do i = 1, fn, 1     q(i,i) = 1.0d0   enddo    ! sum of squares by strings   sum_sqr = 0.0d0   do i = 1, fn, 1     do j = 1, fn, 1       if(i /= j) sum_sqr(i) = sum_sqr(i) + (ReH(i,j)**2 + ImH(i,j)**2)     enddo   enddo   noconv = .false.      ! rotation iterations   ROTATION: do     i11 = maxloc(sum_sqr); i1 = i11(1)     mask1 = .true.; mask1(i1) = .false.     k11 = maxloc(ReH(i1,:)**2 + ImH(i1,:)**2, mask=mask1); k1 = k11(1)     m = maxval(ReH(i1,:)**2 + ImH(i1,:)**2, mask=mask1)        if(m < eps)exit      if(t == 100000)then       noconv = .true.       exit     endif      mask1(k1) = .false.     sum_sqr = sum_sqr - sum(ReH(:,i1)**2 + ImH(:,i1)**2, mask=mask1)          i = i1; k = k1        ! rotation parameters     e = dabs(ReH(i,i) - ReH(k,k)) / dsqrt( (ReH(i,i) - ReH(k,k))**2 + 4.0d0*(ReH(i,k)**2 + ImH(i,k)**2) )     s1 = dsqrt( 0.5d0*(1.0d0 - e) ); c1 = dsqrt( 0.5d0*(1.0d0 + e) )     if(ReH(i,i) /= ReH(k,k)) s1 = sign(s1, ReH(i,i) - ReH(k,k))     if(ReH(i,k) /= 0.0d0) s1 = sign(s1, ReH(i,k))        s2 = ImH(i,k) / dsqrt(ReH(i,k)**2 + ImH(i,k)**2)     if(ReH(i,k) /= 0.0d0) s2 = sign(s2, ReH(i,k))        c2 = s1 * dabs(ReH(i,k)) / dsqrt(ReH(i,k)**2 + ImH(i,k)**2)     s1 = s1*s2        ! make the rotations     RT1 = c1*q(:,i) + c2*q(:,k) + s1*q2(:,k)     RT2 = -c2*q(:,i) + c1*q(:,k) + s1*q2(:,i)     RT3 = -s1*q(:,k) + c1*q2(:,i) + c2*q2(:,k)     RT4 = -s1*q(:,i) - c2*q2(:,i) + c1*q2(:,k)          q(:,i) = RT1; q(:,k) = RT2; q2(:,i) = RT3; q2(:,k) = RT4      RT1 = c1*ReH(:,i) + c2*ReH(:,k) + s1*ImH(:,k)     RT2 = -c2*ReH(:,i) + c1*ReH(:,k) + s1*ImH(:,i)     RT3 = -s1*ReH(:,k) + c1*ImH(:,i) + c2*ImH(:,k)     RT4 = -s1*ReH(:,i) - c2*ImH(:,i) + c1*ImH(:,k)      ReH(:,i) = RT1; ReH(:,k) = RT2; ImH(:,i) = RT3; ImH(:,k) = RT4      RT1 = c1*ReH(i,:) + c2*ReH(k,:) - s1*ImH(k,:)     RT2 = -c2*ReH(i,:) + c1*ReH(k,:) - s1*ImH(i,:)     RT3 = c1*ImH(i,:) + c2*ImH(k,:) + s1*ReH(k,:)     RT4 = -c2*ImH(i,:) + c1*ImH(k,:) + s1*ReH(i,:)      ReH(i,:) = RT1; ReH(k,:) = RT2; ImH(i,:) = RT3; ImH(k,:) = RT4          t = t+1        mask1 = .true.; mask1(i1) = .false.; mask1(k1) = .false.     sum_sqr = sum_sqr + sum(ReH(:,i1)**2 + ImH(:,i1)**2, mask = mask1) &               + sum(ReH(:,k1)**2 + ImH(:,k1)**2, mask = mask1)         mask1 = .true.; mask1(i1) = .false.     sum_sqr(i1) = sum(ReH(i1,:)**2 + ImH(i1,:)**2, mask = mask1)        mask1 = .true.; mask1(k1) = .false.     sum_sqr(k1) = sum(ReH(k1,:)**2 + ImH(k1,:)**2, mask = mask1)   enddo ROTATION  !----------------------------------------------------   if(noconv)then     print *, "Realization does not converge"   else     print *,ires, ", Rotation finished at ", t, " iterations" !---------------------------------------------------- ! eigenvalues !----------------------------------------------------     do i = 1, fn, 1       RT1(i) = ReH(i,i)     enddo      ! write energy     write(2,'(A,I8)') "Realization ", ires     do i = 1, fn, 1       write(2,'(I6,E20.6E3)') i, RT1(i)     enddo     write(2,*)  !---------------------------------------------------- ! evaluate probabilities and histogram amplitudes !----------------------------------------------------     histp = 0.0d0     o = sx      call p78()      ReH = matmul(b, q); ImH = matmul(b, q2)     d1 = transpose(q); b = transpose(q2)      q = matmul(d1, ReH) + matmul(b, ImH)     q2 = matmul(d1, ImH) - matmul(b, ReH)      do i = 1, fn, 1       do j = 1, fn, 1         c = q(i,j)**2 + q2(i,j)**2         k = floor(1.0d0 + dabs(RT1(i) - RT1(j)) / res)         if(k <= nh) histp(k) = histp(k) + c       enddo     enddo      hist = hist + histp     ires = ires + 1     endif enddo REALIZATION !----------------------------------------------------  open(3,file="hist.txt", form="formatted", action="write") do i = 1, nh, 1   write(3,'(2E20.6E3)') i*res, hist(i) / Nres enddo close(1); close(2); close(3)  print *,"Histogram is written at hist.txt"  !---------------------------------------------------- ! memory clear !---------------------------------------------------- deallocate(sx); deallocate(sy); deallocate(sz); deallocate(sI) deallocate(o); deallocate(v); deallocate(w) deallocate(ReH); deallocate(ImH); deallocate(b); deallocate(q); deallocate(q2) deallocate(mask1); deallocate(a1) deallocate(RT1); deallocate(RT2); deallocate(RT3); deallocate(RT4); deallocate(sum_sqr) deallocate(histp); deallocate(hist) deallocate(dip); deallocate(J0); deallocate(sp); deallocate(sm) deallocate(x1); deallocate(y1); deallocate(z1) deallocate(x); deallocate(y); deallocate(z); deallocate(r); deallocate(d1)  contains !---------------------------------------------------- ! Kronecker product of one spin and N-1 identity !---------------------------------------------------- subroutine p78()   b = 0.0d0    do k = 1, N, 1     a1(k,:,:) = sI   enddo      do k = 1, N, 1     a1(k,:,:) = o     call p34()     a1(k,:,:) = sI     b = b + d1   enddo end subroutine p78  !---------------------------------------------------- ! Kronecker product of two spin and N-2 identity !---------------------------------------------------- subroutine p56()   do k = 1, N, 1     do t = 1, N, 1       if(k /= t)then         a1(k,:,:) = v; a1(t,:,:) = w       endif              call p34()                    a1(k,:,:) = sI; a1(t,:,:) = sI       ReH = ReH + u(k,t)*d1     enddo   enddo end subroutine p56  !---------------------------------------------------- ! Kronecker product of N matrices !---------------------------------------------------- subroutine p34()   if(allocated(d0)) deallocate(d0)   if(allocated(d1)) deallocate(d1)      allocate(d0(1:f,1:f)); d0 = 0.0d0   allocate(d1(1:f**2,1:f**2)); d1 = 0.0d0      d0 = a1(1,:,:)      do m3 = 2, N, 1     NN = f**(m3-1)     do i = 1, NN, 1       do j = 1, NN, 1         do ms = 1, f, 1           do l = 1, f, 1             r1 = (i-1)*f + ms; i1 = (j-1)*f + l             d1(r1,i1) = d0(i,j)*a1(m3,ms,l)           enddo         enddo       enddo     enddo          if(m3 < N)then       deallocate(d0); allocate(d0(1:f**m3,1:f**m3)); d0 = 0.0d0       d0 = d1       deallocate(d1); allocate(d1(1:f**(m3+1),1:f**(m3+1))); d1 = 0.0d0     endif        enddo   deallocate(d0) end subroutine p34  end program NMR_rarefied

Простите, без подсветки, хабраредактор всё-таки современен

Пройдёт ещё лет десять, и уже этот фортрановский файл можно будет тоже записывать в археологию. А может, уже пора. Но тем не менее, компилируем, запускаем:

Оно живое!

Оно живое!

И строим из выведенной таблички вполне приличный модельный спектр:

Резонансный пик вблизи единичной безразмерной частоты с мелкой рябью вокруг

Резонансный пик вблизи единичной безразмерной частоты с мелкой рябью вокруг

Итоги

Не надо конечно думать, что эта именно программа активно используется. Хотя и правда, мы поразбирались с её результатами и попытались адаптировать для конкретных задач, не без помощи вышеупомянутого автора Е.К. Хеннера. Программа жива, работоспособна и действительно позволяет симулировать магниторезонансные эксперименты. А размер моделируемого кластера частиц на каждой случайной реализации упирается только в объём доступной для размещения массивов памяти.

Для массовых рабочих станций, которыми мы сегодня у себя пользуемся, при 16 Гб RAM это где‑то в районе 12–14 частиц со спином 1/2, в зависимости от загрузки системы другими процессами. Немного, но если накопить статистику по реализациям, получается вполне достоверно. Таких работ относительно немного, но наша группа не одна на этом поприще, к счастью, есть с кем сравниваться в плане разработки и реализации методов. Есть, конечно, подходы и пакеты куда более серьёзные. Например, несравненный Spinach, на котором можно замоделировать, похоже, вообще всё что угодно, вплоть до полноразмерных белковых структур. Если, конечно, есть под рукой суперкомпьютер с GPU — гильбертово пространство квантовой системы от выбора алгоритма моделирования меньше не становится.

Сегодня этот же, по сути, метод частично живёт в виде кода на Python с NumPy и LAPACK, и потихоньку плодит с нашей помощью статьи и дипломы студентов. Чуть более гибко, чуть более просто писать, не думая о внутренней сущности отдельных команд и просто зная, что раз написано, например,np.kron— значит это точно отлаженное и надёжное кронекеровское перемножение матриц, а огромный кусок с методом вращений заменён на единственную строку с вызовомnp.linalg.eigh. По скорости же обе программы, если в Python сейчас оставить только функционал, соответствующий старому исходнику на АЛГОЛ, оказываются абсолютно сопоставимы.

А вот оживить программу, написанную полвека назад и сохранившуюся на пожелтевших страницах — по‑моему, это просто красиво!

Литература
Хеннер Е. К., Шапошников И. Г. Об одном численном методе статистической теории магнитного резонанса в твёрдых телах // Сб. Радиоспектроскопия – ПГУ, Пермь, 1976. – № 10. – С. 74–81.


ссылка на оригинал статьи https://habr.com/ru/articles/737624/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *