Difference between revisions of "Fortran"

From PHASTA Wiki
Jump to: navigation, search
m
(Replace equivalence statement with associate statement documentation)
 
Line 7: Line 7:
  
 
== Miscellaneous Notes ==
 
== Miscellaneous Notes ==
 +
=== Associate Statement ===
 +
Associate is effectively the replacement for (and better version of) the equivalence statement (which was formerly documented here, removed to reduce confusion).
 +
Effectively, it adds clarity to programming by allowing you to name "temporary variables"
  
=== Equivalence Statement ===
+
The <code>associate</code> statement allows different variable names to reference the same data storage (ie. memory allocation).
'''Caution:''' ''The <code>equivalence</code> statement has been labeled obsolete in modern Fortran standards (see [https://software.intel.com/content/www/us/en/develop/documentation/fortran-compiler-developer-guide-and-reference/top/language-reference/deleted-and-obsolescent-language-features/obsolescent-language-features-in-the-fortran-standard.html#obsolescent-language-features-in-the-fortran-standard Intel compiler documentation]).''
+
A common paradigm in PHASTA is the use of temporary arrays to store the partial result of calculations that maybe reused multiple times.
 
+
For example, the function below calculates sum over i of [ (sqrt(a) * sqrt(b))^2_i + sqrt(a * b)_i ], where a and b are vectors:
The <code>equivalence</code> statement allows different variable names to reference the same "data" (read as memory allocation).
 
A common paradigm in PHASTA is the use of temporary arrays to store the result of calculations that maybe reused multiple times.
 
For example, the function <code>fun_prod</code> below calculates sum over i of [ 2* (a * b)_i + sqrt(a * b)_i ], where a and b are vectors:
 
  
 
  <nowiki>
 
  <nowiki>
real function fun_prod1(a, b)
+
function example_func1(a, b) result(c)
 
     implicit none
 
     implicit none
 
     real, intent(in), dimension(4) :: a, b
 
     real, intent(in), dimension(4) :: a, b
     real, dimension(4) :: temp, sqrtab
+
     real, intent(out) :: c
     integer :: i
+
     real, dimension(4) :: temp
  
 
     temp = a*b
 
     temp = a*b
     sqrtab = sqrt(temp)
+
     c = sqrt(temp)
     temp = temp * 2.
+
 
     fun_prod1 = sum(temp + sqrtab)
+
     temp = sqrt(a)*sqrt(b)
 +
     c = c + temp**2
  
 
     return
 
     return
end function fun_prod1
+
end function example_func1
 
</nowiki>
 
</nowiki>
  
While reusing the <code>temp</code> array for both a*b and 2*a*b is memory efficient (as we only need to have 2 1x4 arrays) and computationally efficient (as we calculate a*b only once), it's not very easy to read as the ''definition'' of <code>temp</code> changes throughout the code.
+
While reusing the <code>temp</code> array for both a*b and sqrt(a)*sqrt(b) is memory efficient (as we only need to have 2 1x4 arrays, <code>temp</code> and <code>c</code>) and computationally efficient (as we calculate a*b only once), it's not very easy to read as the ''definition'' of <code>temp</code> changes throughout the code.
  
To improve the legibility, we could set a*b and 2*a*b to be different variables instead:
+
To improve the legibility, we could set a*b and sqrt(a)*sqrt(b) to be different variables instead:
  
 
  <nowiki>
 
  <nowiki>
real function fun_prod2(a, b)
+
function example_func2(a, b) result(c)
 
     implicit none
 
     implicit none
 
     real, intent(in), dimension(4) :: a, b
 
     real, intent(in), dimension(4) :: a, b
     real, dimension(4) :: ab, ab2, sqrtab
+
    real, intent(out) :: c
    integer :: i
+
     real, dimension(4) :: ab, ab_sqrt
   
+
 
 
     ab = a*b
 
     ab = a*b
     sqrtab = sqrt(ab)
+
     c = sqrt(ab)
     ab2 = ab * 2.
+
 
     fun_prod2 = sum(ab2 + sqrtab)
+
     ab_sqrt = sqrt(a)*sqrt(b)
 +
     c = c + ab_sqrt**2
  
 
     return
 
     return
end function fun_prod2
+
end function example_func2
 
</nowiki>
 
</nowiki>
  
This is easier to read, as the contents of <code>ab</code> and <code>ab2</code> are far less ambiguous. However, note that we are less memory efficient than before; we now require 3 1x4 arrays compared to the previous 2. This may not seem like much, but memory speed is often the bottle-neck when it comes to HPC performance, so a 50% increase in required memory is significant.  
+
This is easier to read, as the contents of <code>ab</code> and <code>ab_sqrt</code> are far less ambiguous.  
 +
However, note that we are less memory efficient than before; we now require 3 1x4 arrays (<code>ab</code>, <code>ab_sqrt</code>, and <code>c</code>) compared to the previous 2.  
 +
This may not seem like much, but memory speed is often the bottle-neck when it comes to HPC performance, so a 50% increase in required memory is significant.
 +
This is especially true when you deal with arrays with millions of elements rather than just 4.
 +
 
 +
However, using the <code>associate</code> statement, we can get the best of both worlds:
  
However, using the <code>equivalence</code> statement, we can get the best of both worlds:
 
 
  <nowiki>
 
  <nowiki>
real function fun_prod3(a, b)
+
function example_func3(a, b) result(c)
 
     implicit none
 
     implicit none
 
     real, intent(in), dimension(4) :: a, b
 
     real, intent(in), dimension(4) :: a, b
     real, dimension(4) :: equiv__ab, equiv_2ab, sqrtab
+
     real, intent(out) :: c
     equivalence (equiv_ab, equiv_2ab)
+
     real, dimension(4) :: temp
    integer :: i
+
 
      
+
     associate(ab => temp)
    equiv_ab = a*b
+
        ab = a*b
     sqrtab = sqrt(equiv_ab)
+
        c = sqrt(ab)
    equiv_2ab = equiv_ab * 2.
+
     end associate
     fun_prod3 = sum(equiv_2ab + sqrtab)
+
 
 +
    associate(ab_sqrt => temp)
 +
        ab_sqrt = sqrt(a)*sqrt(b)
 +
        c = c + ab_sqrt**2
 +
     end associate
  
 
     return
 
     return
end function fun_prod3
+
end function example_func3
 
</nowiki>
 
</nowiki>
  
 
+
Here, <code>ab</code> and <code>ab_sqrt</code> use the ''same memory'', thus we only require 2 1x4 arrays. <code>example_func3</code> is compiled as completely identical to <code>example_func1</code>, but it's more easily readable.
Here, <code>eqab</code> and <code>eq2ab</code> use the ''same memory'', thus we only require 2 1x4 arrays. <code>fun_prod3</code> is compiled as completely identical to <code>fun_prod1</code>, but it's more easily readable.
+
It also has the nice effect of separating the code into computation chunks; if you use temporary variables, each new associate block represents a new "task" being completed.
 
 
'''Note:''' <code>equivalence</code> can unexpected results if the programmer is not aware that two variables are made equivalent. That is why the variables <code>ab</code> and <code>ab2</code> in <code>fun_prod2</code> were changed to <code>equiv_ab</code> and <code>equiv_2ab</code>. This serves as a reminder that variables with the prefix <code>equiv_</code> belong to the same memory space, and thus cannot be treated as different.
 
  
 
[[Category:Software_Engineering]]
 
[[Category:Software_Engineering]]

Latest revision as of 16:21, 4 February 2021

Fortran (short for Formula Translation) is a programming language geared towards numerical computation.

Documentation

Some documentation of the language format can be found at Intel's Developer Guide.

Fortran subroutines can also be documented using Doxygen. It is recommended that you document your code in a Doxygen format, as we're looking to move to Doxygen for PHASTA documentation.

Miscellaneous Notes

Associate Statement

Associate is effectively the replacement for (and better version of) the equivalence statement (which was formerly documented here, removed to reduce confusion). Effectively, it adds clarity to programming by allowing you to name "temporary variables"

The associate statement allows different variable names to reference the same data storage (ie. memory allocation). A common paradigm in PHASTA is the use of temporary arrays to store the partial result of calculations that maybe reused multiple times. For example, the function below calculates sum over i of [ (sqrt(a) * sqrt(b))^2_i + sqrt(a * b)_i ], where a and b are vectors:

function example_func1(a, b) result(c)
    implicit none
    real, intent(in), dimension(4) :: a, b
    real, intent(out) :: c
    real, dimension(4) :: temp

    temp = a*b
    c = sqrt(temp)

    temp = sqrt(a)*sqrt(b)
    c = c + temp**2

    return
end function example_func1

While reusing the temp array for both a*b and sqrt(a)*sqrt(b) is memory efficient (as we only need to have 2 1x4 arrays, temp and c) and computationally efficient (as we calculate a*b only once), it's not very easy to read as the definition of temp changes throughout the code.

To improve the legibility, we could set a*b and sqrt(a)*sqrt(b) to be different variables instead:

function example_func2(a, b) result(c)
    implicit none
    real, intent(in), dimension(4) :: a, b
    real, intent(out) :: c
    real, dimension(4) :: ab, ab_sqrt

    ab = a*b
    c = sqrt(ab)

    ab_sqrt = sqrt(a)*sqrt(b)
    c = c + ab_sqrt**2

    return
end function example_func2

This is easier to read, as the contents of ab and ab_sqrt are far less ambiguous. However, note that we are less memory efficient than before; we now require 3 1x4 arrays (ab, ab_sqrt, and c) compared to the previous 2. This may not seem like much, but memory speed is often the bottle-neck when it comes to HPC performance, so a 50% increase in required memory is significant. This is especially true when you deal with arrays with millions of elements rather than just 4.

However, using the associate statement, we can get the best of both worlds:

function example_func3(a, b) result(c)
    implicit none
    real, intent(in), dimension(4) :: a, b
    real, intent(out) :: c
    real, dimension(4) :: temp

    associate(ab => temp)
        ab = a*b
        c = sqrt(ab)
    end associate

    associate(ab_sqrt => temp)
        ab_sqrt = sqrt(a)*sqrt(b)
        c = c + ab_sqrt**2
    end associate

    return
end function example_func3

Here, ab and ab_sqrt use the same memory, thus we only require 2 1x4 arrays. example_func3 is compiled as completely identical to example_func1, but it's more easily readable. It also has the nice effect of separating the code into computation chunks; if you use temporary variables, each new associate block represents a new "task" being completed.