Portability

Strategy for Portable Fortran Code
==================================

D. McCune, C. Karney, C. Ludescher, A. Pletzer, L. Randerson -- Apr. 1999
PPPL Computational Plasma Physics Group -- work funded by U.S. DoE.
Last Update -- D. McCune 26 July 1999

This document describes a set of tools for making fortran code portable
between machines.  The emphasis is on the conversion of legacy codes
written in fixed format fortran-77.

If you want to use these tools without knowing all the details:  please
read the introductory sections up through "Quick_Start_Guide".

Home Top


Design_Philosophy

This document describes a reasonably comprehensive strategy for the
conversion of legacy Fortran code to a form which is portable to a wide
variety of machines.  As will be seen, portability concerns impose many
constraints; satisfying all of these simultaneously is not always a
simple task.  Even so, there are a number of solutions.  One method
is presented in this document.  The philosophy of the strategy presented 
here is based on the following principles:

  *  compatibility with reuse of legacy fortran-77 code without requirement
     of extensive rewrite.

  *  useful in context of mixed language codes containing C, C++, 
     fortran-77, and fortran-90.

  *  programmer continues to work with "familiar" languages:  fortran-77,
     fortran-90, cpp, with C and C++ used only if required by the 
     application itself.  The C pre-processor language (cpp) is used
     for conditional compilation of machine specific code, and machine-
     dependent macro expansion.

  *  portability considerations should not force a programmer to 
     maintain a "private copy" of standard math library routines 
     such as those contained in BLAS and LAPACK.  However, mathematical
     tools, for example the "Slatec" library, may be provided as NTCC
     modules, if the standard distributions contain portability
     problems.

The emphasis will be on portability "within the Unix world", although the
techniques presented would not be incompatible with maintaining a single
source that also builds on VMS, NT, or other non-Unix platforms.  In fact,
the methods described have been used to implement a portable version of
TRANSP (a very large tokamak physics data analysis code system with 500K
lines of code, much of it ancient).  This code now runs on both VMS and
many brands of Unix.

Note also that the portability techniques described here are based on
methods that have been tested and found to work-- but may not be 100%
standards conforming in all respects.  Although 100% adherance to ANSI
standard fortran-77 is certainly one way to assure portability, we find
this approach is seldom feasible in practice.  In any case, the main
concern is methods for achieving a practical portability for legacy
fortran codes.  These codes, as received, are essentially never in
100% conformance with ANSI standards.  Even so, programmers are urged
to stick to the standards wherever possible.

In codes where non-portable vendor-specific methods for such things
as compound data types or memory allocation have been used, it is
generally not possible to achieve portability without rewrite to a
(more nearly) standards conforming programming approach.  One good
solution in such sitations would be a port to fortran-90.

Difficult porting problems do arise.  This document cannot cover
all issues.  Some codes, which rely on a non-portable vendor specific 
subroutine libraries, might not be feasibly portable at all.

Development of portability strategies is an ongoing effort.  Suggestions
for improvements are welcome.  Such suggestions can be sent by Email to 
PPPL participants in the NTCC Modules Library Project:

    Doug McCune -- dmccune@pppl.gov
    Tina Ludescher -- ludesche@pppl.gov

Home Top


Summary_of_Issues

Portability issues come up in several ways:

  *  portability of the fortran source code itself,

  *  portability issues related to mixed language programming,

  *  access to portable macro preprocessing of fortran source code,

  *  "makefile" issues -- machine dependence in the process of
     compilation and loading of code binaries.

Each of these sets of issues will be considered in separate sections.

It should be emphasized that the proposed solutions to porting problems
are not unique, and that they are also a matter of ongoing development.
Users and programmers may choose other methods.  However, achievement of
genuine portability is not as simple as one might expect, and yet is a 
matter of considerable "economic" importance if one is concerned with 
the ability to share and reuse code -- especially, legacy fortran code.

Programmers desiring a more sophisticated and thorough approach to
portability, documentation and other programming aids... may want to 
look at FWEB:

     https://w3.pppl.gov/~krommes/fweb_toc.html 

Another approach to portability is to use the LLNL Basis software, see:

     http://basis.llnl.gov 

Home Top


NTCC_Portability_Modules

Reference will be made to codes and portability tools which are available 
in the National Transport Code Collaboration (NTCC) Modules Library; NTCC
library codes can be downloaded from the website at 

     https://w3.pppl.gov/NTCC 

The NTCC Modules Library Project is funded by the U.S. Department of Energy
(Fusion Energy Sciences Office).

The following NTCC modules are of interest in code porting work:

  * portlib -- a library of interfaces to operating system functions
               (such as "getenv", "getcwd", "system", etc), which hides
               operating system dependency from the user code.

  * fpreproc-- a set of python scripts and cpp ".h" files that allow 
               the use of cpp for preprocessing of fortran sources, to 
               support conditional compilation and macro substitution,
               which give methods for implementing portable code from
               a single source.

  * ftoken  -- this is a small collection of programs based on a
               subroutine library "ftoken" which detects symbols
               or "tokens" in fortran code, while screening out
               comments and quoted strings.  The programs included
               in the ftoken module are useful for portability
               conversions.  These programs are:

       => fgtok -- program for wholesale manipulation of names and
               declarations in fortran codes (mainly:  fixed format
               fortran).  The `fgtok' program, with accompanying name
               substitution tables, can perform such operations as
                 => perform any user specified table driven token (name)
                    substitution operations (for example, change from 
                    using single precision "nag" to double precision "nag"
                    subroutine calls).
                 => convert all floating point declarations to "REAL*8"
                    and "COMPLEX*16".
                 => declare "IMPLICIT NONE" in all routines.
                 => convert all floating point constants to a common
                    standard form.
                 => convert all non-generic intrinsic function references
                    to their generic form.
                 => "detab" source code.
                 => check that all source lines satisfy the 72 character
                    length limit.

       => idecl -- a program for generating the declaration of formerly 
               implicitly declared items in fixed format fortran source.
               Reason:  when converting code for greater portability,
               it is often a good idea to convert it to use IMPLICIT NONE.
               But that requires declaring all previously implicitly 
               declared names.  The idecl program does this.

       => cpcheck -- a program that scans a named fortran source looking
               for macros specified in a named cpp #include file; it
               simply writes "present" if macros are found, "absent"
               otherwise.

       => r8real -- a program that automatically generates an interface,
               written in fortran-90 using copies from/to dynamically
               allocated arrays, which allows a REAL*8 code to call 
               subroutines in a library written with REAL arguments,
               or, conversely for REAL code to call subroutines in a 
               library written with REAL*8 arguments.

       => cface -- a program that generates a C-callable interface to
               a fortran routine that contains INTEGER, REAL, REAL*8,
               and CHARACTER*(*) arguments.  The interface routine
               uses portlib to convert between C null terminated byte
               strings to fortran character strings.

       => iface -- a program that generates "interface modules" which
               a fortran-90 program can "use" for compile time checking
               of subroutine arguments.  Also it can be used to group
               several specific routine interfaces under one generic
               name.

               Further details on these codes are included below.
Home Top


Target_machines

The following machines have been designated as target machines in
the porting strategy described here, and were used for testing.

Supercomputers:  Cray UNICOS systems (NERSC)

Workstations:    DEC alpha (Linux & DIGITAL UNIX),IBM,HP,SGI,SUN

PCs:             Intel Linux systems -- Portland Group Fortran or
                                        Fujitsu Fortran.

A subset of our testing was done on workstation class VMS machines
as well.

We have not attempted to address issues of portability to MacOS or
Windows environments-- we think systematic support of portability 
to these environments in addition to UNIX/VMS may prove expensive.

Home Top


System_Prerequisites

The portability scheme outlined in this document makes the following
assumptions about target systems:

(1)  availability of gnu software:

     * gcc -- for the C preprocessor
     * gmake -- for makefiles

(2)  availability of standard mathematical freeware

     * BLAS
     * LAPACK

In NTCC modules library submissions, it shall be presumed that
module authors need not duplicate freely available software in
their source distributions -- rather, the module author should
document the dependency and indicate a source for the needed
freeware, which on some systems may need to be downloaded and
installed.

The SLATEC library (a library of special functions:  Bessel
functions, etc.) presents a special case; a portable SLATEC
special functions library (not the complete SLATEC) is available
as an NTCC module.

Home Top


Quick_Start_Guide

For users wishing to try out the NTCC portability tools without
learning about all the details, the following procedure is
recommended.

(1)  acquire the NTCC portability tools-- download the portability
modules "portlib", "ftoken", and "fpreproc" from the modules library
at  https://w3.pppl.gov/NTCC  .  PPPL users take note:  the software
is already installed on PPPL UNIX cluster machines in:

  /usr/pppl/ntcc/bin, /usr/pppl/ntcc/etc, and /usr/pppl/ntcc/include.  

Put /usr/pppl/ntcc/bin and /usr/pppl/ntcc/etc into your PATH.

(2)  use "fpreproc" to preprocess all fortran code (see subtopic for
more information).

(3)  for Cray / Workstation compatibility, use the commands
`r8_convert' and `r8_convert_include' to convert fortran-77 legacy
code to REAL*8 precision (see subtopic for more information).

(4)  as the need arises, use "portlib" routines to replace non-
portable calls to operating system services such as access to 
shell commands.  The portlib library is covered in detail at
the beginning of the following section, "Fortran_Issues".

(5)  use a carefully constructed Makefile to build your code.  The
Makefile invokes the preprocessor and encodes the system variations
of compiler switches and the like.  Examples are available from the 
NTCC modules library (e.g. the REAL*8 `lsode' module).

(6)  mixed language programming:  use the "f77name.h" #include file
and F77NAME macro, to portably make C functions callable from fortran
code (see subtopic for more information).

Home Top


fpreproc

`fpreproc' is a script that enables the famous C preprocessor (cpp)
to be used in a fortran friendly way:  the preprocessor will not be
confused by fortran comments, and the 72 character line width limit
will be respected wherever possible.

In addition, use of `fpreproc' with certain #include files (which
can be automatically inserted into source codes with `r8_convert')
allows the automatic conversion calls of common library routines
(blas, lapack, linpack, nag) when moving code between CRAY and
workstation environments.

An example of interactive use of fpreproc follows.  Note the source 
code is stored in a file named `foo.F' instead of `foo.f'.  On a SUN 
workstation:

    > fpreproc foo.F foo.f -D__UNIX -D__SUN
    > f77 -c foo.f

If `foo.F' contains lines like
    
    #if __UNIX

          [...UNIX specific code...]

    #elif __VMS

          [...VMS specific code...]

    #endif

then the preprocessor will remove the #if/#elif/#endif lines and
the VMS code, leaving only 

          [...UNIX specific code...]

The general form of the fpreproc command is:

  fpreproc sce.F sce.f [CPP options...] [-free|-132]

where

  sce.F  --  source file, input to preprocessor
  sce.f  --  preprocessed source, output
  [CPP options...]  --  generally, macro definitions such as 
             "-D__UNIX" -- options for cpp
  [-free|-132]      --  -free for free form f90 source; -132
             for fixed form 132 character wide fortran source --
             options for prefpp/postfpp perl scripts.
             
Usually, fpreproc or an equivalent procedure is invoked through a 
makefile.  Some common "macro" symbols used to distinguish 
environments:

  __UNIX    -- UNIX
  __VMS     -- VMS

  __SUN     -- SUN workstations
  __ALPHA   -- DEC ALPHA workstations
  __CRAY    -- NERSC CRAY machines
  __SGI     -- SGI machines such as the Origin-2000
  __RS6000  -- IBM RS6000 workstaitons
  __HP      -- HP workstations
  __LINUX   -- LINUX workstations

  __F90     -- fortran-90

Home Top


limitations

The r8_convert tool was designed for precision conversion of fortran-77
"legacy" source code.  While it may have applicability to fortran-90
sources as well, its usefulness is less assured and will depend on the
programming style used for the sources in question.  The tool was built
for a "one time only" conversion of legacy code, and to deal with 
precision portability issues inherant in fortran-77.  Unlike fortran-77,
fortran-90 has within its standard good methods for dealing with 
portability of floating point precision.  These should be used in 
preference to reliance on convertor tools.

Home Top


r8_convert

This powerful script is used for a onetime conversion of legacy
fortran-77 code to a Cray/Workstation portable form.  The code
is convereted to REAL*8 / COMPLEX*16 precision -- the REAL*8 
declaration is preferred, because, unlike "REAL" or "DOUBLE PRECISION",
"REAL*8" has the same meaning on all machines.

The r8_convert script (for source files) is invoked as follows:

  r8_convert input.f output.f [s-file(s)...] [CONST|CONST_R8] [k-file] \
         [TEX] [NODECL] [NO-COMMENT]

and the r8_convert_include script (for include files) as follows:

  r8_convert_include input.inc output.inc [s-file(s)...] \
         [CONST|CONST_R8] [TEX] [NODECL] [NO-COMMENT]

where 

  input.f   -- input source file
  output.f  -- output, REAL*8 converted source file
  input.inc   -- input source INCLUDE file
  output.inc  -- output, REAL*8 converted source INCLUDE file

  [s-files(s)...] -- optional, substitution table files (see below).
  CONST     -- optional:  convert constants to CONST macro form
               1.0 -> CONST(1.0,0)  1.602D-19 -> CONST(1.602,-19)
               (see below).
  CONST_R8  -- optional:  convert constants using f90 convention:
                (a) declare 
                  "INTEGER, PARAMETER :: R8=SELECTED_REAL_KIND(12,100)"
                (b) append _R8 on every constant:
                  1.0 -> 1.0_R8   1.602D-19 -> 1.602E-19_R8
  [k-file]  -- optional, *not for INCLUDE files*: constants symbol
               definition file (see below).
  TEX       -- optional:  convert source only, in mixed TEX/fortran
               source file.

  NODECL    -- optional:  suppress automatic declaration of implicitly
               declared symbols (idecl).  It is recommended to use this
               option if attempting to apply r8_convert to a fortran-90
               source, otherwise some fortran-90 intrinsics may be mis-
               taken as symbols requiring automatic declaration.

CAUTION -- r8_convert may expand some fortran-77 lines beyond the
the 72 column limit.  Any such line is flagged in messages written by
r8_convert, but, it is left to the user to repair such lines with
a text editor.

When codes are under active development, r8_convert can be re-applied,
e.g. to enforce a standard representation of floating point constants.

Home Top


Substitution-table-files

Substitution table files contains records of the form

  <from-name>  <to-name>

and can be used to convert a set of subroutine names and all
corresponding calls.  The substitution table files can contain
comments starting with "#" in the first character position.
For example:

# map subroutine names, REAL names -> REAL*8 names

pspline  r8pspline
bcspeval r8bcspeval
...

Home Top


CONST_or_CONST_R8

By default, r8_convert maps all hard coded constants to D-
exponent form, i.e.

    1.0  ->  1.0D0
    1.602e-19 -> 1.602D-19

Then, on CRAY machines, a compiler switch is used to force
interpretation of these constants as REAL*8 precision numbers.

Alternatively, the CONST macro can be used.  Then, r8_convert
does the mapping

    1.0  ->  CONST(1.0,0)
    1.602e-19 -> CONST(1.602,-19)

and then at compile time the preprocessor (fpreproc) expands

    CONST(1.602,-19) -> 1.602E-19  on CRAY machines,
    CONST(1.602,-19) -> 1.602D-19  on workstations,

thus yielding code which is portable without reliance on a
compiler switch.

Another option-- and a good one-- is to rely on fortran-90.  Using
the CONST_R8 macro option will cause r8_convert to generate a
declaration of 

    INTEGER, PARAMETER :: R8=SELECTED_REAL_KIND(12,100)

at the top of each program unit, and then, map all floating point
constants to one of two forms, as illustrated by these examples:

    1.0  ->   1.0_R8     (no exponent)
    1.602D-19 -> 1.602E-19_R8   (exponent, "E" symbol enforced).

Note:  when choosing CONST_R8 or other constants mapping option,
all forms of constants, including the CONST(n.nn,m) constructs if
already present in the input source file, are mapped to the chosen 
form.

Home Top


constants-symbol-definition-files

It can be convenient to replace commonly occurring constants with 
a symbolic name (parameter).  This has two advantages:

  (a) improves readability of code, relative to CONST(...,..) or _R8
      constructs, and
  (b) may reduce line length relative to other constructs.

The latter aspect is especially useful when working against a 72
character length limit processing fixed form source.

This file has the same syntax as a substitution file, but the
interpretation is different:

 <symbolic-name>   <constant-value>

leading to all constants matching <constant-value> to be replaced
by the <symbolic-name>, and, causing generation of a declaration
of <symbolic-name>, in fortran-90 style, at the top of program 
units where it is used.

Here is an example of output resulting from using a constants file
("k-file") in conjunction with the CONST option:

      subroutine dpowers(w,akzt,ichi,jpsi,zdRovR,lposabs,kinetic,
     %                  pwrden,pwrspc,pwrmc,pwre,pwrh,cnprp2,emtoep)
 !
      REAL*8, PARAMETER :: ZERO = CONST(0.0,0) ! _fgtok_constant_
      REAL*8, PARAMETER :: HALF = CONST(0.5,0) ! _fgtok_constant_
      REAL*8, PARAMETER :: ONE = CONST(1.0,0) ! _fgtok_constant_

 ...
 ...
      cnmag2=(ONE,ZERO)*cabs(cnprp2)
      tprp0=(ZERO,ZERO)
      tcrs0=(ZERO,ZERO)

(these are complex number assignments)

without using this option, one would have seen lines such as

      cnmag2=(CONST(1.0,0),CONST(0.0,0))*cabs(cnprp2)

which are longer and a bit harder to read.

Too suppress the generation of declarations, which one should do e.g.
if planning to use an f90 module to define constants, the k-file should
include the line

    $DECLARE  NONE

the default is to declare constants as REAL*8.

What follows is a copy of "fgtok.constants", a sample k-file that
is distrubuted with the ftoken module:

 #
 #  this is an example of an fgtok "symbolic constants" file
 #  constants can take several forms:  here are examples:
 #    1.0  1.0e2  3.1415d00  CONST(0.0,0)  1.0e6_R8
 #
 #  when selecting a "constants file" for a particular conversion job,
 #  one should take care to choose names for common constants which
 #    (a) are easily recognizable, and 
 #    (b) do not conflict with existing names
 #
 #  fgtok cannot prevent name conflicts
 #
 #  names will be in CAPITALS when inserted into code.
 #  caution:  max mantissa length = 20 characters; "third" is at the limit.
 #
 #  add   $DECLARE  NONE       ! to suppress generation of declarations
 #  add   $DECLARE  REAL*4     ! to change type of declarations
 #  add   $CONST_TOL  1.0D-10  ! to change equality test tolerance from 1.0D-12
 #
    zero     0.0d0
    half     0.5d0
    third    .3333333333333333333d0
    c4th     0.25d0
    c10th     0.1d0
    c20th     0.05d0
    c50th     0.02d0
    c100th     0.01d0
    c1000th     0.001d0
    one      1.0d0
    c3ov2     1.5d0
    two      2.0d0
    three     3.0d0
    four     4.0d0
    five     5.0d0
    six      6.0d0
    seven     7.0d0
    eight     8.0d0
    nine     9.0d0
    c10      10.0d0
    c20      20.0d0
    c100    100.0d0
    c1000    1000.0d0
    c10000    10000.0d0
    cbig10     1.0d10
    cbig30     1.0d30
    ceps4     1.0d-4
    ceps5     1.0d-5
    ceps6     1.0d-6
    ceps7     1.0d-7
    ceps8     1.0d-8
    ceps10     1.0d-10
    ceps30     1.0d-30


Home Top


TEX

This option should be use to process source code that is imbedded
inside a .tex file.  Only the "verbatim" text is processed; the
tex-part of the file is passed through without modification.

Home Top


NODECL

This option suppresses use of the idecl program, so that automatic
declaration of implicitly declared symbols is not performed.  This
step may be useful e.g. when codes are converted to fortran-90 and
begin to use fortran-90 keywords and intrinsics which might be
misinterpreted by the fortran-77 oriented idecl program as implicitly
declared symbols requiring a declaration to be generated.  By using
this option, r8_convert can be re-applied more safely to codes which
already use IMPLICIT NONE.

Home Top


Usage_recommendation

When converting a system with fortran INCLUDE files, it is best
to convert the INCLUDE files last.  This is because one of the
steps of r8_convert -- explicit declaration of all names (idecl)
-- can depend on IMPLICIT statements present only in the unconverted
INCLUDE file.

Home Top


f77name

The following C code (from portlib) illustrates the use of the
f77name #include file and F77NAME macro:

  /* cpclock.c */
  /*   cpu clock -- not for all systems */
 
  #include <stdio.h>
 
  ion names link differently depending on OS */
 
  #include "f77name.h"
 
  int F77NAME(cpclock)()
  {
    return clock();
  }

The macro expression F77NAME(cpclock) expands to either
cpclock or cpclock_ depending on the requirements of the 
system being used.  (On CRAY systems, a conversion to 
uppercase CPCLOCK is needed and this is typically handled
by invoking shell script code from a makefile).  Thus,
the function name CPCLOCK is made externally visible and 
callable by other fortran routines in the larger program.

Home Top


Fortran_Issues

Several portability problems arise...

Home Top


OS_interface

Problem:  fortran-77 offers no standard interface to operating system
and i/o functionality.  Unix "3f" routines (getenv, getcwd, getarg, etc)
are not consistently implemented.  Although "posix" standard interfaces
have been defined for such functions, real world implementation is rare.

   => general solution:  assemble a library which "encapsulates"
                  functionality requiring operating systems dependent
                  implementation.  Applications are coded to use this
                  library rather than calling the non-portable operating
                  systems interface routines provided by vendors.  
                  Examples of encapsulated functionality:
                    * fetch value of environment variables
                    * determine the current working directory
                    * file opens
                    * access to shell commands
                  The NTCC subroutine library "portlib" is an 
                  implementation of this strategy.  Subtopics
                  describe the "portlib" routines.

   => example of problem:  fixed record length random access files are 
                  a high performance i/o option in fortran.  But, to
                  open such a file, one must specify the record length
                  (RECL keyword in the fortran OPEN statement).  On
                  some systems, the record length is given in "bytes";
                  on other systems, it is given in 4 byte "words".

   => solution to example:  use the "portlib" routine "genopen" to open
                  direct access binary files.

            call genopen(
           >             ilun,   ! fortran i/o unit number
           >             name,   ! file name
           >             fstat,  ! file status 'OLD' 'NEW' or 'UNKNOWN'
           >             ftype,  ! file type 'ASCII' 'BINARY' or 'DIRECT'
           >             irecsz  ! record size (4 byte WORDS)
            )                    ! irecsz used only for ftype.eq.'DIRECT'

                  integer ilun,irecsz
                  character*(*) name,fstat,ftype


   => alternate solution:  use cpp code to compile in the correct record
                  length blocking factor (CAUTION:  see information on
                  cpp portability, elsewhere in the help document).

            #if __ALPHA || __SGI
                  nblkfac=1
            #elif __HP || __IBM || __SUN ||  __RS6000 || __LINUX
                  nblkfac=4
            #endif
                  open(unit=ilun,file=name,status=fstat,
                 >     type='DIRECT',recl=nblkfac*irecsz)

   => general comments:  from a maintenance standpoint, use of "portlib"
                  is superior.  If a new machine is added to the "list 
                  of machines to be supported", code changes are
                  concentrated in the portlib subroutines.  On the 
                  other hand, if machine-specific cpp code is scattered
                  through a user application, adding a new machine would
                  require visiting all of these locations in the source
                  code.

   => summary recommendation:  use portlib.

Home Top


Summary_of_Portlib_Routines

Date and Time

  CALL c9date(str_out)
  CALL c24date(str_out)
  CALL wall_seconds(isecnds_out)

  integer iarray_out(4)
  CALL cvt_days(isecnds_in,iarray_out)

CPU Timer

  REAL rcptime_out
  CALL cptimer(rcptime_out)

  REAL*8 rcptim8_out
  CALL cptimr8(rcptim8_out)

Difference in wall clock time (using f90 DATE_AND_TIME intrinsic)

  INTEGER, DIMENSION(8) :: tim1,tim2 ! returned by 2 calls to DATE_AND_TIME
  REAL :: tdiff     ! time difference in seconds
  REAL*8 :: tdiff8  ! time difference in seconds (real*8 precision)

  CALL DATE_AND_TIME(values=tim1)  ! f90 intrinsic
   ...
  CALL DATE_AND_TIME(values=tim2)  ! f90 intrinsic

  ! tim1(1) = year, tim1(2) = month), tim1(3) = day, etc...

  ! difference in time between 2 successive calls to DATE_AND_TIME:

  CALL wclock_diff(tim1,tim2, tdiff)

  CALL wclock_diff_r8(tim1,tim2, tdiff8)  ! real*8 precision

Environment variable Translation

  CALL sget_env(str_varname_in,str_value_out)

Process ID number

  CALL sget_pid(ipid_out)

Username

  CALL sget_user(str_username_out)

Current working directory

  CALL sget_cwd(str_cwd_out)
          additional related routines are available.

Create a (possibly multi-level) subdirectory <root>/<path>

  CALL gmkdir(str_root_in,str_path_in,iwarn_out)

Command line arguments

  CALL get_arg_count(i_numargs_out)
  CALL get_arg(i_argnum_in,str_argval_out)

Access to shell

  istat_out = jsystem(str_cmd_in)

Convert between fortran string and C string

  CALL cstring(char_arg,byte_array,cmd_str)

Form a filename from parts

  CALL ufilnam(str_path_in,str_filename_in,str_fullname_out)

Generic fortran file OPENs & related routine

  CALL genopen(ilun_in,str_filename_in,str_fstatus_in,
     >      str_ftype_in,i_recwords_in, io_status_out)

  irecsize_fac_out = nblkfac(iwarn_out)

File rename copy or delete

  CALL fcopy(str_old_filename_in,str_new_filename_in,i_status_out)
  CALL frename(str_old_filename_in,str_new_filename_in,i_status_out)
  CALL fdelete(str_filename_in,i_status_out)

Wildcard file deletion - all files in a directory

  CALL fclean_dir(str_path_in,i_status_out)

Wildcard file deletion -- wildcard file expression, one "*"

  CALL fwc_delete1(path_wildcard_expr,i_status_out)
  CALL fwc_delete(path,file_wildcard_expr,i_status_out)

Unique name for temporary file

  CALL tmpfile(str_prefix_in,str_filename_out,i_namelen_out)
  CALL tmpfile_d(str_prefix_in,str_filename_out,i_namelen_out)
        for difference btw tmpfile & tmpfile_d see detailed description.


Determine if file is ascii or binary

  CALL is_ascii(filename,iascii,ier)

Terminal oriented i/o

  CALL term_str_out(str_in)
  CALL term_char_in(char_out)

Pause in execution

  CALL gosleep(isecnds_in)

Data on floating point arithmetic 
  ***used by lots of numeric software***

  real ransr_out
  double precision dansr_out

  iansr_out = I1MACH(ival_in)
  ransr_out = R1MACH(ival_in)
  dansr_out = D1MACH(ival_in)

String Manipulation

  ilength_out = str_length(str_in)
    calling programs declare str_length to be integer

  CALL str_pad(str_inout)
  CALL uupper(str_inout)
  CALL ulower(str_inout)

Floating Point Error Handling

  CALL err_ini
  CALL err_end

Program Exit

  CALL good_exit
  CALL bad_exit
  CALL errmsg_exit(str_msg_in)

Home Top


Details_on_Portlib

Portlib is a collection of subroutines built to provide a common
interface to an assortment of non-portable vendor supplied operating
systems functions.

It is not possible to hide all portability issues in a single library.
If, for example, an application which deals with file paths and file 
names needs to be portable between UNIX and VMS, the application will
need explicit operating system dependent code beyond what portlib can
provide, since the syntax for naming files varies between UNIX and VMS.

Even so, the authors have found it useful to "hide" common portability
details through library calls, where possible.  The authors welcome 
suggestions for improvements, or for new routines to add-- preferably,
in the form of tested source code.  Send Email to:

    dmccune@pppl.gov.

A brief description of the `portlib' common interfaces follows, below.

Acknowledgements:
=================

Many thanks to Charles Karney for providing sources "sysdep.web" --
many of its algorithms and methods are incorporated here.  `portlib'
is really a merger of the methods used by Charles Karney to support
the Degas-2, and the methods of Doug McCune to support TRANSP.

The methods for dynamic memory allocation from "sysdep.web" were not
included.

Architectures Currently Supported by "portlib":
===============================================

VMS
UNIX:  DEC-alpha (DIGITAL UNIX), DEC-alpha (Linux), SUN, SGI, 
       IBM RS-6000, HP, Intel LINUX:  Fujitsu Fortran and
       Portland Group Fortran., Cray:  UNICOS.

Defects in this support are possible.  Report such problems to the
author, dmccune@pppl.gov.

Home Top


Date_and_Time_Routines

Portlib routines:

** c9date:
      CHARACTER*9 zdate
      CALL c9date(zdate)

returns zdate, Y2K compliant, in form  DDmmmYYYY -- e.g. "14apr1999".

** c24date:
      CHARACTER*24 zdate
      CALL c24date(zdate)

returns zdate in form DD-Mmm-YYYY hh:mm:ss.sss -- 
e.g. "14-Apr-1999 11:19:13.607".

** wall_seconds:

      INTEGER secnds
      CALL wall_seconds(secnds)

returns in secnds an integer value giving the elapsed time since
00:00 1 Jan 2000 (a negative number for calls in 1999).

You can do a (wall-clock, not cpu) timing of a section of code by
the following method:

      INTEGER wtime, wt1,wt2
      ...
      CALL wall_seconds(wt1)

      ...(code to be times)

      CALL wall_seconds(wt2)
      wtime = wt2-wt1
      WRITE(6,*) 'elapsed wall clock time:',wtime

When the number of seconds is large, it might be desirable to "change 
the units" of the time value into elapsed days/hours/minutes/seconds.
This can be done with:

** cvt_days:

      INTEGER tscnds            ! elapsed time, seconds, .gt.0, INPUT
      INTEGER tarray(4)         ! this time in days/hours/mins/secs
                                ! OUTPUT
      CALL cvt_days(tscnds,tarray)

example of usage:

      tscnds=500000
      CALL cvt_days(tscnds,tarray)
      WRITE(6,fmt) tscnds, (tarray(i),i=1,4)

where fmt is a format specifier such that the following output is 
produced:

 ...500000 seconds is:   5 days, 18 hours, 53 minutes, 20 seconds.

Obviously, cvt_days might be applied to elapsed cpu-time (in seconds)
as easily as elapsed wall-clock time (in seconds).

Home Top


Elapsed_CPU_Time

** cptimer:

      REAL zcptime         ! elapsed CPU time since start of process,
                           ! OUTPUT
      CALL cptimer(zcptime)

** cptimr8:

      REAL*8 zcptime       ! elapsed CPU time since start of process,
                           ! OUTPUT
      CALL cptimr8(zcptime)

(REAL*8 version of cptimer).

To use this to time a section of code, write

      REAL cptime,zc1,zc2
      ....
      CALL cptimer(zc1)

      ...(code to be timed)

      CALL cptimer(zc2)
      cptime=zc2-zc1       ! elapsed CPU time between cptimer calls.

The result may be well defined only for non-MPP applications.
Home Top


Elapsed_Wall_Clock_Time

A portlib routine is provided to compute the difference, in seconds,
between time stamps returned by two successive calls to the fortran-90
intrinsic DATE_AND_TIME.

The intrinsic returns an 8 word integer vector containing values for:
  year, month, day, time-zone, hour, minute, second, millisecond

E.g.:  2007, 9, 27, -240, 9, 52, 20, 452
       for Sept 27, 2007, 9:52:20.452 am EDT.

The portlib routines are: wclock_diff and wclock_diff_r8; usage is 
summarized here:

  INTEGER, DIMENSION(8) :: tim1,tim2 ! returned by 2 calls to DATE_AND_TIME
  REAL :: tdiff     ! time difference in seconds
  REAL*8 :: tdiff8  ! time difference in seconds (real*8 precision)

  CALL DATE_AND_TIME(values=tim1)  ! f90 intrinsic
   ...
  CALL DATE_AND_TIME(values=tim2)  ! f90 intrinsic

  ! difference in time between 2 successive calls to DATE_AND_TIME:

  CALL wclock_diff(tim1,tim2, tdiff)      ! tdiff in seconds

  CALL wclock_diff_r8(tim1,tim2, tdiff8)  ! tdiff8 in real*8 precision

NOTE: the time zone shift data is ignored by wclock_diff[_r8].  In effect,
the code assumes that the two successive calls to DATE_AND_TIME are made
in the same time zone.

Home Top


Environment_Variable_Translation

(on VMS systems, this routine translates a VMS "Logical Name").

** sget_env:

      CHARACTER*(*) varname      ! name of variable, INPUT
      CHARACTER*(*) value        ! translation value, OUTPUT

      CALL sget_env(varname,value)

For example:  "call sget_env('HOME',hdir)" would set the character
variable "hdir" to a string specifying the user's login directory,
on a UNIX system.  On a VMS system, this would be done with 
"call sget_env('SYS$LOGIN',hdir)".

If the name passed is that of an environment variable (logical name)
which does not exist, then the value returned is blank (' ').
Home Top


Command_Line_Arguments

** get_arg:

      INTEGER i            ! request to fetch i'th argument, INPUT
      CHARACTER*(*) arg    ! value of argument returned, OUTPUT
      CALL get_arg(i,arg)

** get_arg_count:

      INTEGER numargs      ! number of command line arguments, OUTPUT
      CALL get_arg_count(numargs)

These routines are supported on both VMS and UNIX.  The notion of
command line arguments is explicitly supported in UNIX (and the c
programming language).  On VMS, a runtime library call 
(lib$get_foreign) is used to fetch the entire command line 
(excluding the command itself).  The number of arguments on the
command line is defined as the number of (blank delimited) words.
For an item containing imbedded blanks to be counted as a single
word, it must be enclosed in double quotes.

Home Top


Other_Process_and_Environment_Info

==> Get Process ID number:

** sget_pid:

      INTEGER ipid
      CALL sget_pid(ipid)

The pid can be useful, e.g. for creating a unique name for a 
temporary file.  Note that a VMS pid is usually longer than a
UNIX pid -- typically a positive integer of 9 digits.


==> Get Name of Current Host:

** sget_host:

      CHARACTER*(*) host_name          ! name of host, OUTPUT
      CALL sget_host(host_name)

This fetches the nodename of the current host.  Note, this is not 
guaranteed to be a fully qualified internet name, it depends on how
the host system was set up.  Applications which require a reliable
internet name may want to support an option to have this specified 
through an environment variable.

==> Get Username:

**sget_user

      CHARACTER*(*) user_name          ! the username string, OUTPUT
      CALL sget_user(user_name)

works on UNIX via translation of standard environment variables; on
VMS by means of a lib$getjpi call.

==> Determine if Process is Controlled from a Script:

      INTEGER iscript            ! =1 if a script, =0 if a tty, OUTPUT
      CALL isbatch(iscript)

On UNIX, iscript=0 is returned if "stdin" is the user's tty; iscript=1
is returned if "stdin" is anything else.  The behaviour on VMS is
essentially the same, though VMS terminology would yield a different
description.

==> Get/Set Current Working Directory:

** sget_cwd:

      CHARACTER*(*) dir_name            ! the directory path, OUTPUT
      CALL sget_cwd(dir_name)

** sget_dsk:  ** VMS ONLY **

      CHARACTER*(*) dsk_name            ! current working disk, OUTPUT
      CALL sget_dsk(dsk_name)

** showdefl:  ** VMS ONLY **

      CHARACTER*(*) dsk_name,dir_name  ! disk & directory, OUTPUT
      INTEGER ldsk,ldir                ! non-blank length of outputs,
                                       ! OUTPUT
      CALL showdefl(dsk_name,ldsk,dir_name,ldir)

** sset_cwd:  ** UNIX ONLY **

      CHARACTER*(*) dir_name           ! directory to move to, INPUT
      INTEGER ierr                     ! completion code, OUTPUT, 0=OK.
      CALL sset_cwd(dir_name,ierr)

Notes:  A full VMS path specification usually has a form like:

      DSK1:[abc.def.xyz].

"sget_dsk" would return "DSK1:"; "sget_cwd" would return 
"[ABC.DEF.XYZ]"; showdefl would return both.

UNIX directory paths lack the notion of a disk-- hence, no diskname
translation.

A VMS method for changing the current working directory has not been
found.

Home Top


Make_a_subdirectory

** gmkdir (subroutine):

      CHARACTER*(*) root     ! root directory (must exist), INPUT
      CHARACTER*(*) path     ! relative path from root, INPUT
      INTEGER istat          ! completion code (returned), OUTPUT
      CALL gmkdir(root,path,istat)

If the directory (root/path) does not exist, create it, along
with any intermediate directories as needed to generate the
entire path.

"root" can be an environment variable or a string of the form
a/b/c where "a" is an environment variable.  The environment
variable will be translated.

Home Top


Access_to_Shell

** jsystem (integer function):

      CHARACTER*(*) cmd      ! shell command to execute, INPUT
      INTEGER istat          ! status code returned by jsystem
      istat = jsystem(cmd)

This executes the command "cmd" in the shell of the current OS
(UNIX:  sh, VMS:  DCL).  The command must be valid in the language
of this shell.  The status code set by execution of this command
is returned.

Home Top


Build_Filename_from_Parts

** ufilnam:

      CHARACTER*(*) path  ! directory path or start of filename, INPUT
      CHARACTER*(*) filename  ! completion of filename, INPUT
      CHARACTER*(*) fullname  ! complete file specification, OUTPUT

      CALL ufilnam(path,filename,fullname)

This routine essentially concatenates two filename components to 
form a single, complete filename.  On UNIX systems, an environment
variable translation is tried on the "first part" of the "path" 
argument (see examples).  On both VMS and UNIX systems, punctuation
is added at the point of concatenation if it is deemed necessary.

UNIX Examples (suppose the environment variable HOME translates to
"/u/dmccune"):

      call ufilnam('/u/dmccune','foo.tmp',fullname)
            => fullname = '/u/dmccune/foo.tmp'
      call ufilnam('/u/dmccune/','foo.tmp',fullname)
            => fullname = '/u/dmccune/foo.tmp'

(comment:  a trailing slash in the path specification is optional).

      call ufilnam('HOME','foo.tmp',fullname)
            => fullname = '/u/dmccune/foo.tmp'
      call ufilnam('$HOME','foo.tmp',fullname)
            => fullname = '/u/dmccune/foo.tmp'
      call ufilnam('HOME/tmp','../foo.tmp',fullname)
            => fullname = '/u/dmccune/tmp/../foo.tmp'
            (equivalent to fullname = '/u/dmccune/foo.tmp' if
                /u/dmccune/tmp exists).

(comments:  if the first character of "path" is neither "~" nor "/",
then name translation is tried on the leading part of "path" up to 
its first "/" character, if any.  If in the leading part of path, 
and if HOME can be translated, then, "HOME" and "$HOME" are 
equivalent.  The "path" argument does not have to be a full path to 
the target file; "filename" can include the trailing part of the path
as well as the file name itself).

VMS examples:

      call ufilnam('SYS$LOGIN','foo.tmp',fullname)
            => fullname = '$SYS$LOGIN:foo.tmp'
      call ufilnam('DSK1:[user.dmccune]','foo.tmp',fullname)
            => fullname = 'DSK1:[user.dmccune]foo.tmp'
      call ufilnam('DSK1:[user.','dmccune]foo.tmp',fullname)
            => fullname = 'DSK1:[user.dmccune]foo.tmp'

(comments:  if the last character of "path" is not one of 
{":",".","[","]","<",">"} then it is assumed to be a "disk like"
logical name, and a colon is inserted between "path" and "filename"
during the concatenation to form "fullname".  Translation of logical
names is never attempted, because if the fullname formed is ever used
e.g. in a fortran OPEN statement, logical name translation will be
handled as necessary by the VMS system).

Home Top


Open_a_File

** genopen:

The GENOPEN subroutine can be used for access to a standardized
fortran OPEN statement, for STATUS='OLD', 'NEW', or 'UNKNOWN'
fortran files of TYPE='ASCII', 'BINARY', or 'DIRECT'.  The
subroutine takes as arguments a fortran i/o "unit number", a
filename, a file STATUS, a file TYPE, a record size (in 4 byte
words) for direct access fixed record length i/o, and a status
code is returned.  For exact details:  see the source code.
The subroutine header follows:

      subroutine GENOPEN(lun,name,fstat,ftype,irecsz,ios)
C
      integer lun          ! logical unit number
      character*(*) name   ! file name
      character*(*) fstat  ! file status 'OLD' or 'NEW' or 'UNKNOWN'
      character*(*) ftype  ! file type 'ASCII' 'BINARY' or 'DIRECT'
      integer irecsz       ! record size ('DIRECT' files only)

** ierfnof (integer function):

      INTEGER ilun      ! fortran i/o "unit number" to use, INPUT
      INTEGER ierf      ! integer to hold value returned from ierfnof.
      ierf=ierfnof(ilun)

ierfnof returns the error code which on the current system means
"file not found".  This it does by actually trying to open a file
with a very strange name, 99.9999% sure not to exist, and then 
returning the resultant status code.

This can be used to examine an error code returned from GENOPEN,
to see if the error is "file not found" or some other error.

** nblkfac (integer function):

      INTEGER ierr      ! status code returned, 0=OK, 1=warn, OUTPUT
      INTEGER iblkfac   ! factor for specifying record length
      iblkfac = nblkfac(ierr)

This function returns a "units conversion factor" for the value to
be used in specifying record length in a fortran OPEN statement, on the
current machine.  On machines for which the record length specifier
"units" are 4 byte words, nblkfac returns 1.  On machines for which
the record length specifier is given in bytes, nblkfac returns 4.
Nblkfac returns ierr=1 if, at compile time, non of the cpp tests
succeeded, such that the units conversion factor appears to be 
unknown.  In this case the function value returned is 1.

Usage:

      INTEGER iwords
      data iwords/150/  ! want fixed length 150 (4 byte) word records

      ifac=nblkfac(ierr)
      if(ierr.eq.1) write(6,*) 
     >   'WARNING -- machine unknown, nblkfac=1 used.'

      OPEN(unit=lun,file=name,...,  recl = ifac*iwords )

Home Top


Rename_or_Copy_or_Delete_a_File

** frename:

      CHARACTER*(*) oldname      ! old filename, INPUT
      CHARACTER*(*) newname      ! new filename, INPUT
      INTEGER ier                ! status code returned, OUTPUT, 0=OK
      CALL frename(oldname,newname,ier)

renames a file.

** fcopy:

      CHARACTER*(*) oldname      ! old filename, INPUT
      CHARACTER*(*) newname      ! new filename, INPUT
      INTEGER ier                ! status code returned, OUTPUT, 0=OK
      CALL frename(oldname,newname,ier)

copies a file.

** fdelete:
      CHARACTER*(*) filename     ! file to delete, INPUT
      INTEGER ier                ! status code returned, OUTPUT, 0=OK
      CALL fdelete(filename,ier)

deletes a file.  (On VMS, all versions of the file are deleted, unless
a specific version of the file is specified in the filename argument).

A file can also be deleted using fortran OPEN(...) and 
CLOSE(...,status='DELETE') statements, but then an i/o unit number
must be allocated.

Home Top


Wildcard_file_delete

**fclean_dir:

      CHARACTER*(*) path         ! directory path
      INTEGER ier                ! status code returned, OUTPUT, 0=OK

      CALL fclean_dir(path,ier)  ! delete all files in (path)

      ! note: subdirectories not touched
      ! note: hidden files ("." 1st character) not touched

Error code is set if directory does not exist.  If there are already no
files in the directory, it is not considered an error.

**fwc_delete1
      CHARACTER*(*) wildcard_expr  ! directory/wildcard expression
      INTEGER ier                ! status code returned, OUTPUT, 0=OK

      CALL fwc_delete1(wildcard_expr,ier)  ! delete indicated files

Exactly one wildcard symbol "*" should appear in the filename field,
optionally following a path expression.  Examples of valid calls:

      CALL fwc_delete1('*.dat',ier)  ! delete *.dat in current working dir
      CALL fwc_delete1('foo/zork/*.dat',ier)  ! delete indicated files

**fwc_delete
      CHARACTER*(*) path         ! path to directory in which to delete
      CHARACTER*(*) wildcard_expr  ! file wildcard expression
      INTEGER ier                ! status code returned, OUTPUT, 0=OK

      CALL fwc_delete(path,wildcard_expr,ier)  ! delete indicated files

Exactly one wildcard symbol "*" should appear in wildcard_expr.
Examples of valid calls:

      CALL fwc_delete(' ','*.dat',ier)  ! delete *.dat in current working dir
      CALL fwc_delete('foo/zork','*.dat',ier)  ! delete indicated files

Home Top


Unique_Name_for_Temporary_File

a valid filename for the operating system currently in use.

**tmpfile_d:
      CHARACTER*(*) prefix     ! filename prefix specification, INPUT
      CHARACTER*(*) filename   ! full temporary filename, OUTPUT
      INTEGER ilfname          ! length of filename returned, OUTPUT

      call tmpfile_d(prefix,filename,ilfname)

in this routine, prefix contains a filename prefix only, without path
information.  

UNIX:  The filename returned will be of the form 
$TMPDIR/<prefix>nnnnn.tmp (if TMPDIR is defined)
or /tmp/<prefix>nnnnn.tmp otherwise.

VMS:  The filename returned will be of the form 
JUNK:<prefix>nnnnn.tmp, if logical name JUNK is defined, or 
SYS$LOGIN:<prefix>nnnnn.tmp otherwise.

Home Top


Is_File_ASCII

Sometimes it is useful to know, before trying to open a file with
fortran, whether it is an ASCII text file or not.

      CHARACTER*(*) filename  ! file in question, INPUT
  ! Outputs:
      INTEGER IASCII          ! =1: yes is ascii; =0: no, -1: error
      INTEGER IER             ! 0 if OK, `C' errno error code if not OK

      call is_ascii(filename,iascii,ier)

Home Top


Byte_Oriented_IO

In the portlib source code, "cftsubs.for" contains an interface to
C code for sequential binary i/o in 512-blocks.  This can be a basis
for a home-grown portable binary file format-- VMS receives binary
files via ftp in 512-blocks.  For details, see the source code.

Home Top


Terminal_Oriented_IO

** term_str_out:

      CHARACTER*(*) str      ! string to write, INPUT
      CALL term_str_out(str)

The character bytes in str are output to the terminal, immediately,
in a non-record-oriented way, and with buffer flushing.  Thus, "str"
can contain an "escape sequence" to cause a terminal to change modes,
e.g. a VT100 switch into "reverse video" mode.  Or, it can simply be
used to generate a prompt for user input that does not end in a 
carriage return / line feed.

** term_char_in

      CHARACTER*1 achar      ! character to read, OUTPUT
      CALL term_char_in(achar)

"achar" is read in from the terminal, immediately, without echoing,
and without the user being required to type a carriage return.

Home Top


Go_To_Sleep

** gosleep:

      INTEGER isecnds            ! time to sleep, seconds, INPUT
      CALL gosleep(isecnds)

Put the current process in "sleep" state for the indicated number
of seconds.

Home Top


Floating_Point_Information

portable, fortran callable c code versions of the standard functions

      integer function I1MACH(i)
      real function R1MACH(i)
      double precision function D1MACH(i)

are provided.  The meaning of the value returned is as described in
the source code; further information can be found at
 http://www.netlib.org  and at other standard sources as well.

Home Top


String_Manipulation

      integer function str_length(str)
         ...returns the non-blank non-null length of a string.
         ...returns zero if the entire string is blank or null.

      subroutine str_pad(str)
         ...replaces all null characters with blanks in str.

      subroutine uupper(str)
         ...converts all alphabetic characters in str to uppercase.

      subroutine ulower(str)
         ...converts all alphabetic characters in str to lowercase.

These routines are included in "portlib" to make the library be
self-contained.

      subroutine cstring(str,buffer,cmd)

      character*(*) str     ! fortran string
      byte buffer(*)        ! C null terminated string  **NOTE**
      character*(*) cmd     ! "2C": fortran->C; "2F": C->fortran

If cmd is "2C", copy contents of str into buffer and append a null;

If cmd is "2F", copy contents of buffer into str, stopping
when a null is encountered or len(str) is reached.  blank-pad
the string as needed.

**NOTE**  the byte array declaration shown in this example is not
portable.  A method for portable handling of byte arrays is covered
in the section on byte arrays.

Home Top


Floating_Point_Error_Handling

Most systems allow the error handling utilized in floating point
arithmetic to be controlled at compile time via the compiler command
line.  On SUN systems, however, an actual call is required inside the
code.

** err_ini:

      CALL err_ini

(called at program startup) causes SUN floating point error handling
to be set up as follows:  (a) signal abort on:  overflow, divide by 
zero, invalid.  (b) underflow to zero silently.  On non-SUN systems,
the call has no effect.

** err_end:

      CALL err_end

(called just before program exit) restores SUN default floating point
error handling.  This prevents an obnoxious warning from being printed
when the program exits.  On non-SUN systems, the call has no effect.

Home Top


Exit_a_Program

** good_exit:

      CALL good_exit

causes a program to "call err_end" and then "stop", for a normal
program termination.

** bad_exit:

      CALL bad_exit

causes a program to "call err_end" and then "call exit(1)" (UNIX) or
"call lib$stop( %val(2) )" (VMS), causing a status code to be set
which can be detected by the shell from which the program was run.

** errmsg_exit:

      CHARACTER*(*) msg      ! message to write before exiting, INPUT
      CALL errmsg_exit(msg)

causes the message "msg" to be written, followed by a call to
"bad_exit".

Home Top


Byte_Arrays

Problem:  Some fortran-77 codes need byte arrays and scalars.  These
are useful e.g. for creating C-style character strings.  But, there is
no standard way to declare these arrays.  Some compilers support "BYTE"
as a data type; others support "INTEGER*1" (neither is standard but
almost all popular fortran-77 compilers support one or the other).  Some 
fortran-90 compilers support neither of these options and appear
to allow no means of declaring a byte array.  On UNICOS Crary machines,
which have no fortran-77 compiler, CHARACTER*1 can be used.

   => general solution:  stick to fortran-77.  Use the cpp include file
      "byte_declare.h" (available in the NTCC fpreproc module).  Use
      the fpreproc package for fortran preprocessing (more information
      on fpreproc elsewhere in the help document).  In the source code,
      where a byte array must be declared, use "BYTE_DECLARE" to declare
      it:


      #include "byte_declare.h"
      ...
            BYTE_DECLARE buffer(512)
            Byte_Declare buffer2(512)

      BYTE_DECLARE is a macro which will be replaced with either BYTE
      or INTEGER*1, depending on the system type.  Note that with 
      fpreproc, fortran source code (excepting quoted strings and 
      comments) is folded to uppercase prior to preprocessing, so that
      "Byte_Declare" will have the same effect as "BYTE_DECLARE".
      The fpreproc respects the fortran tradition of case independence.

   => comment:  it is constructive to look inside "byte_declare.h":

      /* define BYTE_DECLARE as a macro for declaration of a byte array */

      #ifndef BYTE_DECLARE

      #if __SUN || __SUNOS || __HP
        #define BYTE_DECLARE byte
      #elif __CRAY
        #define BYTE_DECLARE character*1
      #else
        #define BYTE_DECLARE integer*1
      #endif

      #endif

      ...note that the macro name, BYTE_DECLARE, is longer than either
      possible substitution string; therefore, the cpp substitution will
      not result in the lengthening of a fixed format fortran source line
      across the 72 character limit.

   => recommendation:  use "byte_declare.h" rather than explicit cpp code
      to handle byte array declarations.

Home Top


Integer_and_Logical_DataTypes

Generally, portable code should use the bare datatypes

  INTEGER
  LOGICAL

rather than INTEGER*8, INTEGER*4, INTEGER*2, INTEGER*1 and
LOGICAL*1, LOGICAL*2, LOGICAL*4.  The INTEGER*n and LOGICAL*n
declarations are non-standard, and not supported by all 
manufacturers.  (There is a portable way to create byte
arrays which are INTEGER*1 on some systems.  See the section
on byte arrays).

Any LOGICAL function must be declared LOGICAL in the program unit from
which it is called.  The results of logical expressions should be
stored in LOGICAL variables, and the results of INTEGER expressions
in INTEGER variables; the types should not be mixed.  The conditional
clause in an IF statement should be a LOGICAL expression, not an
INTEGER expression.

Code which blurs the distinction between INTEGER and LOGICAL is
generally not reliably portable.

Home Top


int_precision.sub

This file, distributed with fgtok, can be used to clean up INTEGER
and LOGICAL declarations:

#  map all INTEGER*n to INTEGER
#  map all LOGICAL*n to LOGICAL
#  map INTEGER*1 to BYTE_DECLARE  
#
#    fgtok <input-file> <output-file> -s int_precision.sub
#
integer*1       BYTE_DECLARE

integer*2       INTEGER
integer*4       INTEGER
integer*8       INTEGER

logical*1       LOGICAL

logical*2       LOGICAL
logical*4       LOGICAL
logical*8       LOGICAL

Caution -- some f77 compilers allow integers of various sizes
to be declared on a single line, as in

INTEGER ii,jj*2,kk*4,mm(3)*1

such code is not portable, and is not cleaned up by using
int_precision.sub or intcon.csh.
Home Top


intcon.csh

This script, distributed with fgtok, can be used to clean up a
source code, with respect to INTEGER declarations.  It will also
insert the appropriate cpp #include file reference, if BYTE_DECLARE
is used.

#! /bin/csh -f
#!
#! from $1 make a source code that only declares INTEGER and LOGICAL
#! (no INTEGER*n or LOGICAL*n).  If INTEGER*1 is detected replace this
#! with BYTE_DECLARE.  At the end, check for BYTE_DECLARE; if it is
#! present, insert the appropriate #include file reference.
#!
#! the output is written to $2.  If the optional argument $3 is 
#! specified, this is a user supplied substitution table or list.
#!
  @ ier = 0 
  if ( $#argv < 2 ) then
    echo "?intcon.csh:  usage:"
    echo ">  csh -f  intcon.csh  old-source-file  new-source-file  [s-file]"
    @ ier++
  endif
#
  if ( $#argv > 0 ) then
    if ( ! -f $1 ) then
      echo "?intcon.csh:  not found:  $1"
      @ ier++
    endif
  endif
#
  if ( $?S_TABLE_DIR == 0 ) then
    echo "?intcon.csh:  set environment variable S_TABLE_DIR to point"
    echo " to the directory containing standard substitution table files:"
    echo " int_precision.sub, etc."
    @ ier++
  else
    if ( ! -f $S_TABLE_DIR/int_precision.sub ) then
      echo "?intcon.csh:  S_TABLE_DIR = $S_TABLE_DIR"
      echo " environment variable may be incorrectly set."
      echo " $S_TABLE_DIR/int_precision.sub:  file not found."
      @ ier++
    endif
  endif
#
  if ( $?CPP_HFILE_DIR == 0 ) then
    echo "?intcon.csh:  set environment variable CPP_HFILE_DIR to point"
    echo " to the directory containing standard .h files for library"
    echo " name substitions:  byte_declare.h, etc."
    @ ier++
  else
    if ( ! -f $CPP_HFILE_DIR/byte_declare.h ) then
      echo "?intcon.csh:  $CPP_HFILE_DIR = $CPP_HFILE_DIR"
      echo " environment variable may be incorrectly set."
      echo " $CPP_HFILE_DIR/byte_declare.h:  file not found."
      @ ier++
    endif
  endif
#
  if ( $ier > 0 ) then
    exit 1
  endif
#
  cp $1 $1.tmp0
#
  if ( -f $2 ) then
    echo "%intcon.csh:  old version of $2 moved to $2~"
    mv $2 $2~
  endif
#
  if ( $#argv >= 3 ) then
    set s0 = (int_precision.sub $3)
  else
    set s0 = int_precision.sub
  endif
#
# OK start working.
# 1.  standardize all INTEGER and LOGICAL declarations, 
#
  fgtok $1.tmp0 $1.tmp \
    -p $S_TABLE_DIR -s $s0 -cmt "intcon.csh conversion"
#
# 2.  if the source code uses BYTE_DECLARE to create byte arrays,
#     insert the approriate header file.
#
  set libtest = \
    `cpcheck $1.tmp6 -p $CPP_HFILE_DIR -incl_cpp byte_declare.h`
  if ( "$libtest" == "present" ) then
    fgtok $1.tmp $1.tmp7 -incl_cpp library_names.h
  else
    cp $1.tmp $1.tmp7
  endif
#
# 3.  final step:  cleanup; warn about line length
#
  mv $1.tmp7 $2
  rm $1.tmp*
#
  fgtok $2 -w72

Home Top


Floating_Point_Precision

The problem here is that fortran-77 standard does not define the actual
floating point precision associated with the data type declarations 
"REAL" and "DOUBLE PRECISION".  Thus, on Cray machines, "REAL" maps to 
a 64 bit word, while on conventional unix workstations, "REAL" maps to
a 32 bit (IEEE) floating point word.

To have any hope of creating numerical codes which yield equivalent
accuracy on all platforms, it is necessary to systematically use 
declarations for floating point arrays and scalars which have the 
same meaning, in terms of precision, on all machines.

Given the emergence of 64 bit architectures and the ever increasing
speed of current hardware, the time has arrived where one should insist
that 64 bit arithmetic be used in all numerical codes for which the
attributes "portable" and "reliable" are to be claimed.  Numerical
software vendors (e.g. NAG) made this choice a long time ago, by 
no longer supporting 32 bit versions of their numerical subroutine
libraries on most brands of unix workstations.  This decision is
well founded-- the labor involved in coping with problems arising
from insufficiently accurate computation in numerical simulations
is no longer justified by the modestly greater speed and smaller
memory requirements of 32 bit arithmetic.

Many authors have tried to standardize on 64 bit arithmetic by using 
"autodouble" compiler options ("-r8" on many unix systems) to 
automatically convert codes to double precision at compile time.  
The problem is, this approach is not portable, because support for
"autodouble" compile time switches are not consistently provided on 
all machines.  For example, use of "autodouble" compilers affects the
size of INTEGER as well as REAL declarations, but, not consistently
from machine to machine.  Also, experience shows that intractible 
and confusing problems can arise when one tries to put together 
large codes from interdependent fortran subroutine libraries, some
of which are compiled with an "autodouble" switch, and some without.
***The use of "-r8" or other compiler "autodouble" switches is not 
recommended!

In fortran-90, the language standard provides a mechanism for portably
specifying floating point precision:  the KIND= qualifier in floating 
point data type declaration statements.  This is a good solution and 
should be used in fortran-90 codes.

In fortran-77, there is no good standards conforming solution, but, in
practice, a universally effective solution does exist:

  * declare all floating point reals using REAL*8
  * declare all complex numbers using COMPLEX*16.

There is some subtlety involved in the conversion of fortran-77 codes
to follow this practice, but, the portability tools provided in the NTCC
modules library do permit a largely automated and reliable method of
conversion.  It is a multi-step process, detailed in the subtopics.
If all steps are indicated, a script can be invoked (see the section
on automatic conversion).

Caution:  the methods described are for fortran-77 codes only; however,
fixed format fortran-90 sources that are written in a fortran-77 style
and only use one or two fortran-90 features, may also be convertible
by the means described here.

Home Top


Automatic_Conversion

The NTCC module `ftoken' includes tools for converting source code.
Included with the `fgtok' tool are the scripts,

    r8con.csh       -- convert source files
    r8con_incl.csh  -- convert INCLUDE files

which will carry out "complete" conversions of fortran-77 source
code to REAL*8/COMPLEX*16 precision.  The procedure for using the
script is as follows:

  (1) define the environment variable S_TABLE_DIR to point to the
directory containing the "standard" fgtok substitution tables
r8_precision.sub, etc.  This is usually the same as the directory
containing the r8con.csh script itself.

  (2) define the environment variable CPP_HFILE_DIR to point to the
directory containing "standard" cpp .h files used for fortran
preprocessing at compile time.  This would normally be the home 
directory of the NTCC `fpreproc' module, and will contain files
such as "blas_names.h"-- a header file that forces codes to use
single precision or double precision BLAS libraries, depending
on the precision class of the current machine architecture.

  (3) invoke the script with the command:

  > csh -f r8con.csh input.f output.f user_subs.table CONST

where "input.f" is the input, unconverted source file, "output.f"
is the output, converted source file.

The arguments "user_subs.table" and "CONST" are optional.  The user
may specify 0 or more user supplied `fgtok' name substitution table
filenames.

The presence of the argument CONST means, add a header file to the
code to define the CONST macro, and represent floating point constants
e.g "1.2345d-15" instead as CONST(1.2345,-15).  This will expand to
"1.2345D-15" or "1.2345E-15", depending on which form specifies a 64
bit floating point constant in the current environment.  In a sense
CONST gives the most precise portability conversion for floating point
constants, but, it has the disadvantage of significantly expanding the
lengths of code lines, causing many to wrap beyond the 72 character
limit.  The default (no CONST argument) simply maps floating point
constants to their D representation; on Crays these can be mapped
to E (64 bit) form, using a compiler switch.

For convenience the user may want to write a jacket script that carries 
out the above steps (1) and (2) in a way appropriate to the local 
environment, and then runs (3).

The script r8con.csh carries out the following steps.

  step 1:  using the `fgtok' program, redeclare all floating point
           scalars and arrays to REAL*8 and/or COMPLEX*16.  
           Also carry out any user specified name substitutions.
           --see "Declarations".

  step 2:  Use the `idecl' program to generate explicit declarations
           for all symbols not already explicitly declared.
           --see "Make_Explicit"

  step 3a: Insert the "IMPLICIT NONE" directive in every program unit,
           and remove any pre-existing IMPLICIT statements.  `idecl'
           should run before this step, because it extracts data from
           IMPLICIT statements other than "IMPLICIT NONE".

  step 3b: using fgtok, convert all floating point constants to
           "D-exponent" form, e.g. 1.0 => 1.0D0.
           --see "Constants"

  step 4:  using fgtok, convert all type conversion intrinsic
           functions (REAL, FLOAT, AIMAG, CMPLX, etc). to a
           standard form.  These instrinsics will require further
           conversion at compile time (using fpreproc), because
           on each machine the correct intrinsic name depends on 
           whether REAL*8 is single precision (i.e. Crays), or
           double precision (i.e. most other machines).
           --see "Conversion_Intrinsics"

  step 5:  using fgtok, convert all non-generic intrinsic functions
           to their generic form (i.e. DSQRT -> SQRT, etc.).
           --see "Generic_Intrinsics"

  step 6:  deal with standard numerical subroutine library calls
           (BLAS, LAPACK, SLATEC, etc).  For this, fpreproc will
           be used at compile name, because the correct name choice
           is different between Crays and most other machines.
           Standard #include .h files are provided with the fpreproc
           module, to define the necessary library name mappings.
           --see "Libraries"

  step 7:  check for source code lines of length greater than 72
           characters (use fgtok -w72); correct such any such lines
           with a text editor, or try fgtok -fix72.
           --see "Line_length"

This sequence is applied once to the source code and INCLUDE files,
to put it permanently and maintainably into a portable state.

The r8con_incl.csh script carries out a subset of the above steps,
appropriate for INCLUDE files.

Home Top


Declarations

(step 1 of the automatic conversion script).

The requirement here is to explicitly convert every floating point
declaration type in the code to REAL*8 / COMPLEX*16.  The table 
driven name substitution program `fgtok', part of the NTCC `ftoken'
module, is used.

Example of use:

  fgtok  input.f  output.f  -p $FGTOK_DATA -s r8_precision.sub

The switch -s specifies a substitution table file;
the switch -p specifies a path to a directory containing substitution
table files.

`r8_precision.sub' is distributed with the fgtok program; its
contents are:

#  map all real & complex types to real*8 and complex*16

real            REAL*8               1
real*4          REAL*8               1
real*8          REAL*8               1
double precision        REAL*8       1

complex         COMPLEX*16           1
complex*8       COMPLEX*16           1
complex*16      COMPLEX*16           1
double complex          COMPLEX*16   1

To complete the conversion, the above fgtok command should be
applied to all components of a given code-- not overlooking INCLUDE
files.

As the next step, all implicit declarations will be made explicit,
and the "IMPLICIT NONE" statement will be inserted in all program
units.

Home Top


Make_Explicit

(step 2 of the automatic conversion script).

The conversion of a code to IMPLICIT NONE naturally requires that
anything previously implicitly declared must now have an explicit
declaration generated.

This is tedious to do by hand.  It can also be tricky to automate:
an automation tool must be able to parse input fortran code 
sufficiently to figure out (a) which symbols exist, and (b) which
are user symbols that need to be declared.  In doing this, it
needs to look at INCLUDE files, so that symbols declared in
INCLUDE files are not incorrectly re-declared.

The NTCC modules library tool `idecl' (in the `ftoken' module) will
do the trick for fixed format fortran codes:

  idecl input.f output.f -r8

For codes using the fortran default implicit rules, this generates
declarations of undeclared symbols in each program unit,
by the rule that any symbol whose first letter is I,J,K,L,
M or N is declared INTEGER, and all others are declared REAL*8.  If
the "-r8" switch is omitted, the non-INTEGERs are simply declared 
REAL.

`idecl' also supports a -I switch, through which paths to INCLUDE
files may be specified.

For codes with non-default IMPLICIT rules, `idecl' parses any
IMPLICIT statements it finds, and modifies its generated declarations
accordingly.  This means `idecl' should be used BEFORE using `fgtok'
to replace the non-default IMPLICIT statements with "IMPLICIT NONE"
statements.

Home Top


IMPLICIT_NONE

After making all declarations explicit, implicit data declarations
should be banned, by inserting "IMPLICIT NONE' in all program units.
If this is not done, then, under the default fortran rules, variables
added to the program which are implicitly declared will have the 
wrong precision.

After using `idecl' to generate explicit declarations, using any
IMPLICIT rules found in the source, use

  fgtok input.f output.f -impno

to get rid of all pre-existing IMPLICIT statements and insert an
IMPLICIT NONE statement at the head of each program unit.

Home Top


Constants

(step 3b of the automatic conversion script).

When converting a code to REAL*8 precision, hard coded constants
should also be converted.  Failure to do this results in a
portability hazard:  if a hard coded constant in a subroutine
argument list is taken as REAL*4 by a compiler, and this is
passed to a routine which expects REAL*8, the result is 
unpredictable-- a bug that can be quite hard to find.

To avoid this, there are two recommended approaches.  One is to use
"fgtok -expd", which converts all constants to explicit "D-exponent"
form, e.g. "1.0D0" rather than just "1.0".

fgtok can do this as follows:

  fgtok input.f output.f -expd -w72

The -expd switch causes fgtok to convert all constants in input.f,
writing the results in output.f.  The -w72 option can be used to
check for source lines that are widened beyond the 72 character
limit.

Incidentally, on Cray/UNICOS, a compiler switch is used to prevent
the "D-exponent" constants from being interpreted as quad-precision
words.

A second recommended approach is to use a CONST macro expansion,
which can be applied by:

  fgtok input.f output.f -const -incl_cpp fp_const.h -w72

This command will insert a reference to the (fpreproc) "fp_const.h"
header file which defines the CONST macro, and, convert all floating
point constants to the form CONST(mantissa,exponent).  The macro will
expand at compile time to either <mantissa>E<exponent> or 
<mantissa>D<exponent> depending on which format gives a 64 bit 
representation on the current machine.

From the point of view of robust portable representation of floating
point constants, use of the CONST macro is probably superior.  It has
the drawback, however, of significantly lengthening source lines
containing arithmetic with many hand coded floating point constants.
Such lines will need manual repair to maintain readability (one
can try the fgtok -fix72 option but hand repair is generally
superior).  The -expd switch can also lengthen source lines past
the 72 character limit, but the amount of lengthening is much less.

Home Top


Conversion_Intrinsics

(step 4 of the automatic conversion script).

The fortran-77 conversion intrinsics are a set of functions used
to convert between data types, form complex numbers from two
reals, or extract the real or imaginary part of a complex number
to a real.  These functions are:

  REAL, DBLE, FLOAT, DFLOAT, AIMAG, DIMAG, CMPLX, DCMPLX
  (DIMAG and DCMPLX are non-standard but widely available).

These present a portability problem with respect to control of
precision, because, on systems where REAL*8 counts as double
precision, typically the intrinsics DBLE, DFLOAT, DIMAG, and
DCMPLX must be used to avoid loss of precision in floating
point calculations; on systems where REAL*8 counts as single
precision, REAL, FLOAT, AIMAG and CMPLX should be used, and,
DIMAG and DCMPLX (which are non-standard) do not always exist.

To make matters worse for authors of automated code conversion
tools, the intrinsic function "real" represents a name conflict
with "real" as used to declare symbols.

The recommended portability conversion solution is as follows:

  (a)  convert the code to use as standard names AREAL, AIMAG,
       and CMPLX, for these functions, and,

  (b)  use cpp pre-processing to map AREAL to REAL or to DBLE
       depending as REAL*8 counts as single or double precision,
       and similarly to redefine (or not) AIMAG and CMPLX.

The NTCC ftoken module's fgtok program includes a substitution 
table file "r8_icomplex.sub" to implement part (a).  Because of 
the "REAL" name conflict issue, this should only be applied AFTER 
the basic REAL*8 conversion of the code has been done, otherwise 
one risks ending up with code which tries to declare symbols of 
type "AREAL".

For part (b), the NTCC fpreproc module contains a cpp .h file 
"f77_dcomplx.h" which conditionally defines AREAL, AIMAG and 
CMPLX name substitutions at compile time.  To set up the code
to use both parts (a) and (b) do this:

  fgtok  input.f  output.f  \
         -p $FGTOK_DATA  -s r8_icomplex.sub  -incl_cpp f77_dcomplx.h

This finds and applies the "r8_icomplex.sub" substitution table
(in directory $FGTOK_DATA); it also inserts the line

#include "f77_dcomplx.h"

at the head of the source file.  At compile time, a copy of the
#include file will have to be placed where the preprocessor can
find it (or use the preprocessor's -I switch to specify directories
to search for user #include files).

Home Top


r8_icomplx.sub

This substitution table comes with the fgtok program.

#  map all real <--> complex conversion intrinsics to standard names.
#  source code should 
#
#  #include f77_dcomplx.h 
#
#  this is REQUIRED because the non-standard name AREAL is used (to undo
#  fortran's name conflict btw the REAL intrinsic and REAL data type).
#
#  for environment-dependent compile time remapping.
#
#  usage:
#     fgtok <input-file> <output-file> -s r8_icomplex.sub -w72
#
#  <input-file> means the old version fortran source; <output-file>
#               means the new version; these should be distinct files.
#
#  if <output-file> is omitted, stdout is used.  If both <input-file>
#  and <output-file> are omitted, stdin and stdout are used.
#
#  recommended switches:
#   -w72 ... warns of any occurrences of line length .gt. 72 characters
#
#  when converting codes to real*8 usage, use r8_precision.sub *first*
#  to remove all REAL data type declarators, *before* using this 
#  substitution table.
#
#    ... D. McCune 2 Apr 1999

real            AREAL
dble            AREAL
areal           AREAL

float           AREAL
dfloat          AREAL

aimag           AIMAG
dimag           AIMAG

cmplx           CMPLX
dcmplx          CMPLX
Home Top


f77_dcomplx.h

#ifndef f77_dcomplx      /* cpp code for rename of double complex */
#define f77_dcomplx      /* fortran intrinsic functions */

#ifndef __SINGLE_PRECISION
#ifndef __DOUBLE_PRECISION

#ifdef __CRAY
  #define __SINGLE_PRECISION
#else
  #define __DOUBLE_PRECISION
#endif

#endif  /* ifndef __DOUBLE_PRECISION */
#endif  /* ifndef __SINGLE_PRECISION */

#ifdef __DOUBLE_PRECISION       /* workstation f77 -- double precision */

  #define AREAL DBLE
  #define AIMAG DIMAG
  #define CMPLX DCMPLX

#else                         /* CRAY f77 source -- single precision */

  #define AREAL REAL
  #undef AIMAG
  #undef CMPLX

#endif  /* DOUBLE or SINGLE_PRECISION */

#endif  /* ifndef f77_dcomplx */

Home Top


Generic_Intrinsics

(step 5 of the automatic conversion script).

Fortran-77 contains a large number of intrinsic functions for
basic math:  SQRT, EXP, MIN, MAX, etc.  These come in "generic"
forms which adapt to the precision of the input variables given,
and "non-generic" forms which do not adapt.  For portability of
REAL*8 precision code, the "generic" forms MUST be used.  The
use of a non-generic intrinsic like DSQRT creates a problem,
because on a Cray machine, a DSQRT with a REAL*8 argument will
be interpreted as a user defined function which will cause an
undefined reference at load time.

To avoid this, use fgtok to convert all intrinsic function 
references:

  fgtok  input.f  output.f  -p $FGTOK_DATA  -s r8_intrinsic.sub

where $FGTOK_DATA gives the directory containing the "standard"
fgtok substitution table files (distributed with the fgtok NTCC
module).

Home Top


Libraries

(step 6 of the automatic conversion script).

It is likely that a REAL*8 code will have to link to libraries
which were not built with the REAL*8 style.  Typically, this means
that at compile time, a portable source needs to specify a call
to a different routine, according as REAL*8 is single precision
or double precision in the current environment.

For example, a REAL*8 application would want to use the BLAS routine
DAXPY on workstations and linux machines, but it would need to use
SAXPY for correct results on Cray machines.

The recommended way to cope with this is to use cpp-style name
substitution at compile time.  This amounts to preprocessing with
logic as follows:

#if _DOUBLE_PRECISION
  #define SAXPY DAXPY
#else
  #define DAXPY SAXPY
#endif

This is fine, except that of course one would prefer to have a
single cpp #include file which appropriately renames all of the
BLAS routines.

This is provided in the fpreproc NTCC module as "blas_names.h"
Similar #include files are provided for the LAPACK library.  A
single header file, "library_names.h", is provided which will
#include all available library names conversion header files.

Many authors have resorted to imbedding REAL*8 converted copies 
of BLAS and LAPACK routines in their source code, usually converted
with a -r8 switch at compile time.  This is not recommended for
several reasons:

  (a)  the code fails to use the locally available math library
       routines with architecture specific optimizations,
  (b)  the author has extra code to maintain, and
  (c)  if the author was not careful to rename the routine, this
       creates a name conflict with the originally library, which
       could cause a serious and hard to find bug in a larger
       program which sought to incorporate the author's work
       as a subprogram.

Unfortunately, rename #include files do not yet exist for every
library.  The NTCC modules library seeks contributions from the
user community (see Email addresses at top of document).

Scripted implementation of this step:  the program `cpcheck',
part of the ftoken module, is used to determine if a source 
code needs to use the "library_names.h" header file.  If so,
it is inserted using

  fgtok input.f output.f -incl_cpp library_names.h

It is worth making one forther remark on the topic of numerical
libraries.  Many numerical codes use the routines "R1MACH" and 
"D1MACH" to determine the local floating point arithmetic environment.
In REAL*8 precision codes, one wants to use either R1MACH or D1MACH,
depending as the current machine has REAL*8 as single or double 
precision.  Therefore, the mapping of the R1MACH and D1MACH names 
is also included in the blas_names.h and lapack_names.h files.

Portable versions of "R1MACH" and "D1MACH" are in "portlib" as C code.
Use of these versions is strongly encouraged.

Home Top


Line_Length_Check

(final step of the automatic conversion script).

The various transformation operations described in this section
can sometimes cause the length of a line of code to be increased.
If it increases beyond the 72 character limit of fixed format
fortran, a repair is necessary.  To find such lines:

  fgtok input.f -w72

One can also use `fgtok' to do an automatic fix to such lines, but, 
for reasons of readability, a hand edit is usually preferable.

If the fgtok fix (which involves breaking a line at a word boundary 
and inserting an extra continuation line) must be used, one can try

  fgtok  input.f  output.f  -fix72

but it is recommended to examine the results for readability.
Home Top


Bugs

The user should view the `fgtok' and `idecl' tools alluded to in 
this section as "beta" code.  Although a large amount of fortran
source has been run through these tools, it is not possible to
guarantee that the tools will produce satisfactory results for
every fixed format fortran source in existence.  Indeed there
are undoubtedly codes out there that will cause problems for
these tools.

Users should exercise due caution.  Please notify the author
of bugs (dmccune@pppl.gov).
Home Top


Unconverted_Libraries

It may not be practical to convert all fortran source libraries
to REAL*8 form.  Nevertheless, one might want to call routines in
an unconverted library.

For example, the ancient Ufiles library implements a single 
precision file format for numerical data.  This library is coded
with REAL precision and will not be changed.  (The Ufiles library
is available as an NTCC library module).

REAL*8 codes might need access to Ufiles.  It would be convenient
to be able to generate an REAL*8 interface to Ufiles.

For this reason, the `ftoken' module incorporates a code generator
tool `r8real'.  This program uses an analysis of the target library
source code to generate a fortran-90 REAL*8 interface to a non-REAL*8
library.  It does this by writing a subroutine which has REAL*8
arguments and arrays, but allocates local copies of the arguments
of type REAL.  The REAL*8 input data is copied into the local arrays,
the original subroutine is called using the local arrays, and then
output data are copied from the local arrays back into the REAL*8
data arrays passed to the interface routine.

Home Top


example

Suppose we want to call this routine:

      REAL function foosum(v1,n)
      INTEGER n
      REAL v1(n)

      foosum=0.0
      do i=1,n
        foosum=foosum+v1(i)
      enddo

      return
      end

The fortran-90 generated interface would look like this:

!----------------------------------------------------------
! r8real -- generated (f90 fixed form) real*8 interface to:  FOOSUM
      REAL*8 FUNCTION R8_FOOSUM(
     > R8_V1,N)

      external FOOSUM
      REAL FOOSUM

! argument declarations
      REAL*8 R8_V1(N)
      INTEGER N

! local (automatic array) declarations

      integer alloc_stat
      REAL, pointer :: V1_act(:)
      REAL, pointer :: V1_ref(:)

! allocation of working arrays...

      ALLOCATE(V1_act(N)
     >   ,stat=alloc_stat)
      if(alloc_stat.ne.0) call errmsg_exit(
     >  ' ?R8_FOOSUM -- V1_act ALLOCATE error!')
      ALLOCATE(V1_ref(N)
     >   ,stat=alloc_stat)
      if(alloc_stat.ne.0) call errmsg_exit(
     >  ' ?R8_FOOSUM -- V1_ref ALLOCATE error!')

! executable code:  copy for input

      V1_act=R8_V1
      V1_ref=R8_V1

! call to original routine:  FOOSUM

      R8_FOOSUM=FOOSUM(
     > V1_act(1),N)

! copy back outputs if modified.

      where(V1_act.ne.V1_ref)
         R8_V1 = V1_act
      end where
      DEALLOCATE(V1_act)
      DEALLOCATE(V1_ref)

! exit
      return
      end

Of course, precision is lost-- but this is unavoidable, if the
starting premise is that we are not going to convert our single
precision code.

Home Top


Procedure

To create a REAL*8 interface to a single precision REAL library,
use the "r8igen.csh" script provided with the `r8real' program
in the `ftoken' module.  In a directory containing the sources
for the single precision library:

> csh -f r8igen.csh  r8_output.f  r8_output.sub

For each global name <name> in source files *.f in the source file
directory, an interface routine r8_<name> is generated and appended
to r8_output.f.  The file r8_output.sub will contain a substitution
table file (suitable for use by fgtok) with a mapping from the
original names to the generated r8_ prepended names.

Or, to create a REAL interface to a double precision REAL*8
library, use the similar procedure

> csh -f r4igen.csh  r4_output.f  r4_output.sub

Home Top


R4_option

The original and default standard use of `r8real' is to create a
REAL*8 interface to REAL code, to make it easier to call subroutines
in legacy 32 bit libraries.  However, the program can also be used to
generate a REAL interface to REAL*8 code.  This is done by invoking
the "-r4" switch.  Thus:

  > r8real foo_sub.f r4_foo_sub.f -r4

generates in r4_foo_sub.f a REAL interface to presumed REAL*8 code
in foo_sub.f.  One can also use the r4igen.csh script provided with
the software.

Home Top


Caveats

The r8real program has the following limitations:

(1)  it cannot handle interfaces defined by fortran-77 ENTRY 
statements.

(2)  it needs to know the full shape of each array argument.
Therefore, REAL arrays declared with a trailing "*" array
dimension result in code that is not immediately usable-- it
must be edited first.  For the method to work, the size of
the array must be known at runtime (so that a matching size 
local copy can be allocated).

(3)  the generated code is inefficient, because the code
generation is done without knowledge of which arguments of
the interfaced routine are input and which are output.  So,
it must assume that all are both input and output.  Where
efficiency is a concern, the programmer may want to edit
the r8real output generated code.

Home Top


Actual_Usage_Experience

The r8real program was used to generate REAL*8 interfaces to
the single precision NTCC modules "Ufiles" and "TrGraf".  These
interfaces were then tested with REAL*8 converted versions of
the test driver programs "ugraf1" and "ugraf2", and they work.

Although the generated i/o routines are "inefficient" in terms
of causing some unnecessary copying of data (since r8real does
not know what is input and what is output), this does not have
a noticeable impact on performance, since the routines are
actually generating reads/writes from/to disk or from/to a
graphics terminal, which is much slower yet.

In the "Ufiles" and "TrGraf" modules source code, there are
routines r8*.for which contain the r8real generated code.
Home Top


Mixed_Language_Issues

The emphasis here is on going back and forth between fortran and 
C or C++.

There are two main issues:  

  (1)  making the right global names visible at load time, and

  (2)  passing compound data types (fortran-77 CHARACTER variables,
       and fortran-90 compound types).

These are considered in the subtopics.  Problem (2) is only partially
addressed just for CHARACTER variables.  Generally, there are no
standards for implementations of fortran compound data types, and
the implementations vary from vendor to vendor.  Mixed language
programming exposes these variations, creating serious portability
problems.  The most practical advice is to try to avoid passing
fortran compound data types through mixed language interfaces.

Home Top


Global_Names

Problem:  handling global names in the context of mixed language fortran
and C programming.  On many systems, C routine names need to be 
"decorated", typically by appending an "_", in order for the name 
to be resolved at load time.  On other systems, the trailing "_" must
NOT be present.

   => example:

      fortran code:

            program foo
            external icsum

            ii = icsum(2,2)     ! add 2 and 2
            write(6,*) ii       ! ...print 4.

            stop
            end

      C code:

            /* trailing underscore required or forbidden */
            /* (depending on system) for fortran code to find... */

            int icsum_(ia, ib)   /* icsum(ia, ib) on some systems */
                  int* ia;
                  int* ib;
            {
               int isum;
               isum = *ia + *ib;
               return isum;
            }

   => general solution:  use cpp.  An include file "f77name.h" is 
      provided in the NTCC fpreproc module.  It defines a macro, 
      F77NAME, which expands to a name with or without decoration
      as needed for the current machine environment.  Thus:

            #include "f77name.h"

            int F77NAME(icsum)(ia, ib)
                  int* ia;
                  int* ib;
            {
               int isum;
               isum = *ia + *ib;
               return isum;
            }

   => comment:  if the C routine is also called from C code, then,
      not just the declaration of the routine but every C code reference
      to the routine needs to be imbedded in the F77NAME(...) macro.

   => another comment:  Cray environments require the C code's
      function name to be in upper case, for it to be visible to
      fortran codes.  The cpp preprocessor cannot quite do the case
      conversion.  Instead, on Crays only, a mini perl script is 
      run at compile time, to upper case convert anything between 
      the parentheses of an F77NAME(...) macro in C code.

Home Top


Fortran_CHARACTER_variables

Problem:  passing fortran character strings to C subroutines.  The
implementation of fortran CHARACTER variables is not standardized; this
lack of standardization is exposed when one tries to call C code with
fortran CHARACTER string arguments.

   => solution.  There are various levels of sophistication available
      in dealing with this problem.  For an advanced approach, see e.g.

            http://home.online.no/~arnholm/cppf77.htm 

      If, however, the need to pass CHARACTER data type between fortran
      and C code is rare, a simpler, more explicit fortran-77 approach
      might suffice.  This consists in explicitly copying the Fortran 
      string into a null terminated C string, in a byte array declared
      for this purpose.  Then, the C routine is called; any output string
      is also written to a byte array.  If the output string is needed
      as a fortran CHARACTER variable, it is explicitly copied in.

      "portlib" contains a routine, CSTRING, for doing this copying
      back and forth.  The following code illustrates its usage to
      do an environment variable name translation [see NOTE]:

      #include "byte_declare.h"

            integer bufsize
            parameter (bufsize=512)

            byte_declare buffer1(bufsize),buffer2(bufsize)
            character*200 homedir

            ...
c
c  copy 'HOME' into a null terminated C style string in buffer1(...)
c  '2C' specifies fortran -> C
            call cstring('HOME',buffer1,'2C')
c
c  call the "portlib" C subroutine
            call cget_env(buffer1,buffer2,bufsize,bufsize)  ! C code
c
c  copy the C style null terminated string (buffer2, set in cget_env)
c  into the fortran CHARACTER variable "homedir".
c  '2F' specifies C -> fortran
            call cstring(homedir,buffer2,'2F')
   
            ...

      after the second cstring call, the fortran character string
      "homedir" contains the user's home directory.

      NOTE:  this code example is for illustration only.  It is a lot
      easier to use "portlib" routine "sget_env" for environment 
      variable (or VMS logical name) translation in real applications.

   => comment:  this solution is simple and robustly portable, but may 
      not be adequate in a case where fortran/C character string 
      translation requirement exists in a performance critical part 
      of a user application.

Home Top


PreProcessing

The portability strategy outlined in this document relies heavily on the
idea of using the familiar C preprocessor   cpp   to implement conditional
compilation, macro expansion (name substition) and the like.

The use of cpp on fortran code is not as straight-forward as one might
think, for a number of reasons (see cpp_problems).

To avoid these problems, we adopt the following strategy:

  (a)  always use gnu cpp -- it is free, standard, and available on
       all systems as part of the gnu gcc C compiler.

  (b)  use perl scripts to pre-preprocess and post-preprocess the
       fortran code being run through gnu cpp.  This is done for
       protection of comments and quote strings, uppercase conversion,
       and squeezing out of extra blanks inserted by cpp.

The pre-preprocessor modifies the source code to make the comments and
quote strings safe from CPP, and converts all code statements to
uppercase.  The post-preprocessor reconstitutes the comments and quoted
strings to their original form.

These perl scripts prefpp and postfpp, as well as a collection of 
cpp #include files which are useful for portability, are included
in the NTCC module fpreproc.  Typical usage takes the form:

  perl prefpp my_code.f | \
    gcc -E -P -undef -D__UNIX -D__RS6000 -I[include file directories] - | \
      perl postfpp   > compilable_code.f
  f77 [switches]  compilable_code.f

The intermediate file compilable_code.f may want to be retained for
debugger access to the source code (as compiled).

Home Top


cpp_problems

Here are some hazards discovered through recent experimentation with
various versions of cpp on a variety of systems...

  (1)  vendor cpp programs vary a lot, creating the hazard of cpp
       derived portability problems.

example:  gnu cpp allows indentation of clauses, as in

#ifdef _CRAY
  #define DAXPY SAXPY
#else
  #define SAXPY DAXPY
#endif

but not all vendor cpp programs allow this.

  (2)  cpp is case sensitive, but fortran is not.

this is a problem, the obvious solution to which explodes:

#define daxpy SAXPY
#define DAXPY SAXPY
#define Daxpy SAXPY
#define dAxpy SAXPY
...etc...

  (3)  cpp does not recognize fortran syntax for comments

example:  the use of an apostrophe in a comment, as in "Joe's code",
can cause cpp to interpret the whole rest of the input source file
as a part of a quoted string-- not the desired result.

  (4)  various versions of cpp sometimes does fortran-unfriendly
       things such as dropping "//" and all subsequent characters
       from a line, lengthening code lines by appending unnecessary
       blanks in macro expansion (creating a 72 character hazard),
       and (on VMS systems) moving code text around horizontally
       e.g. into a statement label field, rendering it unfit for
       compilation.

Home Top


fpreproc_module

The NTCC modules library contains a module 

  fpreproc

which is made available to assist with cpp pre-processing of fortran
source code.  This module contains python script to pre-encode and 
post-decode fortran source to enable safe use of "gnu" cpp, and:

  f77_dcomplx.h --   #include file for dealing with conversion intrinsics
                     CMPLX, AIMAG, etc.

  blas_names.h --    #include file for mapping BLAS names to single or
                     double precision form, as needed on the current
                     system.

  lapack_names.h --  #include file for mapping LAPACK names to single or
                     double precision form, as needed on the current
                     system.

  linpack_names.h -- #include file for mapping LINPACK names to single
                     or double precision form, as needed.

  library_names.h -- #include file to reference an #include file for
                     each of several popular math libraries.

  byte_declare.h  -- #include file for system dependent definition
                     of "BYTE_DECLARE" which can then be used to 
                     declare byte variables and byte arrays.

  fp_const.h      -- #include file which defines the CONST macro, for
                     mapping floating point constants to E- or D-
                     exponent form as appropriate for the current
                     architecture.

and the following is for C code:

  f77name.h       -- #include file F77NAME(...) which is convenient
                     for making C code visible to fortran on all
                     systems.  In the C code one writes e.g.

                     #include "f77name.h"
                      ...
                     void F77NAME(fsub)(arg1,arg2,...) { ... }

                     and the global symbol "fsub" will be visible
                     to fortran code.  In practice this means
                     appending an "_" (underscore) on some systems,
                     but not on others.  NOTE:  to make this work on
                     Crays a modification to the makefile is needed;
                     see the section on Makefiles.

additional useful #include files will be added as these are developed.

Home Top


special_macros

In cpp terminology, any name that is detectable with #ifdef, or is
defined with #define, is a "macro".

The cpp #include code provided with fpreproc makes use of several 
macro names:

  __CRAY  --  this is a CRAY environment
  __DOUBLE_PRECISION  --  this is an environment in which REAL*8 is
                          double precision
  __SINGLE_PRECISION  --  this is an environment in which REAL*8 is
                          single precision

additional cpp macros may be created by the code author to deal with
various code portability / conditional compilation requirements.  It
is recommended that the convention of a leading "__" (two underscores)
be used.  This greatly reduces the chance that the CPP name conflicts
with a symbol name found in a source code; it is also compliant with
the ANSI standard for cpp names (this standard being a way to address
the hazard of name conflicts).

Other typical choices for macro names to be used by code developers'
cpp code might be:

  __UNIX   --  this is a UNIX environment
  __VMS    --  this is a VMS environment
  __RS6000 --  this is an RS6000 workstation
  __OSF    --  this is a DEC alpha workstation
  __SGI    --  this is an SGI computer

etc., etc.  It is really up to the code author to invent cpp macro 
names as needed.  However, the code author will then have to provide
for their conditional use, on appropriate systems, in a compilation
script or makefile.

Home Top


MakeFiles

One of the most obvious places where portability issues come up is
in the makefile used to compile and load a fortran application.  As
a practical matter, every fortran compiler ends up requiring a 
different set of switches for proper functioning.

There are many ways to use makefiles and scripts to deal with this 
system to system variability in compilers.  This document does not
propose to recommend any one approach.  However, several guiding
rules are to be suggested:

  (1)  use gnu make.  vendors' make programs vary too much.

  (2)  consider portability issues when designing and writing
       a makefile.

  (3)  document the makefile.

The question to ask, when looking at a makefile design is this:  How
hard would it be to add support for a new machine?  Could a reasonably
knowledgeable programmer read the makefile, documentation, and make
the change within a few minutes, assuming he/she knows the necessary
compiler switches on the new target system?  If not, the design should
be re-examined.

Home Top


Portability_Tools

In the course of the development of the porting strategy described in
this document, it became evident that various portability code conversions
are rather mechanical, and well suited to automation.  There arose out
of this realization several utilities, which are available from the
NTCC modules library inside the `ftoken' module:

  fgtok -- table driven "token finder / token substitution" tool with 
           fortran-77 specific features.

  idecl -- make implicit INTEGER and REAL declarations explicit, in order
           to convert a code to compile with IMPLICIT NONE.

  cpcheck -- scan a named cpp header file for macros; scan a named
           fortran source.  If the named macros occur in the named
           source print "present" otherwise print "absent".

  r8real -- generate a REAL*8 interface to a single precision REAL
           code (which we don't intend ever to convert to REAL*8),
           or, generate a REAL(*4) interface to a double precision
           REAL*8 code.

  cface  -- generate a C-callable interface to a fortran routine
           with INTEGER, REAL, and CHARACTER*(*) arguments.

  iface  -- generate a fortran-90 interface module from f77 code.

All these tools support command lines of the form

  tool-name [input-file [output-file]] [switches...]

or

  tool-name [input-file] [switches...].

It is recommended that the fixed format fortran source files input-file
and output-file be specified first, because, some of the switches take 
filename arguments, leading to the potential for ambiguity if the source
filenames input-file and output-file are specified after the switches.
If output-file is omitted, stdout is used; if input-file is omitted,
both stdin and stdout are used.

The description of the individual tools follow.

Home Top


fgtok

  fgtok -- table driven "token finder / token substitution" tool with 
           fortran-77 specific features.

fgtok can be used for the detection and remediation of common portability
problems in typical legacy fortran-77 code.  The program works by 
detecting "tokens" which are generally variable names, subroutine names,
constants, and fortran keywords, i.e. words delimited by whitespace 
and/or fortran arithmetic operators such as "+", "-", "*", etc.
The fgtok program has some fortran specific knowledge:  tokens inside
comments and quote strings are ignored;  continuation lines are 
recognized; the program knows whether or not a given token is the
first token in a fortran statement.  Certain phrases such as "REAL*8" 
and "DOUBLE PRECISION" are considered as individual tokens.

There are two basic modes of use for the fgtok program.  Its
"information" mode produces output giving information on an input
fortran source file.

Its "transformation" mode takes a fortran source file as input and
writes a transformed fortran source file as output.

These modes will be illustrated by example.  A complete list of 
fgtok switches are given in the "summary" subtopic.

Home Top


Driver_Scripts

The fgtok program comes with several driver scripts, 
intcon.csh, r8con.csh and fgtok_list.csh.
"intcon.csh" is described under Fortran Issues -- Integer and Logical
Data Types.

Home Top


r8con.csh

r8con.csh implements a thorough conversion of a source code to REAL*8
precision.  This is described in detail elsewhere in this
document, under the discussion of fortran portability issues--
floating point precision.

Home Top


r8con_incl.csh

A real*8 / complex*16 conversion script for INCLUDE files; see also
"r8con.csh".

Home Top


fgtok_list.csh

  fgtok_list.csh  <list-of-source-files>

will run "fgtok -f <source-file> -upper" on each filename in the list
(in a simple foreach loop).

To get a sorted list, do:

  fgtok_list.csh  <list-of-source-files> | sort

To get a double column list (useful as a template for building a name
substitution table), do:

  fgtok_list.csh  <list-of-source-files> | sort | sed 's#.*#&  &#'


Home Top


Information_Mode

general form of an "informational" fgtok command:

  fgtok input-filename [<switches>]

    or

  fgtok [<switches>] < input-filename

In the examples that follow, the first from will be used.

Let "foo.f" be a fortran source for which information is desired.
Examples of usage of fgtok:

  fgtok foo.f              # get list of global names

...outputs a list of global names in foo.f; i.e. names of programs,
subroutines, functions, and block data modules.

  fgtok foo.f -w72         # warn about length > 72 chars

...interspersed inside the list of global names, this command
outputs a list of source lines in foo.f, for which the executable
statement text extends beyond character position 72 (the portion
of the statement beyond this position is in danger of being ignored 
by most fortran-77 compilers).  An example with a ficticious foo.f:

saturn.pppl.gov> fgtok foo.f -w72
foo
 **file:  foo.f line no.     4        
 line length .gt. 72 characters warning:
         abc = a*b*c*d*e*f*g*(aaaaaaaaaa+bbbbbbbbb+ccccccccc+dddddddddddd)*3


  fgtok foo.f -wfp         # warn about floating point constants.
  fgtok foo.f -wfp4        # warn about non-double-precision constants.

...hard coded constants are a hazard when converting to a standard
floating point precision-- because the compiler will make certain
assumptions about the precision of the constant.  If these assumptions
are applied to a constant in a subroutine argument list, the result
can be a type mismatch with the called routine, leading to unpredictable
results at runtime.  An example of fgtok output:

[saturn.pppl.gov|106] fgtok foo.f -wfp
ecnter
 **file:  foo.f line no.   106
 warning:  hard coded floating point constant:
       IF(ZTEST1.GE.0.0) THEN
 ___________________/
 **file:  foo.f line no.   110
 warning:  hard coded floating point constant:
         IF(ZTEST1.GT.0.0) ISTAT=ISTAT+4
 _____________________/


  fgtok foo.f -wcplx     # warn about fp intrinsics

...warn about floating point type conversion intrinsics.  These can
be a portability hazard when creating a numerical code that should
port between 64 bit Cray and 32 bit workstation environments.  Use
of the wrong intrinsic leads to loss of precision at runtime.  Example
of output:

[saturn.pppl.gov|107] fgtok foo.f -wcplx
ecnter
 **file:  foo.f line no.   119
 warning:  floating point conversion intrinsic:
         IR=IFIX(1.0+(FLOAT(NF)-1.0)*(RAD0-R1)/(R2-R1))
 _____________________/

  fgtok foo9.f -wfmt     # warn about D/E/F/G items in FORMAT stmts

...warn about floating point specifications in FORMAT statements.
when converting a code to REAL*8 precision, one might want to increase
the number of digits displayed in the output of floating point
numbers.  Example of output:

[saturn.pppl.gov|110] fgtok foo9.f -wfmt
foo9
 **file:  foo9.f line no.     6
 warning:  floating point FORMAT item:
  1000  format(' z = ',1pe11.4)
 _____________________/

Note that format specifications can also be imbedded in character
strings.  ** fgtok cannot detect this ** because it does not look
inside quoted strings.

All -w* options can accept filenames as arguments; in which case the
information output is rerouted from stdout to the indicated file.

  fgtok <fortran-source-file>

outputs, in lowercase, global names found in the source file, one per line,
in the order they are found.  A global name is a name following any one
of the following keyword sequences:

   BLOCK DATA
   FUNCTION
   PROGRAM
   SUBROUTINE

In the processing of the source code, keyword match tests do not depend 
on upper/lowercase coding style.  If the output global names are desired
in uppercase, add the "-upper" switch:

  fgtok <fortran-source-file> -upper

Home Top


Transformation_Mode

General forms of the "transformational" fgtok command line:

  fgtok input-file output-file [switches]

or

  fgtok input-file [switches] > output-file

or

  fgtok [switches] < input-file  > output-file

... the first command line form will be used in the examples.

Note that informational mode switches can be added; this results
in output of information / warnings to a separate file.  The user
should take care that the information output file and the source
code output file are not the same.

Subtopics show specific transformations that are available.
Home Top


comment

To have fgtok insert a comment at the end of the output file,
as a record of fgtok processing of the source, do:

  fgtok foo.f foo.new -cmt "this is a comment"

Then foo.new is the same as foo.f except with a comment of the
form

  ! 30apr1999 fgtok "this is a test"

added at the end.

Normally, -cmt "comment text" is used in conjunction with some
other transformation option.  These additional options are
also inserted in the comment string.  For example,

  fgtok foo.f foo.new -impno -cmt "this is a comment"

results in a trailing comment of the form

  ! 30apr1999 fgtok -impno "this is a test"

(and the processing associated with -impno is also performed).

Home Top


Token_substitution

  fgtok input-file output-file  -s substitution-table-file

Generally, "substitution-table-file" is a file containing two
columns of information:  a column of "from" tokens and a
column of "to" tokens.  Usually, tokens are blank delimited,
but certain phrases like "double precision" are counted as
a single token.  "substitution-table-file" can also have
a one of two possible one character flags in the third column:

  "1" means only apply the substitution if this is the first
      token in the statement -- recommended for transformation
      of type declarations, because, "REAL", not in the first
      token position, is an intrinsic function reference.

  "*" means apply the substitution inside comments and quote
      strings.  It might be appropriate, when changing the name
      of a subroutine, to change references to that name in 
      comments and quoted strings (e.g. error messages) as well.

a "#" character in the first character position *only* is taken as
a comment in a substitution table file.

** example **

fortran source input file:  foo.f

      program foo
      real a,b
      double precision foosum
      complex z

      a=1.0
      b=foosum(a,2.0)
      z=cmplx(a,b)

      stop
      end

      real function foosum(arg1,arg2)
      real arg1,arg2

      foosum=arg1+arg2
      return
      end

substitution file:  "r8_precision.sub" which comes with the
fgtok program:

#  map all real & complex types to real*8 and complex*16
#    ... D. McCune 2 Apr 1999

real            REAL*8               1
real*4          REAL*8               1
real*8          REAL*8               1
double precision        REAL*8       1

complex         COMPLEX*16           1
complex*8       COMPLEX*16           1
complex*16      COMPLEX*16           1
double complex          COMPLEX*16   1

example of execution:

[saturn.pppl.gov|134] fgtok foo.f foo.new -s r8_precision.sub
[saturn.pppl.gov|135] cat foo.new
      program foo
      REAL*8 a,b
      REAL*8 foosum
      COMPLEX*16 z
 
      a=1.0
      b=foosum(a,2.0)
      z=cmplx(a,b)
 
      stop
      end
 
      REAL*8 function foosum(arg1,arg2)
      REAL*8 arg1,arg2
 
      foosum=arg1+arg2
      return
      end

Notes:
(1)  "foo.new" has a bug in it:  the hard coded constant passed
     as an argument to foosub.  See the neighbouring subtopic on
     conversion of hard coded floating point constants.

(2)  other standard substitution table files included with fgtok:

     r8_intrinsic.sub  --  map non-generic intrinsic functions to
                           intrinsic form (e.g. DSQRT -> SQRT).

     r8_icomplex.sub   --  map type conversion intrinsics to a 
                           standard subset of names

     (see subtopics).

(3)  the fgtok -s option can take multiple filenames, in which case
     the substitution tables are merged.  However, it is the user's
     responsibility to avoid name conflicts in the "from" column.

(4)  do not use r8_precision.sub and r8_icomplex.sub in the same
     fgtok command, because both remap the token "REAL".
Home Top


r8_instrinsic.sub

This substitution table comes with the fgtok program.

[saturn.pppl.gov|142] cat r8_intrinsic.sub
#  map all intrinsics to generic intrinsics (where possible) in fortran
#  source code -- for use with D. McCune's fgtok program.
#
#  usage:
#
#     fgtok <input-file> <output-file> -s r8_intrinsic.sub -w72
#
#  <input-file> means the old version fortran source; <output-file>
#               means the new version; these should be distinct files.
#
#  if <output-file> is omitted, stdout is used.  If both <input-file>
#  and <output-file> are omitted, stdin and stdout are used.
#
#  recommended switches:
#   -w72 ... warns of any occurrences of line length .gt. 72 characters
#
#  note that the floating point / complex number conversion intrinsics
#    REAL DBLE AIMAG DIMAG CMPLX DCMPLX FLOAT DFLOAT are not mapped here:
#    see -- r8_icomplex.sub
#
  dsqrt         sqrt
  qsqrt         sqrt
  csqrt         sqrt
  cdsqrt        sqrt

  alog          log
  dlog          log
  qlog          log
  clog          log
  cdlog         log

  alog10        log10
  dlog10        log10
  qlog10        log10

  dexp          exp
  qexp          exp
  cexp          exp
  cdexp         exp

  dsin          sin
  qsin          sin
  csin          sin
  cdsin         sin

  dcos          cos
  qcos          cos
  ccos          cos
  cdcos         cos

  dtan          tan
  qtan          tan

  dasin         asin
  qasin         asin

  dacos         acos
  qacos         acos

  datan         atan
  qatan         atan

  datan2        atan2
  qatan2        atan2

  dsinh         sinh
  qsinh         sinh

  dcosh         cosh
  qcosh         cosh

  dtanh         tanh
  qtanh         tanh

  iiabs         abs
  jiabs         abs
  dabs          abs
  qabs          abs
  cabs          abs
  cdabs         abs

  iint          int
  jint          int
  iidint        int
  jidint        int
  iiqint        int
  jiqint        int

  dint          aint
  qint          aint

  inint         nint
  jnint         nint
  iidnnt        nint
  jidnnt        nint
  iiqnnt        nint
  jiqnnt        nint

  dnint         anint
  qnint         anint

  iifix         ifix
  jifix         ifix

  dconjg        conjg

  imax0         max
  jmax0         max
  imax1         max
  jmax1         max
  amax1         max
  dmax1         max
  qmax1         max
  max0          max
  max1          max
  amax0         max
  aimax0        max
  ajmax0        max

  imin0         min
  jmin0         min
  imin1         min
  jmin1         min
  amin1         min
  dmin1         min
  qmin1         min
  min0          min
  min1          min
  amin0         min
  aimin0        min
  ajmin0        min

  iidim         dim
  jidim         dim
  ddim          dim
  qdim          dim

  imod          mod
  jmod          mod
  amod          mod
  dmod          mod
  qmod          mod

  iisign        sign
  jisign        sign
  dsign         sign
  qsign         sign

Home Top


r8_icomplex.sub

** caution ** this remaps REAL declarations -- convert these
to REAL*8 first, before using this substitution table!

[saturn.pppl.gov|143] cat r8_icomplex.sub
#  map all real <--> complex conversion intrinsics to standard names.
#  source code should 
#
#  #include f77_dcomplx.h 
#
#  this is REQUIRED because the non-standard name AREAL is used (to undo
#  fortran's name conflict btw the REAL intrinsic and REAL data type).
#
#  for environment-dependent compile time remapping.
#
#  usage:
#     fgtok <input-file> <output-file> -s r8_icomplex.sub -w72
#
#  <input-file> means the old version fortran source; <output-file>
#               means the new version; these should be distinct files.
#
#  if <output-file> is omitted, stdout is used.  If both <input-file>
#  and <output-file> are omitted, stdin and stdout are used.
#
#  recommended switches:
#   -w72 ... warns of any occurrences of line length .gt. 72 characters
#
#  when converting codes to real*8 usage, use r8_precision.sub *first*
#  to remove all REAL data type declarators, *before* using this 
#  substitution table.
#
#    ... D. McCune 2 Apr 1999

real            AREAL
dble            AREAL
areal           AREAL

float           AREAL
dfloat          AREAL

aimag           AIMAG
dimag           AIMAG

cmplx           CMPLX
dcmplx          CMPLX

Home Top


More_on_r8_icomplex.sub

Typical use of r8_icomplex.sub would be as follows:

  fgtok foo.f foo.new -s r8_icomplex.sub -incl_cpp f77_dcomplx.h

With foo.f containing:

[saturn.pppl.gov|180] cat foo.f
        program foo
        real*8 a,b
        complex*16 z

        a=real(1)
        b=float(2)
        z=dcmplx(a,b)

        z=z*z

        a=dble(z)
        b=dimag(z)

        stop
        end

  the above fgtok command creates:

[saturn.pppl.gov|183] cat foo.new
C! 30apr1999 fgtok -s r8_icomplex.sub
#include "f77_dcomplx.h"
        program foo
        real*8 a,b
        complex*16 z
 
        a=AREAL(1)
        b=AREAL(2)
        z=CMPLX(a,b)
 
        z=z*z
 
        a=AREAL(z)
        b=AIMAG(z)
 
        stop
        end

The #include file "f77_dcomplx.h" (from fpreproc) contains:

#ifndef _F77_DCOMPLX
#define _F77_DCOMPLX

#ifdef __CRAY
  #define __SINGLE_PRECISION
#else
  #define __DOUBLE_PRECISION
#endif

#ifdef __DOUBLE_PRECISION     /* workstations */

  #define AREAL DBLE
  #define AIMAG DIMAG
  #define CMPLX DCMPLX

#elif __SINGLE_PRECISION      /* Cray */

  #define AREAL REAL
  #undef AIMAG
  #undef CMPLX

#endif

#endif /* _F77_DCOMPLX */

and this causes the correct floating point type conversion intrinsic
to be used for each type of machine architecture.  Not using the
correct intrinsic leads to loss of precision.

Home Top


Implicit_None

Portability and reliability are more easily maintained in programs
which use "IMPLICIT NONE".  This forces all symbols to be explicitly
declared.  

Use `idecl' to generate explicit declarations of undeclared variables,
then use `fgtok' to insert IMPLICIT NONE statements.

Example:

[jupiter.pppl.gov|85] cat > foo.f
        program foo

        y=1.0
        z=2.0
        x = foofun(y,z)
        call foosub(x,y,z)

        stop
        end

        function foofun(x,y)

        foofun=2*x+y

        return
        end

        subroutine foosub(x,y,z)

        write(6,*) x,y,z

        return
        end

[jupiter.pppl.gov|86] idecl foo.f foo.tmp -r8
[jupiter.pppl.gov|87] cat foo.tmp
        program foo
 
C============
C idecl:  explicitize implicit REAL declarations:
      REAL*8 y,z,x,foofun
C============
        y=1.0
        z=2.0
        x = foofun(y,z)
        call foosub(x,y,z)

        stop
        end
 
        function foofun(x,y)
 
C============
C idecl:  explicitize implicit REAL declarations:
      REAL*8 x,y,foofun
C============
        foofun=2*x+y

        return
        end
 
        subroutine foosub(x,y,z)
 
C============
C idecl:  explicitize implicit REAL declarations:
      REAL*8 y,z,x
C============
        write(6,*) x,y,z

        return
        end

[jupiter.pppl.gov|88] fgtok foo.tmp foo.new -impno
[jupiter.pppl.gov|89] cat foo.new
        program foo
 
C============
C idecl:  explicitize implicit REAL declarations:
      IMPLICIT NONE
      REAL*8 y,z,x,foofun
C============
        y=1.0
        z=2.0
        x = foofun(y,z)
        call foosub(x,y,z)
 
        stop
        end
 
        function foofun(x,y)
 
C============
C idecl:  explicitize implicit REAL declarations:
      IMPLICIT NONE
      REAL*8 x,y,foofun
C============
        foofun=2*x+y
 
        return
        end
 
        subroutine foosub(x,y,z)
 
C============
C idecl:  explicitize implicit REAL declarations:
      IMPLICIT NONE
      REAL*8 y,z,x
C============
        write(6,*) x,y,z
 
        return
        end
 
Home Top


Conversion_of_Constants

Use the fgtok -expd option to convert all floating point constants
to D-exponent form.  Use -expe to convert to E-exponent form.  Use
-const to convert to CONST macro form.

Example:

[saturn.pppl.gov] cat foo.f
        program foo

        data abc/1.3/

        xyz=5.0e1
        call foosub(xyz,1.e+15)

        stop
        end

[saturn.pppl.gov|230] fgtok foo.f foo.new -expd
[saturn.pppl.gov|231] cat foo.new
        program foo
 
        data abc/1.3D0/
 
        xyz=5.0D1
        call foosub(xyz,1.D+15)
 
        stop
        end

[saturn.pppl.gov|230] fgtok foo.f foo.new -const -incl_cpp fp_const.h
[saturn.pppl.gov|231] cat foo.new
#include "fp_const.h"
        program foo
 
        data abc/CONST(1.3,0)/
 
        xyz=CONST(5.0,1)
        call foosub(xyz,CONST(1.,+15))
 
        stop
        end

The "fp_const.h" header file defines the CONST macro, which maps at
compile time to a D-exponent or an E-exponent floating point constant
as appropriate for the current architecture.
Home Top


Insert_cpp_include_file_ref

Use

  fgtok foo.f foo.new -incl_cpp [filename(s)...]

To insert one or more lines of the form

#include "filename"

at the head of the file.

An example of this is shown under the "Token_Substitution" subtopic
"More_on_r8_icomplex".

The 'cpcheck' program can be used to test a source, to see if it
needs a given #include file.

Home Top


Detab

Use

  fgtok foo.f foo.new -detab

To "detab" source code.  There is a non-standard extension to 
fortran-77 that allows a tab character (instead of six blanks)
to be used to cross the statement label field.  This causes
problems for some compilers.  The -detab option replaces tab
initiated statements and continuation lines with lines in the
standard format.  It is recommended to also use -w72 with this
command, because some compilers allow the tab to increase the
number of characters available for code; with -detab this
increase is lost.

Home Top


Fix72

For sources containing code statements exceeding the 72 character
limit, one can use

  fgtok foo.f foo.new -fix72

to generate an automatic line break / continuation line insertion.

The line break will not be done in the middle of a quote string
or token name.  With an overly long quote string, this means that
the -fix72 option might not be able to fix all instances.

** use this option with care **

For purposes of maintaining readability of the source code, one
might prefer to break over the limit lines by hand with the editor,
using fgtok -w72 to find the over long lines.

Home Top


Summary_of_fgtok_command_line

fgtok [input-file [output-file]] [switches]

  ... if output-file is omitted, stdout is used for output.
  ... if both input-file and output-file are omitted, stdin is used
      for input, stdout for output.

=> switches that produce information on the source file.  If the source
file is transformed, the information generated applies to the output
file.  Optionally, a file may be specified to receive the informational
output.

  -w72 [warnings-filename]  ...warn about lines .gt. 72 characters 
                               long.

  -wfp [warnings-filename]  ...warn about any hard coded floating point
                               floating point constants.

  -wfp4 [warnings-filename] ...warn about hard coded floating point
                               floating point constants with no
                               exponent or an "E" exponent -- i.e.
                               single precision constants.

  -wcplx [warnings-filename]...warn about floating point type
                               conversion intrinsics.

  -wfmt [warnings-filename] ...warn about floating point output
                               specification in FORMAT statements.

=> switches that cause a modified output source file to be written.

  -s [substitution-table-filenames(s)...] do token substitution, based
                               on the data in the substitution table
                               files provided.

  -cmt "cmt-string"         ...insert a comment at the end of the 
                               output file.

  -incl_cpp [filename(s)...]...insert #include "filename" at the
                               head of the output file.  Generate
                               a separate #include line for each
                               "filename" specified.

  -impno                    ...insert IMPLICIT NONE at the head of
                               each program unit.

  -expd or -expe or -const  ...convert hard coded floating point
                               constants into D- or E-exponent form,
                               or CONST(mantissa,exponent) macro form.

  -detab                    ...detab the source

  -fix72                    ...if a line of length .gt. 72 characters
                               is found, try to fix it by inserting a
                               break and adding a continuation line.

Home Top


Caution

A word of caution about the fgtok program.

Caution-- the in the fortran-77 standard, whitespace is not significant;
the code lines

      abc = wx +yz  ! fgtok sees tokens:  abc, wx, yz
and

      ab c  = w  x +y z  ! fgtok sees tokens:  ab, c, w, x, y, z

are equivalent.  **fgtok ignores this** and in the second line it
considers ab, c, w, x, y, and z each to be separate tokens.  Any
fortran style book strongly discourages leaving imbedded blanks
inside identifiers; fgtok assumes this style advice has been followed.

If this assumption is incorrect, fgtok could produce unexpected
results.

In fortran-90 (free format source), whitespace *is* significant and
*does* delimit tokens.  Therefore, the f77 code with imbedded spaces
in its identifiers would not port to a fortran-90 only environment
(such as the NERSC Cray machines).

Home Top


idecl

  idecl -- make implicit declarations explicit, in order
           to convert a code to compile with IMPLICIT NONE.

The idecl program can be used to make explicit any implicit declarations
in a fortran source code.  This is useful if one wants to convert to 
using "IMPLICIT NONE", to improve the reliability and maintainability
of a source code.

An example of the use of the idecl program is shown under the 
description of fgtok (transformation subtopic, then implicit_none
subtopic).

In determining what symbols to declare, idecl prepares two lists:

  => an exclusion list:  symbols that are fortran reserved names or
     that are already declared in the source code.  If a symbol is
     referenced in an INCLUDE file, it is presumed to have been
     declared:  an INCLUDE file in an IMPLICIT NONE code should
     declare the data type of all its symbols.  For maintainability
     reasons this should be done in the INCLUDE file itself,
     and not in the program(s) that use the INCLUDE file.

  => a declarations list:  symbols that appear to require declaration.

When the end of a program unit is found, the missing declarations are
generated and inserted into the output file at the head of the
program unit being processed.  Then, the rest of the program unit is
copied (from a temporary file).  Then, `idecl' procedes by searching
for the next program unit.

Unless it finds IMPLICIT statements which change the implicit data
type rules, `idecl' uses the standard fortran implicit rule for 
declarations:  symbols starting with i,j,k,l,m, or n are declared 
INTEGER, the rest are declared REAL (or REAL*8 if the -r8 switch
is used).

`idecl' accepts the following switches:

  -r8  --  declare reals as REAL*8 (if present).  Also declares
           quantities as COMPLEX*16 if they are implicitly 
           declared under an IMPLICIT COMPLEX statement in the 
           original source.

  -I include-file-directory [include-file-directories...]
 
       --  a list of one or more include-file-directories.  This
           tells `idecl' where to find INCLUDE files referenced
           by the source code being converted.  `idecl' needs to
           scan these INCLUDE files for symbol declarations.

  -move -- option to re-order declarations if necessary.  Sometimes, 
           an array dimension, say "N", is used before declared, so
           that under IMPLICIT NONE a compiler error would occur.
           The -move option requests that idecl attempt to move the
           declaration of "N" before the first line where "N" is
           used.  This is not always possible, because other items
           in the declaration line containing the declaration of "N"
           might depend on earlier declarations, making the move 
           unsafe.

Home Top


cpcheck

The program `cpcheck' scans a named input source file for occurrences
of names that are specified in a named cpp header (.h) file.  If 
matching names are found, cpcheck writes "present"; otherwise it
writes "absent".  If an error occurs, it writes "error" followed by 
text containing an error message.

The cpcheck command line is:

  csh> cpcheck [source-filename] [-p path [paths...]] \
               -incl_cpp header-file [header-files...]

For example:

  csh> cpcheck  userprog.for  -p $CODESYSDIR/source/incl_cpp \
                   -incl_cpp blas_names.h lapack_names.h

The optional -p switch defines a path (or multiple paths) to
a directory (directories) containing the named .h file(s).

The required -incl_cpp switch is used to name one or more
header files.

If the header file(s) contain #include statements which refer
to other header file(s), these too are checked.  The code will
recursively follow nested #include statements to any depth, while
keeping a list of scanned files so as to avoid any possibility of
an infinite loop.  In the case of header files included with
the `fpreproc' module, one coulde use "-incl_cpp library_names.h"
instead of "-incl_cpp blas_names.h lapack_names.h", because
library_names.h #include's the blas_names.h and lapack_names.h
files.

If source-filename is omitted, stdin is used.

The cpcheck program is used in fgtok's "r8con.csh" source code
conversion script, to determine wheter a math library name substitution
header file should be inserted in the output source file.

Home Top


r8real

The program `r8real' can be used to automatically generate an
interface from a REAL*8 precision code to a single precision
fortran-77 REAL subroutine library, or from a REAL precision 
code to a REAL*8 subroutine library.

Usage:

  csh> r8real [ -r8 | -r4] [ -tex ] input.f  output.f90  > name.sub

where:

    -r8     ...  gen REAL*8 interface to REAL library routines
    -r4     ...  gen REAL interface to REAL*8 library routines
                 (-r8 is the default if neither are specified)
    -tex    ...  source code is mixed (La)tex and source;
                 process the source only

  input.f   ...  is the single precision REAL input code
  output.f90 ... is the generated REAL*8 -> REAL interface to input.f
  name.sub  ...  is a two column name substitution file defining the
                 name mapping from input.f subroutines to the generated
                 interface routines.  The name.sub file is suitable for
                 use with `fgtok'.

The generated interface routines have the routine names occurring in
the input file, with the string "R8_" prepended (REAL*8 interface)
or "R4_" prepended (REAL interface).  Thus, in the above
example if input.f contained subroutine ABC and function XYZ, and
the option -r8 is in effect, then the interfaces R8_ABC and R8_XYZ
would be generated.

Note that interfaces are generated ONLY for routines which actually
have non-REAL*8 floating point arguments.  COMPLEX to COMPLEX*16
mappings are also handled.  But, if a routine only has INTEGER and
CHARACTER arguments, no R8_ interface is generated.

The interfaces generated by r8real, while close, are not always
defect free.  In particular, if the dimensioning of an array 
argument is not fully specified, r8real cannot generate the
complete interface, and r8real will write a #-comment with a
warning to that effect in the output file (name.sub in the example).
The interface will then require a hand edit.  Additional edits may 
also be desirable to improve the efficiency of the interfaces.

For a detailed discussion, see the section on porting
fortran code / dealing with floating point precision / interfacing
to unconverted single precision libraries.

The NTCC modules UFILES and TRGRAF include r8real-generated interface
routines, to support calls from REAL*8 codes to i/o and graphics 
routines in these single precision libraries.  These interfaces
contain minor hand edited modifications.

The `r8real' program comes with a driver script, "r8igen.csh".  This
can be used to generate an interface to a library whose source code
is scattered over a collection of source files in a single directory.
The "r8igen.csh" script is invoked as follows:

  unix>  csh -f r8igen.csh  output.f90  output.sub

The input files are *.f, *.for, and *.F in the current directory.
The output files are:

  output.f90 -- the generated interface (R8_ routines).
  output.sub -- an fgtok style name substitution files, mapping
                from the original names to the R8_ prepended names.
                Caution-- this file can also contain warning
                messages.

r8igen.csh generates REAL*8 interfaces.  For REAL interfaces, use

  unix>  csh -f r4igen.csh  output.f90  output.sub

Home Top


Tool_Use_Example

The NTCC library module PSPLINE (which supports 1d 2d and 3d
spline and hermite interpolation methods) contains two sets of
interfaces, one for REAL and one for REAL*8 codes.  Both 
interfaces support the same functionality (but different precision)
and both are used and need to be supported.  But, it is highly desirable
from a code maintenance standpoint that the REAL*8 interface be 
generated automatically from the REAL interface.  Otherwise, any
change to the PSPLINE software must be hand coded twice, and there
is a danger that the REAL and REAL*8 routines' functionality would
diverge.

By using the tools described in this document, it was reasonably
straight-forward to create a shell script to automatically generate
the REAL*8 precision interface from the REAL precision source code.
Here is a sketch of how it was done (the script, pspline_genr8.csh,
is included in the PSPLINE module).

(1)  Use r8real to find all interfaces with floating point arguments
and to generate a mapping between standard and "R8" names.  The r8real
fortran-90 interface code is discarded; here we just want the name 
mapping.

(2)  Apply the "r8con.csh" script from fgtok to actually carry out
the conversion steps:  conversion of floating point declarations, 
application of name mapping, insertion of IMPLICIT NONE, make 
explicit any pre-existing implicitly declared names, deal with 
hard coded constants and non-generic intrinsic functions, etc.

(3)  The generated file r8_names.table is kept with the PSPLINE
source code.  If an application which currently uses single
precision REAL and calls the PSPLINE REAL interface is to be
converted to REAL*8, r8_names.table can be used to change all
the PSPLINE calls in the application.

(4)  This is actually done to create the REAL*8 PSPLINE test
driver program "pspltes8.for" from the single precision test
driver program "pspltest.for".
Home Top


About this document

This Document was created by hlptohtml

  • Written By:
  • Manish Vachharajani(mvachhar@pppl.gov)