Определитель в Fortran95

Этот код на фортране вычисляет определитель матрицы размера nxn по формуле Лапласиана (разложение на миноры). Я прекрасно понимаю, как работает этот процесс.

Но может ли кто-нибудь дать мне представление о том, как следующий код работает, скажем, с данной итерацией, этот раздел кода содержит определитель рекурсивной функции (матрица) - предположим, что некоторая матрица nxn считывается и передается, а другая функция для вызова кофактор. Есть аспекты кода, которые я понимаю, но рекурсия меня глубоко смущает. Я пытался пошагово пройтись по матрице 3x3, но безрезультатно.

! Expansion of determinants using Laplace formula
recursive function determinant(matrix) result(laplace_det)
real, dimension(:,:) :: matrix
integer :: msize(2), i, n
real :: laplace_det, det
real, dimension(:,:), allocatable :: cf

msize = shape(matrix) 
n = msize(1)          

if (n .eq. 1) then  
  det = matrix(1,1)
else
  det = 0    
  do i=1, n  
    allocate(cf(n-1, n-1))     
    cf = cofactor(matrix, i, 1)
    det = det + ((-1)**(i+1))* matrix(i,1) * determinant(cf)
    deallocate(cf)
  end do        
end if
laplace_det = det
end function determinant       

 function cofactor(matrix, mI, mJ)
  real, dimension(:,:) :: matrix
  integer :: mI, mJ
  integer :: msize(2), i, j, k, l, n
  real, dimension(:,:), allocatable :: cofactor
  msize = shape(matrix)
  n = msize(1)

  allocate(cofactor(n-1, n-1))
  l=0
  k = 1
  do i=1, n
   if (i .ne. mI) then
     l = 1
     do j=1, n
       if (j .ne. mJ) then
         cofactor(k,l) = matrix(i,j)
         l = l+ 1
       end if
     end do
     k = k+ 1
   end if
  end do
return
end function cofactor

Основной раздел, с которым я борюсь, - это эти два вызова и операция вычисления соответствующего кофактора.

cf = cofactor(matrix, i, 1)
det = det + ((-1)**(i+1))* matrix(i,1) * determinant(cf)

Будем очень признательны за любой вклад в объяснение (например, я сказал пример одной итерации). Это мой первый пост в stack-overflow, так как большая часть моего вопроса находится в mathstack (как вы, вероятно, можете сказать по математической природе вопроса). Хотя у меня есть опыт программирования, концепция рекурсии (особенно в этом примере) действительно ошеломляет меня.

Если требуются какие-либо изменения, пожалуйста, продолжайте, я не знаком с этикетом при переполнении стека.


person pi-e    schedule 12.03.2016    source источник
comment
Может быть полезно добавить общий тег fortran. Кроме того, мне интересно, что из следующих двух является источником трудностей/путаницы: (1) обработка выделяемых массивов в Фортране, (2) базовое поведение функций рекурсии в Фортране (как показано Fibonacci).   -  person roygvib    schedule 12.03.2016
comment
Я понимаю использование распределяемых массивов в Фортране, насколько мне известно, они по существу позволяют динамически назначать размер массива. Хотя я не вижу (хотя это явно работает), как рекурсия determinant(cf) дает нам правильное значение на каждой рекурсивной итерации. Я пытался следовать коду на ручке и бумаге только для одной итерации, скажем, i=1 в recursive function determinant (matrix), но я просто теряюсь в функции кофактора.   -  person pi-e    schedule 13.03.2016
comment
Я думаю, что функция cofactor() строит подмассив из заданного массива, удаляя mI-ю строку и mJ-й столбец переданной матрицы, поэтому cf — это массив 5x5, если matrix — это массив 6x6, например. Полученный cf затем передается в determinant() как determinant(cf), который будет оцениваться заново (т. е. независимо от текущего вызова determinant()). Так что это что-то вроде вызова все большего количества независимых функций с одним и тем же именем. (Извините, если мое объяснение плохое...)   -  person roygvib    schedule 13.03.2016
comment
Хм, я поговорю об этом с профессором в понедельник, то, что вы сказали, имеет смысл, и я следил за этим до этого момента, но работа не проверяется (плохо перепроверяю). Также мне было бы интересно узнать, почему меня заминусовали (от 1 до 0). Я бы предпочел конструктивную критику, чтобы не повторять ту же ошибку. В своем первоначальном посте я указал, что это была моя первая запись о переполнении стека. Несмотря на это, спасибо за вашу помощь, roygvib, это несколько улучшило мое понимание проблемы.   -  person pi-e    schedule 13.03.2016
comment
Я провел несколько тестов с использованием небольших матриц, и кажется, что код работает нормально (хотя двойная точность, вероятно, была бы лучше для большей точности...). Кроме того, вы можете найти дополнительную информацию, погуглив детерминантную рекурсию (например, C, Python и Fortran ‹= Последнее кажется проще, чем ваш код.) Если что-то непонятно, вернитесь.   -  person roygvib    schedule 13.03.2016
comment
Что касается отрицательных голосов, я не думаю, что нужно так сильно беспокоиться, потому что иногда это просто подразумевает не по теме. Например, вопрос о численных алгоритмах лучше подходит для вычислительных наук. И еще один комментарий: рекурсивный алгоритм очень неэффективен для больших матриц (O(N!) по сравнению с O(N^3) с помощью факторизации LU и т. д.? Пожалуйста, проверьте Wiki.)   -  person roygvib    schedule 13.03.2016
comment
Мне тоже это кажется намного проще, хотя я бы очень хотел, чтобы вы объяснили мне этот раздел кода b(:, :(i-1)) = a(2:, :i-1) b(:, i:) = a(2:, i+1:). Я заметил, что рекурсивная функция начинается с определения b как размера (n-1)(n-1), очевидно, мы хотим найти определитель этого минора с учетом матрицы nxn. Также этот код может найти определитель для любой матрицы nxn? Большое спасибо за вашу помощь еще раз roygvib.   -  person pi-e    schedule 13.03.2016
comment
Также я знаю о сложности алгоритма, мне нужно будет проанализировать, что после того, как я закончу понимать этот код, я знаю этот метод O (N!), Но все равно спасибо, и за вашу точку зрения на отрицательные голоса я плохо приму это к сведению, я, вероятно, мог бы даже разместил это в mathstack, учитывая, что fortran используется для вычислительной математики или больших числовых процедур.   -  person pi-e    schedule 13.03.2016


Ответы (1)


Предположим, что мы передаем следующую матрицу 3x3 в determinant():

2 9 4
7 5 3
6 1 8

В подпрограмме следующие две строки выполняются итеративно для i = 1,2,3:

cf = cofactor(matrix, i, 1)
det = det + ((-1)**(i+1))* matrix(i,1) * determinant(cf)

что соответствует разложению Лапласа по первому столбцу. Более конкретно, можно передать вышеуказанное 3x3 matrix в cofactor(), чтобы получить подматрицу 2x2, удалив i-ю строку и 1-й столбец matrix. Полученная подматрица 2x2 (cf) затем передается в determinant() в следующей строке для вычисления кофактора, соответствующего этой подматрице. Итак, в этих первых итерациях мы пытаемся вычислить

введите здесь описание изображения

Обратите внимание, что три определителя в правой части еще предстоит вычислить последующими вызовами determinant(). Рассмотрим один такой последующий вызов, например. для i=1. Мы передаем следующую подматрицу (хранится в cf)

5 3
1 8

до determinant(). Затем та же процедура, что описана выше, повторяется снова и независимо от разложения Лапласа для исходной матрицы 3x3. То есть определитель() теперь перебирает i=1,2 и пытается вычислить

введите здесь описание изображения

Обратите внимание, что i в этом последующем вызове отличается от i предыдущего вызова; все они являются локальными переменными, живущими внутри конкретного вызова подпрограммы и полностью независимыми друг от друга. Также обратите внимание, что индекс аргумента фиктивного массива (например, matrix(:,:)) всегда начинается с 1 в Фортране (если не указано иное). Такого рода операции повторяются до тех пор, пока размер подматрицы не станет 1.

Но на практике я считаю, что самый простой способ понять такой код — написать промежуточные данные и отследить фактический поток данных/подпрограмм. Например, мы можем вставить множество операторов print как

module mymod
    implicit none
contains

recursive function determinant(matrix) result(laplace_det)
    real :: matrix(:,:)
    integer :: i, n, p, q
    real :: laplace_det, det
    real, allocatable :: cf(:,:)

    n = size(matrix, 1) 

    !***** output *****   
    print "(a)", "Entering determinant() with matrix = "
    do p = 1, n
        print "(4x,100(f3.1,x))", ( matrix( p, q ), q=1,n )
    enddo

    if (n == 1) then  
        det = matrix(1,1)
    else
        det = 0
        do i = 1, n  
            allocate( cf(n-1, n-1) )
            cf = cofactor( matrix, i, 1 )

            !***** output *****
            print "(4x,a,i0,a,i0,a)", "Getting a ", &
                    n-1, "-by-", n-1, " sub-matrix from cofactor():"
            do p = 1, n-1
                print "(8x, 100(f3.1,x))", ( cf( p, q ), q=1,n-1 )
            enddo

            print "(4x,a)", "and passing it to determinant()."

            det = det + ((-1)**(i+1))* matrix( i, 1 ) * determinant( cf )
            deallocate(cf)
        end do
    end if

    laplace_det = det

    !***** output *****
    print *, "  ---> Returning det = ", det
end function

function cofactor(matrix, mI, mJ)
    .... (same as the original code)
end function

end module

program main
    use mymod
    implicit none
    real :: a(3,3), det

    a( 1, : ) = [ 2.0, 9.0, 4.0 ]
    a( 2, : ) = [ 7.0, 5.0, 3.0 ]
    a( 3, : ) = [ 6.0, 1.0, 8.0 ]

    det = determinant( a )
    print "(a, es30.20)", "Final det = ", det
end program

то вывод ясно показывает, как обрабатываются данные:

Entering determinant() with matrix = 
    2.0 9.0 4.0
    7.0 5.0 3.0
    6.0 1.0 8.0
    Getting a 2-by-2 sub-matrix from cofactor():
        5.0 3.0
        1.0 8.0
    and passing it to determinant().
Entering determinant() with matrix = 
    5.0 3.0
    1.0 8.0
    Getting a 1-by-1 sub-matrix from cofactor():
        8.0
    and passing it to determinant().
Entering determinant() with matrix = 
    8.0
   ---> Returning det =    8.0000000    
    Getting a 1-by-1 sub-matrix from cofactor():
        3.0
    and passing it to determinant().
Entering determinant() with matrix = 
    3.0
   ---> Returning det =    3.0000000    
   ---> Returning det =    37.000000    
    Getting a 2-by-2 sub-matrix from cofactor():
        9.0 4.0
        1.0 8.0
    and passing it to determinant().
Entering determinant() with matrix = 
    9.0 4.0
    1.0 8.0
    Getting a 1-by-1 sub-matrix from cofactor():
        8.0
    and passing it to determinant().
Entering determinant() with matrix = 
    8.0
   ---> Returning det =    8.0000000    
    Getting a 1-by-1 sub-matrix from cofactor():
        4.0
    and passing it to determinant().
Entering determinant() with matrix = 
    4.0
   ---> Returning det =    4.0000000    
   ---> Returning det =    68.000000    
    Getting a 2-by-2 sub-matrix from cofactor():
        9.0 4.0
        5.0 3.0
    and passing it to determinant().
Entering determinant() with matrix = 
    9.0 4.0
    5.0 3.0
    Getting a 1-by-1 sub-matrix from cofactor():
        3.0
    and passing it to determinant().
Entering determinant() with matrix = 
    3.0
   ---> Returning det =    3.0000000    
    Getting a 1-by-1 sub-matrix from cofactor():
        4.0
    and passing it to determinant().
Entering determinant() with matrix = 
    4.0
   ---> Returning det =    4.0000000    
   ---> Returning det =    7.0000000    
   ---> Returning det =   -360.00000    
Final det =    -3.60000000000000000000E+02

Вы можете вставлять больше строк печати, пока весь механизм не станет ясен.


Кстати, код на странице Rossetta кажется намного проще, чем код OP, путем создания под- матрица непосредственно как локальный массив. Упрощенная версия кода гласит

recursive function det_rosetta( mat, n ) result( accum )
    integer :: n
    real    :: mat(n, n)
    real    :: submat(n-1, n-1), accum
    integer :: i, sgn

    if ( n == 1 ) then
        accum = mat(1,1)
    else
        accum = 0.0
        sgn = 1
        do i = 1, n
            submat( 1:n-1, 1:i-1 ) = mat( 2:n, 1:i-1 )
            submat( 1:n-1, i:n-1 ) = mat( 2:n, i+1:n )

            accum = accum + sgn * mat(1, i) * det_rosetta( submat, n-1 )
            sgn = - sgn
        enddo
    endif
end function

Обратите внимание, что расширение Лапласа выполняется вдоль первой строки, и что submat назначается с использованием секций массива. Задание также может быть записано просто как

submat( :, :i-1 ) = mat( 2:, :i-1 )
submat( :, i: )   = mat( 2:, i+1: )

где опущены верхняя и нижняя границы секций массива (тогда по умолчанию используются объявленные значения верхней и нижней границ). Последняя форма используется на странице Rosetta.

person roygvib    schedule 13.03.2016
comment
Действительно фантастическое объяснение, я изучал это в течение последних нескольких дней, и ваше объяснение дало мне не только лучшее понимание рекурсии, но и более глубокое понимание нарезки массива, я ценю время, которое вы потратили, чтобы дать мне этот ответ , я бы проголосовал, если бы у меня была необходимая репутация, я сделаю это, когда придет время! - person pi-e; 20.03.2016
comment
Ничего страшного, буду рад, если поможет :) - person roygvib; 20.03.2016