Programmer's Manual to Zindo ============================ Who should read this? --------------------- This manual is intended for anyone who plans to modify the source code of Zindo. Users who use Zindo only for production calculations may benefit from the reading in order to make more educated decisions, possibly resulting in greater efficiency of using Zindo. Style ----- Zindo has grown for many years of work by a great number of graduate students, post-docs, visiting and permament professors. There is a great variety of coding styles present in Zindo. In an effort to make Zindo look more uniform in style, the following guidelines should be observed: - Standard language features only. Zindo needs to be portable and any vendor-supplied extensions to the Fortran language, no matter how convenient or widely used, should be avoided. - Only Fortran. We already have enough trouble with the couple of C-routines in Zindo and we do not want any more. - Indentation. Indent all structural elements (IF and DO blocks) using the following style: IF (condition) THEN CODE GOES HERE ELSE CODE GOES HERE END IF DO 10 I=1,K CODE GOES HERE END DO Please indent every subsequent level of nesting by three (1) additional spaces. - Case consistency. Use only UPPER CASE, unless it is refering to a lower case UNIX file. Comments can be either upper case or lower case - ZINDO by default uses double precision numbers, try and use the assigned variables for 1-10, they are given in param.cmn anlong with their integer equilivalent. - Make sure that ALL integers variables and parameters start with I,J,K,L,M,N - If you are using emacs, then add the following lines to your .emacs file and the do, if indention will be done automatically by using TABS (setq fortran-mode-hook '(lambda() (message "setting fortran-mode-hook") (setq abbrev-mode 'T) (setq fortran-line-number-indent 4) (setq fortran-do-indent 1) (setq fortran-if-indent 1) (setq fortran-continuation-char 46) ) ) Memory ------ If the size of any array depends on the size of the system being calculated, compute its size from the following values: in param.cmn: MXA - maximum number of atoms allowed MXB - maximum number of orbitals allowed Avoid these parameters if possible, in favour of the followng: MATCL - maximu number of molecular mechanics atoms MAXTYP - maximum number of atom types (molecular mechanics) in z.cmn: NA - (actual) number of atoms NB - (actual) number of orbitals NRR - size of a NBxNB lower triangular matrix NR - size of a NBxNB square matrix If the size of the array increases like a square (or higher power) of the system size, or even if it is linear with system size, but has a relatively high (over 3) size in the perpendicular direction, you will have to use dynamic memory. In order to avoid sudden increases in the memory requirements during a run, all dynamic memory is allocated at the beginning of the run. At first, information is gathered about memory needs by executing a 'dry run'. During the latter, requests for memory are recorded from all subroutines which will need memory for the particular calculation. After that, all required memory is allocated in one big lump, and the various subroutines will use parts of that big array. From a practical (programming) point of view, the dynamic memory allocation is used as follows: Use a 'wrapper subroutine' (usually located at the beginning of the actual subroutine). The wrapper should "allocate" all necessary memory by calls to MEM_GET. Then, depending of the value of a LOGICAL parameter DRYRUN, it should either call the subroutines which the real subroutine calls, passing the DRYRUN down to them, or call the 'real' subroutine. Finally, all memory should be "deallocated" by calling MEM_FREE. MEM_GET returns an index into an array BIGMEM (found in mem.cmn), which can then be used to make use of the allocated memory. You will need to make sure that your subroutine gets properly called during the dry run from its respective calling subroutine as well. An example subroutine with dynamic allocation could look like this: SUBROUTINE MYSUB (A, B, DRYRUN) C param and mem always need to be included. INCLUDE '../include/param.cmn' INCLUDE '../include/mem.cmn' C you will need to include additional common blocks to calculate C memory requirements. INCLUDE '../include/z.cmn' C Declare parameters which are passed through: LOGICAL DRYRUN DIMENSION A(*), B(*) C allocate array 'C' which contains NRR double-precision numbers I_C = MEM_GET (NRR, 'real', 'MYSUB-C') C allocate array 'K' which contains 6*NA integer numbers I_K = MEM_GET (6*NA, 'integer', 'MYSUB-K') IF (.NOT. DRYRUN) THEN C During a real run, perform the calculation CALL M1SUB (A, B, BIGMEM(I_C), BIGMEM(I_K), DRYRUN) ELSE C OTHERSUB also uses dynamic memory and needs to be called in C dry run CALL OTHERSUB (B,N,DRYRUN) END IF C parameter list to MEM_FREE is the same as for MEM_GET. Please C note the reverse order with respect to allocation. CALL MEM_FREE (6*NA, 'integer', 'MYSUB-K') CALL MEM_FREE (NRR, 'real', 'MYSUB-C') 5000 RETURN END C By convention, the 'real' subroutine gets its name by replacing C the second character with number '1'. Staying with this C convention makes symbolic debugging easier. SUBROUTINE M1SUB (A, B, C, DRYRUN) INCLUDE '../include/param.cmn' C Declare the parameters as if they were actual arrays. DIMENSION A(*), B(*), C(*), K(*) LOGICAL DRYRUN . . . C OTHERSUB uses dynamic memory; the call must be reproduced in the C wrapper. CALL OTHERSUB (B,N,DRYRUN) . . . C THIRDSUB does not use dynamic memory, and is called only during C the actual execution of the program. CALL THIRDSUB (C,L) . . END Debugging dynamic memory problems --------------------------------- All memory allocations, both in dry and real runs, can be traced by setting DEBUGM=.true. in the $CONTRL block. By setting MEMINI=.TRUE. all newly allocated memory is set to a value which prints as 1.2345678, making it easier to track unitialized arrays. The memory subsystem keeps track of array boundaries, and will issue an error message as soon as violations are discovered; usually at the time memory is released. The message includes a 'stack dump' to the extent that the information is preserved. Most often the last array which does *not* have an apparent problem has actually went out of bounds, destroying the control information about the *next* array. You can introduce additional memory stack dumps (useful for debugging) by calling mem_dump (no parameters) anywhere in the program. For a technical description of the memory subsystem, you may read memory.doc in the Zindo documentation directory. File Numbers in Zindo --------------------- Zindo uses a big number of temporary files. In Fortran, files are referred to by unit numbers, which are integers from an implementation-dependent range, usually 1-100. By tradition, unit 5 is the input file, and unit 6 is the output file. All other unit numbers used in Zindo must be allocated dynamically. Because unit numbers may change from run to run, an up to 8-character LABEL is associated with an unit number. Many error messages refer to the unit number as well as label for easy identification. It is recommended that the variable holding the unit number be called IO_SOMETHING, where SOMETHING is the label. If the unit is used in various parts of the program, it should be listed in the units.cmn include file. You can request an unit number by calling the function NEW_FILENUM, giving the LABEL as the parameter: IO_MYUNIT = NEW_FILENUM ('MYUNIT') After you do not need the unit number any more, release it by calling REL_FILENUM with the label: CALL REL_FILENUM ('MYUNIT') Unit numbers not properly released will be listed at the end of the run in the output file. It is recommended, that all files will be given names based on an user-defined parameter ONAME. To simplify this task, a subroutine MAKE_FILENAME has been provided, which applies a programmer-defined extension to ONAME and returns the resulting filename in a character variable. The recommended extension should be the same as the unit label, but in lower case (e.g. '.myunit'). A typical piece of program might look like this: CHARACTER*30 FILENAME . . IO_MYUNIT=NEW_FILENUM('MYUNIT') CALL MAKE_FILENAME(FILENAME,'.myunit') OPEN (UNIT=IO_MYUNIT, FILE=FILENAME, ACCESS='SEQUENTIAL', + FORM='UNFORMATTED') . . WRITE (UNIT=IO_MYUNIT) (A(I),I=1,NRR) . . REWIND (IO_MYUNIT) READ (IO_MYUNIT) (X(I),I=1,NRR) . . C You should normally delete the files unless some other programm C will use them as input: CLOSE (UNIT=IO_MYUNIT,STATUS='DELETE') CALL REL_FILENUM('MYUNIT') Please avoid using STATUS='NEW' and STATUS='SCRATCH' in the OPEN. The first one will cause problems during debugging, when files from crashed runs will cause the program to stop. SCRATCH files will be created with system-provided names and sometimes in a differemt directory, and may clutter filespaces in very unpleasant ways. A unit with a ONAME and label based name is the easiest to track down in case of problems. On some systems, the error messages include file names. In-core temporary files ----------------------- Sometimes it is necessary to store large amounts of information in a temporary file and access it frequently. If the file is actually created and kept on a disk, the I/O may significantly degrade program's performance. In such cases, it may be useful to consider replacing the disk file with an array whenever enough memory is avaiable. When there is not enough memory, the disk file is the only option. In order to make such switches easy and automatic, an internal I/O subsystem (ZIO, in io/zio.f) has been developed. The ZIO system records requests for file sizes, and compares the total amount to available memory. If possible, all units are kept in core, making the program significantly faster. Then a big array is allocated, and subsequent I/O requests are re-directed into copying to and from that array instead. If memory is limited, some or all files will be opened as usual disk files, and regular I/O is performed. If you plan to use internal I/O, you will need to be able to calculate the size of the file you will use. You will define a new file by modifying the subroutine FILEOP, converting a template 'open' into a functioning piece of code. The file will stay open throughout the run, and will be closed automatically at the end. Things to consider in FILEOP are as follows: * Unit number variable. Use the IO_ convention and include the name into units.cmn . * Label. Same rules apply as above. * Extension. You will need to specify an extension, the MAKE_FILENAME will be provided by fileop. Same rules as above apply. * Priority. The higher the priority, the more probably the unit will stay in core. Priority 5 is typical, 9 is the highest and 1 is the lowest. If there are some special cases when you cannot predict the size of the file, you may include an IF which sets priority to zero, forcing the file to be external. * Number of blocks. Both for sequential and direct files, this number is required. * Number of data bytes. For direct files, multiply number of blocks by block size. For sequential files, add sizes of all blocks. Do not add any overhead, the ZIO will calculate any additional bytes required based on the number of blocks you supplied. * Please note that record length for direct files will be found indirectly from the values above. * If the file will be external and sequential, you may assign '1' to both sizes. For external direct, set both to the record length. Never assign zero. * Delete requirements. Unless you specify priority 0, you should set must_delete(IO_...) to .true. . You will not get a disk copy of internal files. It is also recommended to assign a negative number to all IO_ variables in initvar.f . This makes it easier to track errors when units are used without the appropriate 'open' in fileop. The in-core files can not be accessed via the usual READ and WRITE statements. You will need to use access subroutines instead: CALL ZxWRITE (IUNIT, ARRAY, IREC, ISIZE, LABEL) CALL ZxREAD (IUNIT, ARRAY, IREC, ISIZE, LABEL) CALL ZREWIND (IUNIT, LABEL) CALL ZBACKSPACE (IUNIT, LABEL) LOGICAL FUNCTION AT_EOF (IUNIT, LABEL) The character x is I for integer, R for real (actually double precision) and C for character. A single CHARACTER*(ISIZE) variable is read or written for the latter case. You will always need to specify IUNIT (the unit number), and LABEL (a character variable containing the name of the calling subroutine). For sequential READ and WRITE, you will also need ARRAY (the variable or array being read or written), and ISIZE (the number of integers, reals, or characters transferred). Pass a 1 or 0 as IREC, it will be ignored. For direct access, you will also need the record number, IREC. Record numbers start with 1 (one). ZREWIND and ZBACKSPACE can only be used on sequential files. Use AT_EOF in order to test the current location in the file, this will be similar to the functionality of END= parameter in READ clauses. INQUIRE is not supported. At the end of each run, a small table is printed showing statistics about internal I/O. If your newly added file regularly shows less than 100% utilization, you may need to re-specify the size requirements in FILEOP. Input Processing ---------------- If your new development requires small amount of input (e.g. a flag to turn the feature on, and/or a small number or parameters to change), you may add the input to the $CONTRL block. This addition is accomplished by modifying io/getvar.f, following the abundant examples present therein. You may actually assign your variable in this subroutine, all you need to add is the corresponding COMMON block include statement(s). If you prefer to re-scan the variable in your own subroutine, use the subroutines GET_INT, GET_REAL, GET_LOGICAL and GET_CHAR, all of which are documented in the comments of io/procfreeform.f . You may also look at the various examples in io/getvar.f . If you wish to add an input block with a different name, but with similar syntax as $CONTRL, you may do this very easily by adding a corresponding processing section to io/procinp.f, and then requesting the values using GET_INT, GET_REAL, etc. Examples of how to process blocks which contain a plain list of keywords can be found in io/procoutput.f and io/procextern.f. For other cases of input processing, the following recommendations may be useful. In order to find a block with a specified name, use: CHARACTER*80 LINE LOGICAL FIND C Setting FIND to .TRUE. will cause the program to stop if the C block is absent. FIND=.FALSE. CALL FINDBLOCK('MYBLOCK',LINE,FIND) IF (FIND) THEN C Process the block here. The first line is in LINE. Please note C that LINE is returned in the original case (lower is input so C by the user). C You may remove the $BLOCKNAME by calling: CALL DEDOLLAR (LINE) C Get the next line by doing: READ (5,'(A)',END=999) LINE C This removes comments CALL DECOMMENT (LINE) C This converts all to upper case CALL UPCASE (LINE) END IF (FINDBLOCK is in io/procinp.f) Alternately, you may specify that no input data can follow the $MYBLOCK, i.e. input starts from the next non-blank and non-comment line. In this case, you can call NEXTLINE: CALL NEXTLINE (LINE,'MYBLOCK') The block name is only used in error messages. If end of file is reached, such mesage is generated. Otherwise NEXTLINE returns the next usable line, in upper case and without comments.