|
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
Slide 1 Fortran has a long and distinguished history. It was first developed to relieve the programmer from the tedious and non-portable complexities of low-level programming in assembly or machine code.
1 Introduction F90 Model : Introduction Several versions and standards have been developed along the way. The ``most popular'' (IMHO) was Fortran 77, which contained the older features and introduced more structured programming idioms. Much of the freely available numerical software is written to the Fortran 77 standard and is fairly portable to most architectures that support a Fortran 77 compiler.
Fortran 77 promoted more structured programming. The purpose of this presentation is to discuss the programming model that the new Fortran 90 standard appears to promote. This is not a tutorial. You should already be familiar with Fortran 90. This Fortran 90 model is highly speculative and purely opinionated and should not be taken as authoritative, but may be useful as a guide as you develop your own programming style with Fortran 90.
Qualifications (R.K.Owen):
- Nearly 30 years of programming with: assembly, BASIC, Fortran, C, C++, Java
- Currently do much of my work in C
- Certified in object-oriented programming and design
These pages were crafted using a m4 macro package developed to isolate the task of web-page authoring from the HTML details.
Slide 2 The Fortran 90 standard was drafted and finalized after much dissension and debate. The previous standards were more evolutionary and, for the most part, standardized existing practice. However, the Fortran 90 standard committees (ANSI X3J3 & ISO WG5) followed a more innovative route to keep Fortran viable into the future. You may be violently opposed to this approach; but, be that as it may, Fortran 90 exists and does offer many features that make the task of crafting code easier and more enjoyable.
1 F90 Model : Introduction One of the decisions the committees made was to make Fortran 77 a subset of Fortran 90, thus allowing the existing Fortran 77 code base to port directly. This has made Fortran 90 a hermaphroditic monster. You can either look at it as Fortran 77 being a wart on Fortran 90, or Fortran 90 as a wart on Fortran 77.
For this presentation, we'll confine ourselves to that part of Fortran 90 that is unique.
Note: This subset of Fortran 90 has been made into a proprietary language named Ftm largely created by Walter Brainerd(?) and available at http://www.imagine1.com/imagine1/
Slide 3 Fortran 90 is not object-oriented, it does not have:
1 F90 Model : Introduction Fortran 90 does have object-like characteristics:
- Inheritance (is-a relationships)
- Polymorphism
- ``Class'' methods
- User-derived (or user defined) types (has-a relationships)
- Overloaded operator/assignment/procedure interfaces
- Data encapsulation
This places Fortran 90 somewhere in between `C' and C++. Unfortunately, because of the lengthy standards process and the inavailability of conforming compilers, Fortran 90 use has been minimal, but appears to be increasing.
Opinion: Fortran 90 should have been a new language, without any Fortran 77 encumbrances. This would have allowed for more rational optimizing compilers sooner.
Slide 4
2 Style F90 Model : Programming Style Programming style is a matter of taste and can lead to long lasting religious wars. There's no wrong or right way. Do that which seems natural to you.
- Use the Fortran 90 free-format form. There are tricks you can use for header files for portable use from either Fortran 77 fixed-format or Fortran 90 free-format.
- Liberal use of <TABS> for indentation of code blocks.
- Use comments liberally to document the code. Fortran 90 allows statements to have trailing comments.
- Add implicit none as the first statement (after module USE statements). This enforces strong type declarations and code safety. This was common to most Fortran 77 compilers, but was not officially part of the standard until Fortran 90.
- Use entity-oriented declarations instead of attribute-oriented declarations. For example:
instead ofreal, dimension(20), save :: val real, save :: xval real, dimension(10) :: avalThe obvious advantages are that you can quickly see all the attributes for a given variable.real val, xval, aval dimension val(20), aval(10) save val, xval
- Use underscores (`_') instead of spaces within in variable names. Fortran does not use white space as keyword delimiters, but may, like most other languages, do so in the future.
- Variable & routine names can be longer than 8 characters (and less than 32 characters), thus promoting descriptive names.
- If a routine has more than 1 argument, give the dummy arguments sensible names, and use the keyword feature of Fortran 90. For example:
subroutine subr(in, out)then call withcall subr(in=xxx, out=yyy)or equivalently called ascall subr(out=yyy, in=xxx)this hides any subsequent changes to the user interface (by making successive arguments optional and testing for them).
Slide 5 Fortran 90 has several nice features which we'll explore. Many of which extend current practices - others are new additions to the language.
3 F90 features F90 Model : Features
- minimum numeric capability in the form of KINDs
- overloading of functions with GENERIC INTERFACEs
- Modules
- Dynamic memory allocation
- User-derive types
- NAMELIST I/O
- pointers
- deprecated and obsolescent features
Slide 6 Fortran 90 introduces the concept of KIND, which, if used properly, can free the programmer of targeting code for a certain platform. For example, workstations generally have 32-bit REALs and 64-bit DOUBLE PRECISION. Contrast this to SGI/Crays which have default 64-bit REALs and implements a 128-bit DOUBLE PRECISION in software.
3.1 Kinds of Type F90 Model : Kinds of Type For this example I also use the `C' preprocessor for conditional compilation. The source file is tkind.F90, which #includes tkind.h. This method is common on UNIX systems, and on other platforms as well. The `C' preprocessor is more flexible and provides much more capability than the Fortran 90 standard `INCLUDE' (which is deprecated).
In this case, the conditional compilation is necessary for the GENERIC INTERFACE code. Generic interfaces allow the overloading of user functions, similar to overloading ABS to equate to ABS, IABS, DABS, CABS, or etc. It selects the correct function depending on the type of argument given. Generic interfaces require the functions to be distinguishable by Type, Kind, or Rank (TKR). One implementation couldn't handle the KIND=16 and demoted it to KIND=8 instead of failing. This created an ambiguity between show_real8 and show_real16, which yielded a compiler error.
The selected kinds, precision and ranges, are typical for IEEE implementation of floating point representations. Also, notice that parameter values are kept in a MODULE for easy inclusion in all the subroutines.
Code examples
Fortran 90 kinds of intrinsic types
tkind.F90 :
module numeric_kind implicit none ! define several KIND parameters for use everywhere integer, parameter :: INT1 = selected_int_kind(R=2) integer, parameter :: INT2 = selected_int_kind(R=4) integer, parameter :: INT4 = selected_int_kind(R=9) integer, parameter :: INT8 = selected_int_kind(R=18) integer, parameter :: REAL4 = selected_real_kind(P= 6,R=37) integer, parameter :: REAL8 = selected_real_kind(P=13,R=300) integer, parameter :: REAL16 = selected_real_kind(P=27,R=2400) end module numeric_kind subroutine show_int1(x) use numeric_kind implicit none ! KIND must be known at compile time integer(kind=INT1), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***INT1 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_int_kind(R=2) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"RANGE = ", i6)') range(x) write(6,'(1x,"HUGE = ")',advance='NO') ! Have no idea how many digits to plan for ! Use non-advancing output and let compiler ! handle it with list directed output write(6, *) huge(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_int1 subroutine show_int2(x) use numeric_kind implicit none integer(kind=INT2), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***INT2 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_int_kind(R=4) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"RANGE = ", i6)') range(x) write(6,'(1x,"HUGE = ")',advance='NO') write(6, *) huge(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_int2 subroutine show_int4(x) use numeric_kind implicit none integer(kind=INT4), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***INT4 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_int_kind(R=9) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"RANGE = ", i6)') range(x) write(6,'(1x,"HUGE = ")',advance='NO') write(6, *) huge(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_int4 subroutine show_int8(x) use numeric_kind implicit none integer(kind=INT8), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***INT8 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_int_kind(R=18) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"RANGE = ", i6)') range(x) write(6,'(1x,"HUGE = ")',advance='NO') write(6, *) huge(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_int8 subroutine show_real4(x) use numeric_kind implicit none real(kind=REAL4), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***REAL4 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_real_kind(P= 6,R=37) write(6,'(1x,"PRECISION = ", i6)') precision(x) write(6,'(1x,"MAXEXPONENT = ", i6)') maxexponent(x) write(6,'(1x,"MINEXPONENT = ", i6)') minexponent(x) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"EPSILON = ")',advance='NO') write(6, *) epsilon(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_real4 subroutine show_real8(x) use numeric_kind implicit none real(kind=REAL8), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***REAL8 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_real_kind(P=13,R=300) write(6,'(1x,"PRECISION = ", i6)') precision(x) write(6,'(1x,"MAXEXPONENT = ", i6)') maxexponent(x) write(6,'(1x,"MINEXPONENT = ", i6)') minexponent(x) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"EPSILON = ")',advance='NO') write(6, *) epsilon(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_real8 subroutine show_real16(x) use numeric_kind implicit none real(kind=REAL16), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***REAL16 KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_real_kind(P=27,R=2400) write(6,'(1x,"PRECISION = ", i6)') precision(x) write(6,'(1x,"MAXEXPONENT = ", i6)') maxexponent(x) write(6,'(1x,"MINEXPONENT = ", i6)') minexponent(x) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"EPSILON = ")',advance='NO') write(6, *) epsilon(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_real16 program tkind use numeric_kind ! Use parameter names,because KIND values may ! vary for each compiler integer(kind=INT1) :: iq = 1_INT1 integer(kind=INT2) :: ih = 1_INT2 integer(kind=INT4) :: i = 1_INT4 integer(kind=INT8) :: ii = 1_INT8 real(kind=REAL4) :: x = 1.0_REAL4/3.0_REAL4 real(kind=REAL8) :: xx = 1.0_REAL8/3.0_REAL8 real(kind=REAL16) :: xxxx = 1.0_REAL16/3.0_REAL16 ! use the CPP include instead of F90 include, because need conditional ! compilation #include "tkind.h" call show_real4(x) call show_real8(xx) call show_real16(xxxx) call show_int1(iq) call show_int2(ih) call show_int4(i) call show_int8(ii) write(6,'(/1x,"try generic interface")') call show_kind(i) call show_kind(xx) end program tkind
Fortran 90 generic interface
tkind.h :
! ! create an overloaded 'generic' interface which depends on the subroutine ! 'signature' based on data type, kind number, and rank (TKR) interface show_kind subroutine show_int1(x) use numeric_kind integer(kind=INT1), intent(in) :: x end subroutine show_int1 subroutine show_int2(x) use numeric_kind integer(kind=INT2), intent(in) :: x end subroutine show_int2 subroutine show_int4(x) use numeric_kind integer(kind=INT4), intent(in) :: x end subroutine show_int4 subroutine show_int8(x) use numeric_kind integer(kind=INT8), intent(in) :: x end subroutine show_int8 subroutine show_real4(x) use numeric_kind real(kind=REAL4), intent(in) :: x end subroutine show_real4 subroutine show_real8(x) use numeric_kind real(kind=REAL8), intent(in) :: x end subroutine show_real8 #ifndef NO_16 subroutine show_real16(x) use numeric_kind real(kind=REAL16), intent(in) :: x end subroutine show_real16 #endif end interface
Output results
x86 Linux - g77/vastf90 ***REAL4 KIND = 4 requested = 4 PRECISION = 6 MAXEXPONENT = 128 MINEXPONENT = -125 RADIX = 2 DIGITS = 24 EPSILON = 1.1920929E-07 value = 0.333333343 ***REAL8 KIND = 8 requested = 8 PRECISION = 15 MAXEXPONENT = 1024 MINEXPONENT = -1021 RADIX = 2 DIGITS = 53 EPSILON = 2.22044605E-16 value = 0.333333333 ***REAL16 KIND = 8 requested = 16 PRECISION = 15 MAXEXPONENT = 1024 MINEXPONENT = -1021 RADIX = 2 DIGITS = 53 EPSILON = 2.22044605E-16 value = 0.333333333 ***INT1 KIND = 1 requested = 1 RADIX = 2 DIGITS = 7 RANGE = 2 HUGE = 127 value = 1 ***INT2 KIND = 2 requested = 2 RADIX = 2 DIGITS = 15 RANGE = 4 HUGE = 32767 value = 1 ***INT4 KIND = 4 requested = 4 RADIX = 2 DIGITS = 31 RANGE = 9 HUGE = 2147483647 value = 1 ***INT8 KIND = 8 requested = 8 RADIX = 2 DIGITS = 63 RANGE = 18 HUGE = -1 value = 1 try generic interface ***INT4 KIND = 4 requested = 4 RADIX = 2 DIGITS = 31 RANGE = 9 HUGE = 2147483647 value = 1 ***REAL8 KIND = 8 requested = 8 PRECISION = 15 MAXEXPONENT = 1024 MINEXPONENT = -1021 RADIX = 2 DIGITS = 53 EPSILON = 2.22044605E-16 value = 0.333333333
Slide 7 Fortran 90 frees the programmer from integer and floating point representation specificity. Use the KINDs that meet the needed precision for the problem. For most numeric work, 64-bit REALs and 32-bit INTEGERs are sufficient. The specification can be encapsulated in a MODULE for easy inclusion.
3.1.1 Define Specifications F90 Model : Define Minimum Numeric Specification Fortran 90 has defined several intrinsic functions for retrieving the defining characteristics for each intrinsic type. The following example defines three KIND parameters to handle the three intrinsic types which will be used.
Note how literal constants and variables are declared using these parameter types.
Code examples
Fortran 90 - define needed precision for task
module numeric_ implicit none ! define several KIND parameters for use everywhere ! 32bit integer integer, parameter :: INT_ = selected_int_kind(R=9) ! 64bit real integer, parameter :: REAL_ = selected_real_kind(P=13,R=300) ! 64+64bit complex integer, parameter :: COMPLEX_ = selected_real_kind(P=13,R=300) ! ! create an overloaded 'generic' interface which depends ! on the subroutine 'signature' based on data type, ! kind number,and rank (TKR) interface show_kind module procedure show_int_, show_real_, show_complex_ end interface ! ! force the use of the generic interface by excluding ! any public access to specific routines private show_int_, show_real_, show_complex_ contains subroutine show_int_(x) implicit none integer(kind=INT_), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***INT_ KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_int_kind(R=9) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"RANGE = ", i6)') range(x) write(6,'(1x,"HUGE = ")',advance='NO') write(6, *) huge(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_int_ subroutine show_real_(x) implicit none real(kind=REAL_), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***REAL_ KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_real_kind(P=13,R=300) write(6,'(1x,"PRECISION = ", i6)') precision(x) write(6,'(1x,"MAXEXPONENT = ", i6)') maxexponent(x) write(6,'(1x,"MINEXPONENT = ", i6)') minexponent(x) write(6,'(1x,"RADIX = ", i6)') radix(x) write(6,'(1x,"DIGITS = ", i6)') digits(x) write(6,'(1x,"EPSILON = ")',advance='NO') write(6, *) epsilon(x) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_real_ subroutine show_complex_(x) implicit none complex(kind=COMPLEX_), intent(in) :: x ! kind must not be variable write(6,'(/1x,"***COMPLEX_ KIND = ", i6, 10x,"requested = ", i6)') & kind(x), selected_real_kind(P=13,R=300) write(6,'(1x,"PRECISION = ", i6)') precision(real(x)) write(6,'(1x,"MAXEXPONENT = ", i6)') maxexponent(real(x)) write(6,'(1x,"MINEXPONENT = ", i6)') minexponent(real(x)) write(6,'(1x,"RADIX = ", i6)') radix(real(x)) write(6,'(1x,"DIGITS = ", i6)') digits(real(x)) write(6,'(1x,"EPSILON = ")',advance='NO') write(6, *) epsilon(real(x)) write(6,'(1x,"value = ")',advance='NO') write(6, *) x end subroutine show_complex_ end module numeric_ program tnumeric use numeric_ ! Note how variables and literal constants are declared integer(kind=INT_) :: i = 1_INT_ real(kind=REAL_) :: xx = 1.0_REAL_/3.0_REAL_ ! note that the COMPLEX_ kind specification must be appended to each ! literal constant ... not to the complex pair complex(kind=COMPLEX_) :: cc = & (1.0_COMPLEX_,1.0_COMPLEX_)/(3.0_COMPLEX_,0.0_COMPLEX_) ! call show_int_(i) ! illegal - no public access write(6,'(/1x,"Use generic interface")') call show_kind(i) call show_kind(xx) call show_kind(cc) end program tnumeric
Output results
x86 Linux - g77/vastf90 Use generic interface ***INT_ KIND = 4 requested = 4 RADIX = 2 DIGITS = 31 RANGE = 9 HUGE = 2147483647 value = 1 ***REAL_ KIND = 8 requested = 8 PRECISION = 15 MAXEXPONENT = 1024 MINEXPONENT = -1021 RADIX = 2 DIGITS = 53 EPSILON = 2.22044605E-16 value = 0.333333333 ***COMPLEX_ KIND = 8 requested = 8 PRECISION = 15 MAXEXPONENT = 1024 MINEXPONENT = -1021 RADIX = 2 DIGITS = 53 EPSILON = 2.22044605E-16 value = (0.333333333,0.333333333)
Slide 8 Fortran 90 added the new feature of modules, which provides a way to consolidate, isolate and encapsulate code into an ``easily'' maintainable package. As with object-oriented languages, it provides a way to isolate many of the operations and data into a single source.
3.2 Introduction to Modules F90 Model : Modules Modules is a popular concept in software now (e.g. Fortran 90 modules, perl modules, kernel modules, environment modules, apache web-server modules, pluggable authentication modules, etc.). The concept is to provide a documented API, with rules and limitations.
One attribute of modules is the ability to limit ``name space'' pollution. That is, avoid accidental name collision between two variables. Many software development problems stem from undeclared variables, or from accidentally reusing variables unintentionally. Fortran 90 modules can solve this common problem.
Modules can contain and encapsulate the data providing ``methods'' for handling the data. Unlike object-oriented languages there is no way to shield the user from the object details. However, object details can be minimized and the wise user will take advantage of them.
- Eliminate COMMON BLOCKs and non-standard (Fortran 77) INCLUDE
- Encapsulate dynamic memory allocation
- Parameterize and specify numeric precision
- User-derived types
Slide 9 Modules can provide an extremely robust alternative to the Fortran 77 COMMON BLOCK.
3.2.1 Modules Not COMMON BLOCKs F90 Model : Modules Over COMMON BLOCKs
- All details of the data structure & order is isolated to a single source file. No need to replicate COMMON BLOCKs throughout each subroutine or use the non-Fortran 77 INCLUDE as a work-around.
- Avoid problems with unequal COMMON BLOCK definitions.
- Can use all data variables by default, or select and rename a subset.
Code examples
example of typical Fortran 77 code
program tcommon C Here we use implicit declarations ... all REAL variables common /abc/a,b,c C call suba() call subb() call subc() C write(6,'(1x,"a,b,c=",3f7.2)') a,b,c stop end c======================================================================= subroutine suba() common /abc/a,b,c C must declare 'abc' exactly the same in each routine a = 1.0 return end c======================================================================= subroutine subb() common /abc/a,b,c C all variables are visible to each routine that accesses 'abc' b = 2.0 return end c======================================================================= subroutine subc() C Common source of problems,suppose implicitly C declare 'C' variables complex ... yields C different size common block common /abc/a,b,c c = 3.0 return end
Fortran 90 replacement example
module commondat implicit none real, public :: a,b,c data a,b,c / -1.0, -1.0, -1.0/ contains subroutine outdat() write(6,'(1x,"a,b,c=",3f7.2)') a,b,c end subroutine outdat end module commondat program tcommon use commondat implicit none call suba() call subb() call subc() call outdat() end program tcommon subroutine suba() ! USE the module ... it handles all ! the necessary declarations use commondat, only : x=>a implicit none real :: a = 1.0 x = a return end subroutine suba subroutine subb() ! ONLY use the variable we need ... ! even rename it,all other are 'invisible' use commondat, only : x=>b implicit none real :: a = 2.0 x = a return end subroutine subb subroutine subc() use commondat, only : x=>c ! note the rename syntax is same as ! pointer assignment implicit none real :: a = 3.0 x = a return end subroutine subc
Output results
a,b,c= 1.00 2.00 3.00
Slide 10 Fortran 77 did not have any native way to dynamically allocate memory. As a result several hacks were developed to accomplish much of the same capability, such as:
3.2.2 Encapsulate Dynamic Memory F90 Model : Encapsulate Dynamic Memory
- Create a work COMMON BLOCK and edit the size of it by hand to tailor it to the problem size and recompile.
- Use non-portable un-named COMMON BLOCK, which would point to some relatively safe region of memory behind the text and data. This is highly compiler dependent.
- Mix Fortran 77 and 'C', because 'C' includes dynamic memory (and command-line argument parsing) as part of the standard.
Fortran 90 includes dynamic memory allocation with a vengeance. It introduces the concept of pointers, automatic arrays, and allocatable arrays; however, be careful! Dynamic memory allocation & deallocation is expensive and it's generally better to avoid repetitious use of it in often called routines.
The following example demonstrates where a data array could be tailored to the problem size, thus minimizing program memory requirements which could be determined at run-time. The allocation is expected to be infrequent, and the data arrays are expected to be shared through out the program.
The module CONTAINS two routines that ``construct'' and ``destruct'' the arrays, as if in an object-oriented language. However, for Fortran 90 these routines will need to be called explicitly since such constructors and destructors are not called automatically. As with C++ an optional argument can be passed to the constructor to pre-set all the array values to a specified value. If no arguments are given, pre-set to zero.
Code examples
Fortran 90 dynamic memory module
module data_array implicit none ! allocatable arrays are always assumed-shape real, dimension(:,:), allocatable :: datarr, tadarr contains ! will emulate object-like constructors and destructors ! the difference is that they are not called automatically ! as arrays come into and out of scope ! construct data arrays function ctor_dat(dim1,dim2, value) logical :: ctor_dat ! returns .TRUE. if OK integer, intent(in) :: dim1, dim2 real, intent(in), optional :: value ! preset dat to value integer :: istat real :: val ! good design requires that we test for every ! conceivable or likely failure and notify the caller ctor_dat = .FALSE. ! assume the worst allocate(datarr(dim1,dim2), stat=istat) ! allocate space if (istat .ne. 0) then return endif allocate(tadarr(dim2,dim1), stat=istat) if (istat .ne. 0) then deallocate(datarr) ! return memory return endif ! use OPTIONAL arguments for unlikely requirements if (present(value)) then val = value else val = 0.0 endif datarr = val ! preset arrays tadarr = val ctor_dat = .TRUE. ! everything OK return end function ctor_dat ! destroy data arrays function dtor_dat() logical :: dtor_dat ! returns .TRUE. if OK integer :: istat1, istat2 dtor_dat = .FALSE. ! assume the worst deallocate(datarr, stat=istat1) ! deallocate space deallocate(tadarr, stat=istat2) if (istat1 .eq. 0 .and. istat2 .eq. 0) then dtor_dat = .TRUE. endif return end function dtor_dat end module data_array program talloc use data_array implicit none ! try 2x3 case if (.not. ctor_dat(dim1=2,dim2=3,value=-1.0)) then stop 'Error 2x3 ctor' endif write (*,*) "dat = ", datarr write (*,*) "tad = ", tadarr if (.not. dtor_dat()) then stop 'Error 2x3 dtor' endif ! try 3x4 case if (.not. ctor_dat(dim1=3,dim2=4)) then stop 'Error 3x4 ctor' endif write (*,*) "dat = ", datarr write (*,*) "tad = ", tadarr datarr = reshape(source=(/ 1.,2.,3., 2.,3.,4., 3.,4.,5., 4.,5.,6./), & shape=(/3,4/)) write (*,*) "dat = ", datarr tadarr = transpose(datarr) write (*,*) "tad = ", tadarr write (*,*) "dat*tad = ", matmul(datarr,tadarr) write (*,*) "tad*dat = ", matmul(tadarr,datarr) if (.not. dtor_dat()) then stop 'Error 3x4 dtor' endif stop 'Normal end' end program talloc
Output results
dat = -1. -1. -1. -1. -1. -1. tad = -1. -1. -1. -1. -1. -1. dat = 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. tad = 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. dat = 1. 2. 3. 2. 3. 4. 3. 4. 5. 4. 5. 6. tad = 1. 2. 3. 4. 2. 3. 4. 5. 3. 4. 5. 6. dat*tad = 30. 40. 50. 40. 54. 68. 50. 68. 86. tad*dat = 14. 20. 26. 32. 20. 29. 38. 47. 26. 38. 50. 62. 32. 47. 62. 77. STOP Normal end statement executed
Slide 11 Fortran 90 adds the concept of user-derived types. That is, the user may define some entity in terms of intrinsic types or other user-derived types, along with the operations that can be performed on them. The new derived type can be used as if it were an intrinsic type and part of the language.
3.2.3 Create User-derived Types F90 Model : Create User-derived Types The lay-out in memory for a derived type is contiguous, but the amount of memory used may not be as expected since the compiler is free to pad memory as needed to honor word boundaries.
The following example shows a typical Cartesian vector type, and defines what the `+' operation will do between two such types. The advantages are that such code can be isolated and rigorously tested, leading to more robust and less buggy code overall. The derived-type modules then can be re-used in other coding projects.
IMHO: derived-types add 'fun' to the language as it gives you the ability to create useful and meaningful components.
Code examples
Fortran 90 user derived-type module (vector)
module vector3 implicit none type vector3d real :: x,y,z end type vector3d ! Predefine some useful constants type(vector3d), parameter :: i_ = vector3d(1.0, 0.0, 0.0) type(vector3d), parameter :: j_ = vector3d(0.0, 1.0, 0.0) type(vector3d), parameter :: k_ = vector3d(0.0, 0.0, 1.0) ! extend definitions for current standard operators ! define operator interface binding to some common operators interface operator (+) ! must be a pre-existing operator module procedure v3add end interface ! Define your own operators ! define custom operator interface interface operator (.add.) ! must be of .OP. form module procedure v3add end interface ! Can force the use of these operators ! private :: v3add ! can make internal routines private to force ! use of the operator interface. contains ! define operations type(vector3d) function v3add(v1,v2) implicit none type(vector3d), intent(in) :: v1, v2 v3add%x = v1%x + v2%x v3add%y = v1%y + v2%y v3add%z = v1%z + v2%z return end function v3add subroutine v3out(head,v) implicit none character*(*), intent(in) :: head type(vector3d), intent(in) :: v write(6,'(1x,a10,3f8.3)') head, v%x, v%y, v%z end subroutine v3out end module vector3 program tvector3 use vector3 implicit none ! Note the C++-like constructor initialization ! use structure constructors to initialize variables for derived types type(vector3d) :: v1 = vector3d( 1.0, 2.0, 3.0 ) type(vector3d) :: v2 = vector3d( 1.0, 4.0, 9.0 ) type(vector3d) :: v3 = vector3d( -1.0, -1.0, -1.0 ) call v3out('v1 = ', v1) call v3out('v2 = ', v2) call v3out('v3 = ', v3) v3 = v3add(v1,v2) ! illegal if v3add is private call v3out('new v3 = ', v3) v3 = v1 .add. i_ call v3out('new v3 = ', v3) v3 = v1 + j_ call v3out('new v3 = ', v3) end program tvector3
Output results
v1 = 1.000 2.000 3.000 v2 = 1.000 4.000 9.000 v3 = -1.000 -1.000 -1.000 new v3 = 2.000 6.000 12.000 new v3 = 2.000 2.000 3.000 new v3 = 1.000 3.000 3.000
Slide 12 Fortran 90 adds NAMELIST as a part of the standard; however, with I/O syntax that varies from that used by the existing Fortran 77 compilers. This isn't a major catastrophe since many of the Fortran 77 compilers had subtle differences between them since NAMELIST was not part of the Fortran 77 standard.
3.3 Standard NAMELIST I/O F90 Model : NAMELIST I/O NAMELIST provides an excellent way to add annotated input. Ignoring much of the difficulties of precise spacing and precision specifications needed for standard formatted READs. NAMELIST usually shines as a superior method for input, and is hardly used for output, except for checking what is acceptable NAMELIST input. It uses a syntax similar to keyword argument parsing. The order of the keywords is unimportant within the NAMELIST block. However, the order of NAMELIST blocks is important for READs. Some implementations will skip over NAMELIST blocks until the expected one is reached. If no REWIND is performed, you can have multiple NAMELIST blocks of the same name and differing data and read them in as needed.
Code examples
Fortran 90 - Namelist I/O
module datastuff use numeric_ implicit none ! for comparison purposes real :: ttreal, treal = 1.0 integer :: ttinteger, tinteger = 2 complex :: ttcomplex, tcomplex = (3.0, 4.0) character*10 :: ttchar, tchar = 'namelist' logical :: ttbool, tbool = .TRUE. real, dimension(4) :: aareal, & areal = (/ 1.0, 1.0, 2.0, 3.0 /) integer, dimension(4) :: aainteger, & ainteger = (/ 2, 2, 3, 4 /) complex, dimension(4) :: aacomplex, & acomplex = (/ (3.0, 4.0), & (3.0, 4.0), (5.0, 6.0), (7.0, 7.0) /) character*10, dimension(4) :: aachar, & achar = (/ 'namelist ', 'namelist ',& 'array ', ' the lot ' /) logical, dimension(4) :: aabool, & abool = (/ .TRUE., .TRUE., .FALSE., & .FALSE. /) real(kind=REAL_) :: xxreal, xreal = 1.0_REAL_ integer(kind=INT_) :: xxinteger, xinteger = 2_INT_ complex(kind=COMPLEX_) :: xxcomplex, & xcomplex = (3.0_COMPLEX_, 4.0_COMPLEX_) contains subroutine clearstuff() ttreal = 0.0 ttinteger = 0 ttcomplex = (0.0,0.0) ttchar = '' ttbool = .FALSE. aareal(1:4) = 0.0 aainteger(1:4) =0 aacomplex(1:4) =(0.0,0.0) aachar(1:4) = '' aabool(1:4) = .FALSE. xxreal = 0.0_REAL_ xxinteger = 0_INT_ xxcomplex = (0.0_COMPLEX_,0.0_COMPLEX_) end subroutine clearstuff subroutine diffstuff(fullout) implicit none logical :: fullout integer :: numbad,i ! Compare the input data to the expected value numbad = 0 if (ttreal .ne. treal) then numbad = numbad + 1 if (fullout) write(6,*) 'treal diff = ', ttreal - treal endif if (ttinteger .ne. tinteger) then numbad = numbad + 1 if (fullout) write(6,*) 'tinteger diff = ', ttinteger - tinteger endif if (ttcomplex .ne. tcomplex) then numbad = numbad + 1 if (fullout) write(6,*) 'tcomplex diff = ', ttcomplex - tcomplex endif if (ttchar .ne. tchar) then numbad = numbad + 1 if (fullout) write(6,*) 'tchar diff = ', ttchar, tchar endif if (ttbool .xor. tbool) then numbad = numbad + 1 if (fullout) write(6,*) 'tbool diff = ', ttbool, tbool endif do i = 1,4 if (aareal(i) .ne. areal(i)) then numbad = numbad + 1 if (fullout) write(6,*) 'areal diff = ', aareal(i) - areal(i) endif if (aainteger(i) .ne. ainteger(i)) then numbad = numbad + 1 if (fullout) write(6,*) 'ainteger diff = ', aainteger(i) - ainteger(i) endif if (aacomplex(i) .ne. acomplex(i)) then numbad = numbad + 1 if (fullout) write(6,*) 'acomplex diff = ', aacomplex(i) - acomplex(i) endif if (aachar(i) .ne. achar(i)) then numbad = numbad + 1 if (fullout) write(6,*) 'achar diff = ', aachar(i), achar(i) endif if (aabool(i) .xor. abool(i)) then numbad = numbad + 1 if (fullout) write(6,*) 'abool diff = ', aabool(i), abool(i) endif enddo if (xxreal .ne. xreal) then numbad = numbad + 1 if (fullout) write(6,*) 'xreal diff = ', xxreal - xreal endif if (xxinteger .ne. xinteger) then numbad = numbad + 1 if (fullout) write(6,*) 'xinteger diff = ', xxinteger - xinteger endif if (xxcomplex .ne. xcomplex) then numbad = numbad + 1 if (fullout) write(6,*) 'xcomplex diff = ', xxcomplex - xcomplex endif if (numbad .ne. 0) then write(6,'(1x,"found ",i3," differences")') numbad else write(6,'(1x,"found "," no"," differences")') endif end subroutine diffstuff end module datastuff program tnmlist use datastuff implicit none ! ! demonstrate the f90 standard namelist ! ! Note that NAMELIST syntax is similar to COMMON BLOCKs namelist /tdata/ treal, tinteger, tcomplex, tchar, tbool namelist /adata/ areal, ainteger, acomplex, achar, abool namelist /xdata/ xreal, xinteger, xcomplex namelist /ttdata/ ttreal, ttinteger, ttcomplex, ttchar, ttbool namelist /aadata/ aareal, aainteger, aacomplex, aachar, aabool namelist /xxdata/ xxreal, xxinteger, xxcomplex ! the OPEN statement defines many of the ! NAMELIST characteristics ! need the delim, else some implementations will not surround ! character strings with delimiters ! recl limits the I/O to 80 character lines open(6, recl=80, delim='APOSTROPHE') open(8,file="tnmlist.in", status='OLD', recl=80, delim='APOSTROPHE') ! ! NAMELIST output varies with compilers ! how NAMELIST data displays on your system write(6,nml=tdata) write(6,nml=adata) write(6,nml=xdata) write(6,*) write(6,*) 'Read first batch' call clearstuff() read(8,nml=ttdata) read(8,nml=aadata) read(8,nml=xxdata) ! using the KEYWORD feature of routine ! calls adds clarity call diffstuff(fullout=.TRUE.) write(6,*) 'Read second batch' call clearstuff() read(8,nml=ttdata) read(8,nml=aadata) read(8,nml=xxdata) call diffstuff(fullout=.TRUE.) end program tnmlist
Input file
! can have blank lines and comments in the namelist input file ! place these comments between NAMELISTs ! ! not every compiler supports comments within the namelist ! in particular vastf90/g77 does not ! ! some will skip NAMELISTs not directly referenced in read !&BOGUS rko=1 / ! &TTDATA TTREAL = 1., TTINTEGER = 2, TTCOMPLEX = (3.,4.), TTCHAR = 'namelist', TTBOOL = T/ &AADATA AAREAL = 1. 1. 2. 3., AAINTEGER = 2 2 3 4, AACOMPLEX = (3.,4.) (3.,4.) (5.,6.) (7.,7.), AACHAR = 'namelist' 'namelist' 'array' ' the lot', AABOOL = T T F F/ &XXDATA XXREAL = 1., XXINTEGER = 2, XXCOMPLEX = (3.,4.)/ ! ! the following output was generated by the Cray f90 compiler ! the Cray compiler supports comments within the namelist, ! and with an "assign" option will skip unreferenced NAMELISTs ! &TTDATA TTREAL = 1., TTINTEGER = 2, TTCOMPLEX = (3.,4.), TTCHAR = 'namelist ', TTBOOL = T / &AADATA AAREAL = 2*1., 2., 3., AAINTEGER = 2*2, 3, 4, AACOMPLEX = 2*(3.,4.), (5.,6.), (7.,7.), AACHAR = 2*'namelist ', 'array ', ' the lot ', AABOOL = 2*T, 2*F / &XXDATA XXREAL = 1., XXINTEGER = 2, XXCOMPLEX = (3.,4.) /
Output results
x86 Linux - g77/vastf90 &TDATA TREAL = 1., TINTEGER = 2, TCOMPLEX = (3.,4.), TCHAR = 'namelist', TBOOL = T/ &ADATA AREAL = 1. 1. 2. 3., AINTEGER = 2 2 3 4, ACOMPLEX = (3.,4.) (3.,4.) (5.,6.) (7.,7.), ACHAR = 'namelist' 'namelist' 'array' ' the lot', ABOOL = T T F F/ &XDATA XREAL = 1., XINTEGER = 2, XCOMPLEX = (3.,4.)/ Read first batch found no differences Read second batch found no differences
Slide 13 Fortran 90 provides advanced dynamic memory management on par with `C'. Therefore, many of the common `C' data structures can be translated into equivalent Fortran 90 forms. This example shows how to implement a singularly-linked list, where we anchor the first element to head and add successive elements until finished. All access to this data is in the same order as input. This method is ideal for situations where the number of elements is known at run-time, but only after the list is entered. Any element can be accessed in linear time, hence, random access is discouraged.
3.4 Pointers and Advanced Memory Management F90 Model : Pointers and Advanced Memory Management Each element is a user-derived type that can contain other user-derived types, intrinsic types, and, most importantly, a pointer of the element type. The last element's pointer will be nullified to alert the user that there's no more data.
If you use the detailed knowledge of the element structure, this is straight forward to implement. The disadvantage is that any changes in the structure (say you want to add a member or change one) reflects itself in code changes not isolated to the MODULE. This can be mitigated by providing object-like ``methods'' that hide the details. There is little performance penalty since such routines will in-line with good optimizing compilers. The coding becomes a little more difficult, but the advantages are more robust codes.
Code examples
Fortran 90 user derived-type - a linked list
module listlink ! Contains a user-derived type - vector use vector3 implicit none ! Implement a singularly-linked list type llink type(vector3d) :: point ! can use other derived types logical :: visible integer :: magnitude type(llink), pointer :: next end type llink contains ! ! Use object-oriented methods - encapsulate and isolate ! Try to hide the implementation details by providing "methods" for the ! "objects". If you change the internal details, then you don't have ! to change the program except in the module itself ! subroutine setnext(link,next) implicit none type(llink), pointer :: link, next if (associated(link) .and. associated(next)) then if (.not. associated(link%next)) then link%next => next endif endif end subroutine setnext function nextlink(link) implicit none type(llink), pointer :: nextlink, link nextlink => link%next end function nextlink subroutine showpoint(link) implicit none type(llink) :: link write(6, '(1x,l5,i5,3f15.5)') link%visible, link%magnitude, & link%point%x, link%point%y, link%point%z end subroutine showpoint ! must return the allocated space as a pointer,else it ! disappears at routine end. function addpoint(pt, vis, mag) implicit none type(llink), pointer :: addpoint, link type(vector3d), intent(in) :: pt logical, intent(in) :: vis integer, intent(in) :: mag allocate(addpoint) addpoint%point%x = pt%x addpoint%point%y = pt%y addpoint%point%z = pt%z addpoint%visible = vis addpoint%magnitude = mag nullify(addpoint%next) end function addpoint end module listlink program tllink use vector3 use listlink implicit none type(llink), pointer :: head type(llink), pointer :: ptr, tmp type(vector3d), dimension(8) :: cube = (/ & vector3d( .5, .5, .5), & vector3d( .5, .5,-.5), & vector3d( .5,-.5, .5), & vector3d( .5,-.5,-.5), & vector3d(-.5, .5, .5), & vector3d(-.5, .5,-.5), & vector3d(-.5,-.5, .5), & vector3d(-.5,-.5,-.5) /) integer :: i ! Nullify pointers to clear them - else ... nullify(head) nullify(ptr) ! set up linked list head => addpoint(pt=cube(1), vis=.TRUE.,mag=9) ptr => head write (6,'(1x,"adding ")', advance='NO') ! Note that at no time we use the internal ! structure,and confine all accesses to the ! provided ``methods'' do i=2, size(cube) write (6,'(i3)', advance='NO') i tmp => addpoint(pt=cube(i), vis=.TRUE., mag=9-i) call setnext(link=ptr,next=tmp) ptr => nextlink(ptr) end do ! print out linked list write (6,'(/,1x,"viewing - ")') ptr => head i = 0 do i = i + 1 if (.not. associated(ptr)) exit write (6,'(i3," : ")', advance='NO') i call showpoint(ptr) ptr => nextlink(ptr) enddo ! deallocate list (clean-up) write (6,'(1x,"deallocating ")', advance='NO') ptr => head i = 0 do i = i + 1 if (.not. associated(ptr)) exit write (6,'(i3)', advance='NO') i tmp => ptr ptr => nextlink(ptr) deallocate(tmp) enddo write(6,'(/,1x,"End of Program")') end program tllink
Output results
adding 2 3 4 5 6 7 8 viewing - 1 : T 9 0.50000 0.50000 0.50000 2 : T 7 0.50000 0.50000 -0.50000 3 : T 6 0.50000 -0.50000 0.50000 4 : T 5 0.50000 -0.50000 -0.50000 5 : T 4 -0.50000 0.50000 0.50000 6 : T 3 -0.50000 0.50000 -0.50000 7 : T 2 -0.50000 -0.50000 0.50000 8 : T 1 -0.50000 -0.50000 -0.50000 deallocating 1 2 3 4 5 6 7 8 End of Program
Slide 14 The main reason Fortran 90 includes the Fortran 77 language as a subset is because of the large body of existing Fortran 77 code. For example: NCAR, LAPACK, SLATEC, netCDF, etc. to name a few available packages, and any personal legacy code. However, Fortran 77 has made certain assumptions about the memory lay-out of arrays, primarily, that arrays are column oriented and contiguous. In many of the matrix oriented packages you need to pass the maximum leading dimension. As far as the routines were concerned, all arrays were one-dimensional. If given the maximum leading dimension and current matrix dimension the routine could determine where the array data was stored. This is called an assumed-size specification.
4 Interfacing With F90 F90 Model : Interfacing with F77 Fortran 90 frees the program of much of these burdens by packaging the array dimensions into the passed array reference. All the routine needs to know is the array shape (i.e. 1,2,3, or more dimensions). This is the assumed-shape specification.
These are mutually exclusive and often lead to either compiling or run-time problems. The solution is to provide an INTERFACE block, which gives the Fortran 90 compiler more information regarding the Fortran 77 routine.
Code examples
Fortran 77 old function to access
oldfun.f :
real function oldfun(itype, na, nb, array) integer itype, na, nb, maxa parameter (maxa = 8) c use linearized array and assume leading dimension of maxa if rank 2 real array(1) integer i,j c c sum values in this array c oldfun = 0.0 if (itype .eq. 1) then c column only do 100 i=1,na oldfun = oldfun + array(i) 100 continue else do 200 j=1,nb do 200 i=1,na oldfun = oldfun + array(i + (j-1)*maxa) 200 continue endif return end
Fortran 77 call to oldfun.f
program toldfun integer maxa, maxb, i, j parameter (maxa = 8, maxb = 6) real array(maxa, maxb) c set up array do 100 j = 1,maxb do 100 i = 1,maxa array(i,j) = i+j-1 100 continue c write (6,*) array write (6,*) 'sum34 = ', oldfun(2, 3,4, array) write (6,*) 'sum43 = ', oldfun(2, 4,3, array) write (6,*) 'should = ', 42. write (6,*) 'sum_3 = ', oldfun(1, 4,0, array(1,3)) write (6,*) 'should = ', 18. end
Fortran 90 call to oldfun.f
program tnewint implicit none integer, parameter :: maxa = 8, maxb = 6 real, dimension(maxa,maxb) :: array integer i,j ! add an interface block for oldfun ... ! this gives the compiler more info regarding ! the f77 fn,but does have some limits interface real function oldfun(itype,na,nb,array) integer, intent(in) :: itype, na, nb ! must use assumed-size specification for f77 array real, dimension(*), intent(in) :: array end function oldfun end interface ! set up array do j = 1,maxb do i = 1,maxa array(i,j) = i+j-1 enddo enddo ! write (6,*) array ! can pass entire array write (6,*) 'sum34 = ', oldfun(2, 3,4, array) ! address of 1st element write (6,*) 'sum34 = ', oldfun(2, 3,4, array(1,1)) ! linearize the array (repacks it) write (6,*) 'sum43 = ', oldfun(2, 4,3, pack(array,.true.)) write (6,*) 'should = ', 42. ! can just give address of 1st element for F77 fns write (6,*) 'sum_3 = ', oldfun(1, 4,0, array(1,3)) ! pass an array slice write (6,*) 'sum_3 = ', oldfun(1, 4,0, array(:,3)) write (6,*) 'should = ', 18. end program tnewint
Output results
sum34 = 42. sum34 = 42. sum43 = 42. should = 42. sum_3 = 18. sum_3 = 18. should = 18.
Slide 15 Fortran 77 provided the EQUIVALENCE statement as a means for the programmer to micro-manage memory by re-using space for different variables and arrays. This often leads to buggy code and endless hours of debugging. There is no need for such memory management, nowadays. Hence, Fortran 90 has made EQUIVALENCE deprecated and it may vanish in some subsequent standard release.
5 F77 EQUIVALENCE? F90 Model : F77 EQUIVALENCE? In nearly 30 years, I have found only one bona fide need for EQUIVALENCE, and it was easily replaced by passing the array twice to a subroutine with suitably declared array dimensions.
Code examples
Fortran 77 EQUIVALENCE
program tequiv ! ! the only time an EQUIVALENCE statement was needed was to ! map a 3xN array to a 2x(3N/2) array for convenience ! and even then,this trick of passing the array as ! an argument twice handled the need. ! implicit none integer, parameter :: MAXDIM3 = 2000 real, dimension(3,MAXDIM3) :: vector3 real, dimension(MAXDIM3) :: radial3 integer, dimension(21) :: rbin integer :: i call gausspdf(vector3=vector3, radial3=radial3, gauss2=vector3) call binrad(bin=rbin, radvec=radial3) ! write output suitable for piping into GNUPLOT open(unit=11, file="equiv.dat", status='UNKNOWN') do i=1,MAXDIM3 write(11,'(1x,4f15.5)') vector3(1:3,i), radial3(i) enddo close(unit=11) open(unit=11, file="equivrad.dat", status='UNKNOWN') do i=1,21 write(11,'(1x,2f15.5)') real(i-1)*.25, rbin(i)/real(MAXDIM3) enddo close(unit=11) write(6,*) 'set nokey' write(6,*) 'set title "x-y distribution"' write(6,*) 'plot "equiv.dat" using 1:2' write(6,*) 'pause 5 "x-y distribution"' write(6,*) 'set title "x-z distribution"' write(6,*) 'plot "equiv.dat" using 1:3' write(6,*) 'pause 5 "x-z distribution"' write(6,*) 'set title "y-z distribution"' write(6,*) 'plot "equiv.dat" using 2:3' write(6,*) 'pause 5 "y-z distribution"' write(6,*) 'set title "r distribution"' write(6,*) 'plot "equivrad.dat" using 1:2 with lines' write(6,*) 'pause 5 "r distribution"' write(6,*) 'quit' contains subroutine gausspdf(vector3, radial3, gauss2) implicit none real, dimension(:,:), intent(inout) :: vector3 ! 3-N vector ! we need vector3 inout because we use the size of it,hence "in" real, dimension(:), intent(out) :: radial3 ! radii real, dimension(2,*), intent(out) :: gauss2 ! convenience vector ! same as vector3 ! automatic arrays and local variables real, dimension(size(vector3)/2) :: r2gauss2 real, dimension(2,size(vector3)/2) :: ranvec logical, dimension(size(vector3)/2) :: maskgauss2 ! don't use initialization here, else the value will be SAVE'd across calls integer :: maxg ! dimension of gauss2 maskgauss2 = .TRUE. maxg = size(vector3)/2 ! compute the Gaussian distribution using the Box-Muller method ! as described by Forsythe, Malcolm, and Moler generate: do call random_number(ranvec) ! create random number in range [-1.0,1.0) ! use vector masking, must have same shape where (maskgauss2) gauss2(1,1:maxg) = (2.0*ranvec(1,1:maxg) - 1.0) gauss2(2,1:maxg) = (2.0*ranvec(2,1:maxg) - 1.0) r2gauss2 = gauss2(1,1:maxg)**2 + gauss2(2,1:maxg)**2 end where where (r2gauss2 > 1.0) maskgauss2 = .TRUE. else where maskgauss2 = .FALSE. end where ! if not within range, go back and do it again for the afflicted entries if (.not. any(maskgauss2)) exit generate end do generate where (r2gauss2 .ne. 0.0) r2gauss2 = sqrt(-2.0*log(r2gauss2)/r2gauss2) gauss2(1,1:maxg) = gauss2(1,1:maxg) * r2gauss2 gauss2(2,1:maxg) = gauss2(2,1:maxg) * r2gauss2 end where radial3 = vector3(1,:)**2 + vector3(2,:)**2 + vector3(3,:)**2 radial3 = sqrt(radial3) end subroutine gausspdf subroutine binrad(bin, radvec) integer, dimension(:) :: bin real, dimension(:) :: radvec ! initialize bin count bin = 0 ! bin size = .25 where (radvec < 5.0) bin(int(radvec/.25)+1) = bin(int(radvec/.25)+1) + 1 end where return end subroutine binrad end program tequiv
Output results
![]()
![]()
Slide 16 The new Fortran 90 standard promotes a model or method for coding that departs from the structured programming style of Fortran 77. This new style, though not strictly object-oriented, does have object-like capabilities that make codes robust and more encapsulated.
6 Conclusion F90 Model : Conclusion As Fortran 90 matures and more compilers come to exist, hopefully, there will be more codes and packages freely available.
Fortran 90 adds a `fun' factor that makes code crafting even more enjoyable.