program main c*********************************************************************72 c cc MAIN is the main program for MD_OPENMP. c c Discussion: c c The program implements a simple molecular dynamics simulation. c c The program uses Open MP directives to allow parallel computation. c c The velocity Verlet time integration scheme is used. c c The particles interact with a central pair potential. c c Output of the program is saved in the TNG format, which is why this c code is included in the TNG API release. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 8 Jan 2013 c c Author: c c Original FORTRAN90 version by Bill Magro. c FORTRAN77 version by John Burkardt. c TNG trajectory output by Magnus Lundborg. c c Parameters: c c None c implicit none include 'omp_lib.h' integer nd parameter ( nd = 3 ) integer np parameter ( np = 250 ) integer step_num parameter ( step_num = 1000 ) double precision acc(nd,np) double precision box(nd) double precision box_shape(9) double precision dt parameter ( dt = 0.0001D+00 ) double precision e0 double precision force(nd,np) integer i integer id double precision kinetic double precision mass parameter ( mass = 1.0D+00 ) double precision pos(nd,np) double precision potential integer proc_num integer seed integer step integer step_print integer step_print_index integer step_print_num integer step_save integer*8 sparse_save integer thread_num double precision vel(nd,np) double precision wtime c c Cray pointers are not standard fortran 77, but must be used to allocate c memory properly. c pointer (traj_p, traj) pointer (molecule_p, molecule) pointer (chain_p, chain) pointer (residue_p, residue) pointer (atom_p, atom) byte traj byte molecule byte chain byte residue byte atom c c The TNG functions expect 64 bit integers c integer*8 n_frames_per_frame_set integer*8 frames_saved_cnt integer*8 tng_n_particles c c These constants are also defined in tng_io.h, but need to c set in fortran as well. This can be copied to any fortran c source code using the tng_io library. c integer*8 TNG_UNCOMPRESSED parameter ( TNG_UNCOMPRESSED = 0) integer TNG_NON_TRAJECTORY_BLOCK parameter ( TNG_NON_TRAJECTORY_BLOCK = 0) integer TNG_TRAJECTORY_BLOCK parameter ( TNG_TRAJECTORY_BLOCK = 1) integer*8 TNG_GENERAL_INFO parameter ( TNG_GENERAL_INFO = 0 ) integer*8 TNG_MOLECULES parameter ( TNG_MOLECULES = 1 ) integer*8 TNG_TRAJECTORY_FRAME_SET parameter ( TNG_TRAJECTORY_FRAME_SET = 2 ) integer*8 TNG_PARTICLE_MAPPING parameter ( TNG_PARTICLE_MAPPING = 3 ) integer*8 TNG_TRAJ_BOX_SHAPE parameter ( TNG_TRAJ_BOX_SHAPE = 10000 ) integer*8 TNG_TRAJ_POSITIONS parameter ( TNG_TRAJ_POSITIONS = 10001 ) integer*8 TNG_TRAJ_VELOCITIES parameter ( TNG_TRAJ_VELOCITIES = 10002 ) integer*8 TNG_TRAJ_FORCES parameter ( TNG_TRAJ_FORCES = 10003 ) integer TNG_SKIP_HASH parameter ( TNG_SKIP_HASH = 0 ) integer TNG_USE_HASH parameter ( TNG_USE_HASH = 1 ) integer TNG_CHAR_DATA parameter ( TNG_CHAR_DATA = 0 ) integer TNG_INT_DATA parameter ( TNG_INT_DATA = 1 ) integer TNG_FLOAT_DATA parameter ( TNG_FLOAT_DATA = 2 ) integer TNG_DOUBLE_DATA parameter ( TNG_DOUBLE_DATA = 3 ) call timestamp ( ) write ( *, '(a)' ) ' ' write ( *, '(a)' ) 'MD_OPENMP' write ( *, '(a)' ) ' FORTRAN77/OpenMP version' write ( *, '(a)' ) ' ' write ( *, '(a)' ) ' A molecular dynamics program.' write ( *, '(a)' ) ' ' write ( *, '(a,i8)' ) & ' NP, the number of particles in the simulation is ', np write ( *, '(a,i8)' ) & ' STEP_NUM, the number of time steps, is ', step_num write ( *, '(a,g14.6)' ) & ' DT, the size of each time step, is ', dt write ( *, '(a)' ) ' ' write ( *, '(a,i8)' ) & ' The number of processors = ', omp_get_num_procs ( ) write ( *, '(a,i8)' ) & ' The number of threads = ', omp_get_max_threads ( ) write ( *, '(a)' ) ' ' write ( *, '(a)' ) ' Initializing trajectory storage.' call tng_trajectory_init(traj_p) c c N.B. The TNG output file should be modified according to needs c call tng_output_file_set(traj, TNG_EXAMPLE_FILES_DIR "tng_md_out_f77.tng") write ( *, '(a)' ) ' Creating molecules in trajectory.' tng_n_particles = np call tng_molecule_add(traj, "water", molecule_p) call tng_molecule_chain_add(traj, molecule, "W", chain_p) call tng_chain_residue_add(traj, chain, "WAT", residue_p) call tng_residue_atom_add(traj, residue, "O", "O", atom_p) call tng_molecule_cnt_set(traj, molecule, tng_n_particles) c c Set the dimensions of the box. c do i = 1, 9 box_shape(i) = 0.0 end do do i = 1, nd box(i) = 10.0D+00 box_shape(i*nd + i) = box(i) end do c c Add the box shape data block c call tng_data_block_add(traj, TNG_TRAJ_BOX_SHAPE, "BOX SHAPE", & TNG_DOUBLE_DATA, TNG_NON_TRAJECTORY_BLOCK, int(1, 8), & int(9, 8), int(1, 8), TNG_UNCOMPRESSED, box_shape) c c Write the file headers c call tng_file_headers_write(traj, TNG_USE_HASH) c c Set initial positions, velocities, and accelerations. c write ( *, '(a)' ) & ' Initializing positions, velocities, and accelerations.' seed = 123456789 call initialize ( np, nd, box, seed, pos, vel, acc ) c c Compute the forces and energies. c write ( *, '(a)' ) ' ' write ( *, '(a)' ) ' Computing initial forces and energies.' call compute ( np, nd, pos, vel, mass, force, potential, & kinetic ) e0 = potential + kinetic c c Saving frequency c step_save = 5 step_print = 0 step_print_index = 0 step_print_num = 10 sparse_save = 10 frames_saved_cnt = 0 c c This is the main time stepping loop. c write ( *, '(a,i4,a)' ) ' Every', step_save, & ' steps particle positions, velocities and forces are' write ( *, '(a)' ) ' saved to a TNG trajectory file.' write ( *, '(a)' ) write ( *, '(a)' ) & ' At each step, we report the potential and kinetic energies.' write ( *, '(a)' ) & ' The sum of these energies should be a constant.' write ( *, '(a)' ) & ' As an accuracy check, we also print the relative error' write ( *, '(a)' ) ' in the total energy.' write ( *, '(a)' ) ' ' write ( *, '(a)' ) & ' Step Potential Kinetic (P+K-E0)/E0' write ( *, '(a)' ) & ' Energy P Energy K ' // & 'Relative Energy Error' write ( *, '(a)' ) ' ' step = 0 write ( *, '(2x,i8,2x,g14.6,2x,g14.6,2x,g14.6)' ) & step, potential, kinetic, ( potential + kinetic - e0 ) / e0 step_print_index = step_print_index + 1 step_print = ( step_print_index * step_num ) / step_print_num c c Create a frame set for writing data c call tng_num_frames_per_frame_set_get(traj, & n_frames_per_frame_set) call tng_frame_set_new(traj, int(0, 8), n_frames_per_frame_set) c c Add empty data blocks. c call tng_particle_data_block_add(traj, TNG_TRAJ_POSITIONS, & "POSITIONS", TNG_DOUBLE_DATA, TNG_TRAJECTORY_BLOCK, & n_frames_per_frame_set, int(3, 8), int(1, 8), int(0, 8), & tng_n_particles, TNG_UNCOMPRESSED, %VAL(int(0, 8))) call tng_particle_data_block_add(traj, TNG_TRAJ_VELOCITIES, & "VELOCITIES", TNG_DOUBLE_DATA, TNG_TRAJECTORY_BLOCK, & n_frames_per_frame_set, int(3, 8), int(1, 8), int(0, 8), & tng_n_particles, TNG_UNCOMPRESSED, %VAL(int(0, 8))) call tng_particle_data_block_add(traj, TNG_TRAJ_FORCES, & "FORCES", TNG_DOUBLE_DATA, TNG_TRAJECTORY_BLOCK, & n_frames_per_frame_set, int(3, 8), int(1, 8), int(0, 8), & tng_n_particles, TNG_UNCOMPRESSED, %VAL(int(0, 8))) c c The potential energy data block is saved sparsely. c call tng_data_block_add(traj, int(10101, 8), & "POTENTIAL ENERGY", TNG_DOUBLE_DATA, TNG_TRAJECTORY_BLOCK, & n_frames_per_frame_set, int(1, 8), sparse_save, & TNG_UNCOMPRESSED, %VAL(int(0, 8))) c c Write the frame set to disk c call tng_frame_set_write(traj, TNG_USE_HASH) wtime = omp_get_wtime ( ) do step = 1, step_num call compute ( np, nd, pos, vel, mass, force, potential, & kinetic ) if ( step .eq. step_print ) then write ( *, '(2x,i8,2x,g14.6,2x,g14.6,2x,g14.6)' ) & step, potential, kinetic, ( potential + kinetic - e0 ) / e0 step_print_index = step_print_index + 1 step_print = ( step_print_index * step_num ) / step_print_num end if c c Output to TNG file at regular intervals, specified by step_save c if ( step_save .EQ. 0 .OR. mod(step, step_save) .EQ. 0 ) then call tng_frame_particle_data_write(traj, frames_saved_cnt, & TNG_TRAJ_POSITIONS, int(0, 8), tng_n_particles, pos, & TNG_USE_HASH) call tng_frame_particle_data_write(traj, frames_saved_cnt, & TNG_TRAJ_VELOCITIES, int(0, 8), tng_n_particles, vel, & TNG_USE_HASH) call tng_frame_particle_data_write(traj, frames_saved_cnt, & TNG_TRAJ_FORCES, int(0, 8), tng_n_particles, force, & TNG_USE_HASH) frames_saved_cnt = frames_saved_cnt + 1 if (mod(step, step_save * sparse_save) .EQ. 0) then call tng_frame_data_write(traj, frames_saved_cnt, & int(10101, 8), potential, TNG_USE_HASH) end if end if call update ( np, nd, pos, vel, force, acc, mass, dt ) end do wtime = omp_get_wtime ( ) - wtime write ( *, '(a)' ) ' ' write ( *, '(a)' ) & ' Elapsed time for main computation:' write ( *, '(2x,g14.6,a)' ) wtime, ' seconds' c c Terminate. c call tng_trajectory_destroy(traj_p) write ( *, '(a)' ) ' ' write ( *, '(a)' ) 'MD_OPENMP' write ( *, '(a)' ) ' Normal end of execution.' write ( *, '(a)' ) ' ' call timestamp ( ) stop end subroutine compute ( np, nd, pos, vel, mass, f, pot, kin ) c*********************************************************************72 c cc COMPUTE computes the forces and energies. c c Discussion: c c The computation of forces and energies is fully parallel. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 31 July 2009 c c Author: c c Original FORTRAN90 version by Bill Magro. c FORTRAN77 version by John Burkardt. c c Parameters: c c Input, integer NP, the number of particles. c c Input, integer ND, the number of spatial dimensions. c c Input, double precision POS(ND,NP), the position of each particle. c c Input, double precision VEL(ND,NP), the velocity of each particle. c c Input, double precision MASS, the mass of each particle. c implicit none integer np integer nd double precision d double precision d2 double precision dv double precision f(nd,np) integer i integer j integer k double precision kin double precision mass double precision PI2 parameter ( PI2 = 3.141592653589793D+00 / 2.0D+00 ) double precision pos(nd,np) double precision pot double precision rij(nd) double precision v double precision vel(nd,np) pot = 0.0D+00 kin = 0.0D+00 c$omp parallel c$omp& shared ( f, nd, np, pos, vel ) c$omp& private ( d, d2, i, j, k, rij ) c$omp do reduction ( + : pot, kin ) do i = 1, np c c Compute the potential energy and forces. c do k = 1, nd f(k,i) = 0.0D+00 end do do j = 1, np if ( i .ne. j ) then call dist ( nd, pos(1,i), pos(1,j), rij, d ) c c Attribute half of the potential energy to particle J. c d2 = min ( d, pi2 ) pot = pot + 0.5D+00 * ( sin ( d2 ) )**2 do k = 1, nd f(k,i) = f(k,i) - rij(k) * sin ( 2.0D+00 * d2 ) / d end do end if end do c c Compute the kinetic energy. c do k = 1, nd kin = kin + vel(k,i)**2 end do end do c$omp end do c$omp end parallel kin = kin * 0.5D+00 * mass return end subroutine dist ( nd, r1, r2, dr, d ) c*********************************************************************72 c cc DIST computes the displacement (and its norm) between two particles. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 13 November 2007 c c Author: c c Original FORTRAN90 version by Bill Magro. c FORTRAN77 version by John Burkardt. c c Parameters: c c Input, integer ND, the number of spatial dimensions. c c Input, double precision R1(ND), R2(ND), the positions of the particles. c c Output, double precision DR(ND), the displacement vector. c c Output, double precision D, the Euclidean norm of the displacement. c implicit none integer nd double precision d double precision dr(nd) integer i double precision r1(nd) double precision r2(nd) do i = 1, nd dr(i) = r1(i) - r2(i) end do d = 0.0D+00 do i = 1, nd d = d + dr(i)**2 end do d = sqrt ( d ) return end subroutine initialize ( np, nd, box, seed, pos, vel, acc ) c*********************************************************************72 c cc INITIALIZE initializes the positions, velocities, and accelerations. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 13 November 2007 c c Author: c c Original FORTRAN90 version by Bill Magro. c FORTRAN77 version by John Burkardt. c c Parameters: c c Input, integer NP, the number of particles. c c Input, integer ND, the number of spatial dimensions. c c Input, double precision BOX(ND), specifies the maximum position c of particles in each dimension. c c Input/output, integer SEED, a seed for the random number generator. c c Output, double precision POS(ND,NP), the position of each particle. c c Output, double precision VEL(ND,NP), the velocity of each particle. c c Output, double precision ACC(ND,NP), the acceleration of each particle. c implicit none integer np integer nd double precision acc(nd,np) double precision box(nd) integer i integer j double precision pos(nd,np) double precision r8_uniform_01 integer seed double precision vel(nd,np) c c Give the particles random positions within the box. c do i = 1, nd do j = 1, np pos(i,j) = r8_uniform_01 ( seed ) end do end do c$omp parallel c$omp& shared ( acc, box, nd, np, pos, vel ) c$omp& private ( i, j ) c$omp do do j = 1, np do i = 1, nd pos(i,j) = box(i) * pos(i,j) vel(i,j) = 0.0D+00 acc(i,j) = 0.0D+00 end do end do c$omp end do c$omp end parallel return end function r8_uniform_01 ( seed ) c*********************************************************************72 c cc R8_UNIFORM_01 returns a unit pseudorandom R8. c c Discussion: c c This routine implements the recursion c c seed = 16807 * seed mod ( 2**31 - 1 ) c r8_uniform_01 = seed / ( 2**31 - 1 ) c c The integer arithmetic never requires more than 32 bits, c including a sign bit. c c If the initial seed is 12345, then the first three computations are c c Input Output R8_UNIFORM_01 c SEED SEED c c 12345 207482415 0.096616 c 207482415 1790989824 0.833995 c 1790989824 2035175616 0.947702 c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 11 August 2004 c c Author: c c John Burkardt c c Reference: c c Paul Bratley, Bennett Fox, Linus Schrage, c A Guide to Simulation, c Springer Verlag, pages 201-202, 1983. c c Pierre L'Ecuyer, c Random Number Generation, c in Handbook of Simulation, c edited by Jerry Banks, c Wiley Interscience, page 95, 1998. c c Bennett Fox, c Algorithm 647: c Implementation and Relative Efficiency of Quasirandom c Sequence Generators, c ACM Transactions on Mathematical Software, c Volume 12, Number 4, pages 362-376, 1986. c c Peter Lewis, Allen Goodman, James Miller, c A Pseudo-Random Number Generator for the System/360, c IBM Systems Journal, c Volume 8, pages 136-143, 1969. c c Parameters: c c Input/output, integer SEED, the "seed" value, which should NOT be 0. c On output, SEED has been updated. c c Output, double precision R8_UNIFORM_01, a new pseudorandom variate, c strictly between 0 and 1. c implicit none double precision r8_uniform_01 integer k integer seed if ( seed .eq. 0 ) then write ( *, '(a)' ) ' ' write ( *, '(a)' ) 'R8_UNIFORM_01 - Fatal error!' write ( *, '(a)' ) ' Input value of SEED = 0.' stop end if k = seed / 127773 seed = 16807 * ( seed - k * 127773 ) - k * 2836 if ( seed .lt. 0 ) then seed = seed + 2147483647 end if c c Although SEED can be represented exactly as a 32 bit integer, c it generally cannot be represented exactly as a 32 bit real number! c r8_uniform_01 = dble ( seed ) * 4.656612875D-10 return end subroutine timestamp ( ) c*********************************************************************72 c cc TIMESTAMP prints out the current YMDHMS date as a timestamp. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 12 January 2007 c c Author: c c John Burkardt c c Parameters: c c None c implicit none character * ( 8 ) ampm integer d character * ( 8 ) date integer h integer m integer mm character * ( 9 ) month(12) integer n integer s character * ( 10 ) time integer y save month data month / & 'January ', 'February ', 'March ', 'April ', & 'May ', 'June ', 'July ', 'August ', & 'September', 'October ', 'November ', 'December ' / call date_and_time ( date, time ) read ( date, '(i4,i2,i2)' ) y, m, d read ( time, '(i2,i2,i2,1x,i3)' ) h, n, s, mm if ( h .lt. 12 ) then ampm = 'AM' else if ( h .eq. 12 ) then if ( n .eq. 0 .and. s .eq. 0 ) then ampm = 'Noon' else ampm = 'PM' end if else h = h - 12 if ( h .lt. 12 ) then ampm = 'PM' else if ( h .eq. 12 ) then if ( n .eq. 0 .and. s .eq. 0 ) then ampm = 'Midnight' else ampm = 'AM' end if end if end if write ( *, & '(i2,1x,a,1x,i4,2x,i2,a1,i2.2,a1,i2.2,a1,i3.3,1x,a)' ) & d, month(m), y, h, ':', n, ':', s, '.', mm, ampm return end subroutine update ( np, nd, pos, vel, f, acc, mass, dt ) c*********************************************************************72 c cc UPDATE performs the time integration, using a velocity Verlet algorithm. c c Discussion: c c The time integration is fully parallel. c c Licensing: c c This code is distributed under the GNU LGPL license. c c Modified: c c 13 November 2007 c c Author: c c Original FORTRAN90 version by Bill Magro. c FORTRAN77 version by John Burkardt. c c Parameters: c c Input, integer NP, the number of particles. c c Input, integer ND, the number of spatial dimensions. c c Input/output, double precision POS(ND,NP), the position of each particle. c c Input/output, double precision VEL(ND,NP), the velocity of each particle. c c Input, double precision MASS, the mass of each particle. c c Input/output, double precision ACC(ND,NP), the acceleration of each c particle. c implicit none integer np integer nd double precision acc(nd,np) double precision dt double precision f(nd,np) integer i integer j double precision mass double precision pos(nd,np) double precision rmass double precision vel(nd,np) rmass = 1.0D+00 / mass c$omp parallel c$omp& shared ( acc, dt, f, nd, np, pos, rmass, vel ) c$omp& private ( i, j ) c$omp do do j = 1, np do i = 1, nd pos(i,j) = pos(i,j) & + vel(i,j) * dt + 0.5D+00 * acc(i,j) * dt * dt vel(i,j) = vel(i,j) & + 0.5D+00 * dt * ( f(i,j) * rmass + acc(i,j) ) acc(i,j) = f(i,j) * rmass end do end do c$omp end do c$omp end parallel return end