From 8df7694b828542b9f74aae0e09f3875cba50825c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 8 Jun 2013 14:28:21 -0400 Subject: [PATCH 01/84] Added means of outputting lost particle tracks --- src/constants.F90 | 1 + src/physics.F90 | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/constants.F90 b/src/constants.F90 index e9688e761d..8887e1fc9b 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -363,6 +363,7 @@ module constants integer, parameter :: UNIT_STATE = 16 ! unit # for writing state point integer, parameter :: CMFD_BALANCE = 17 ! unit # for writing cmfd balance file integer, parameter :: UNIT_PARTICLE = 18 ! unit # for writing particle restart + integer, parameter :: UNIT_TRACK = 19 ! unit # for writing particle tracks !============================================================================= ! CMFD CONSTANTS diff --git a/src/physics.F90 b/src/physics.F90 index 707049b1a8..e1c7f12c32 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -73,6 +73,12 @@ contains ! Force calculation of cross-sections by setting last energy to zero micro_xs % last_E = ZERO + if (run_mode == MODE_PARTICLE) then + open(UNIT=UNIT_TRACK, FILE='test', STATUS='replace', & + ACCESS='stream') + write(UNIT_TRACK) p % coord0 % xyz + end if + do while (p % alive) ! Calculate microscopic and macroscopic cross sections -- note: if the @@ -100,6 +106,10 @@ contains coord % xyz = coord % xyz + distance * coord % uvw coord => coord % next end do + + if (run_mode == MODE_PARTICLE) then + write(UNIT_TRACK) p % coord0 % xyz + end if ! Score track-length tallies if (active_tracklength_tallies % size() > 0) & @@ -175,6 +185,10 @@ contains end do + if (run_mode == MODE_PARTICLE) then + close(UNIT=UNIT_TRACK) + end if + end subroutine transport !=============================================================================== From 18ea0af331e255cd4497336a408187f9adb5e5c1 Mon Sep 17 00:00:00 2001 From: Sterling Date: Mon, 10 Jun 2013 12:35:59 -0400 Subject: [PATCH 02/84] Cleaned up implimentation of track output --- src/DEPENDENCIES | 1 + src/global.F90 | 3 +++ src/initialize.F90 | 2 ++ src/particle_restart.F90 | 20 ++++++++++++++++++++ src/physics.F90 | 16 ++++++---------- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index c241153e76..dfe7390f1d 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -294,6 +294,7 @@ physics.o: material_header.o physics.o: mesh.o physics.o: output.o physics.o: particle_header.o +physics.o: particle_restart.o physics.o: particle_restart_write.o physics.o: random_lcg.o physics.o: search.o diff --git a/src/global.F90 b/src/global.F90 index e0f0bd5bbf..43f8025455 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -283,6 +283,9 @@ module global ! Particle restart run logical :: particle_restart_run = .false. + ! Particle track output + logical :: write_track = .false. + ! ============================================================================ ! CMFD VARIABLES diff --git a/src/initialize.F90 b/src/initialize.F90 index dd28f9bf9c..f8a474c545 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -311,6 +311,8 @@ contains i = i + 1 path_particle_restart = argv(i) particle_restart_run = .true. + case ('-tr', '-track', '--track') + write_track = .true. case default message = "Unknown command line option: " // argv(i) call fatal_error() diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 07c7f5475e..e3bd96afb8 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -11,6 +11,7 @@ module particle_restart use physics, only: transport use random_lcg, only: set_particle_seed use source, only: initialize_particle + use string, only: to_str #ifdef HDF5 use hdf5_interface @@ -19,6 +20,7 @@ module particle_restart implicit none private public :: run_particle_restart + public :: write_particle_track #ifdef HDF5 integer(HID_T) :: hdf5_particle_file @@ -118,6 +120,7 @@ contains subroutine run_particle_restart() integer(8) :: particle_seed + character(MAX_FILE_LEN) :: filename ! initialize the particle to be tracked allocate(p) @@ -138,9 +141,22 @@ contains current_gen - 1)*n_particles + p % id call set_particle_seed(particle_seed) + ! Open particle track output file. + if (write_track) then + filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_work)) // '.binary' + open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & + ACCESS='stream') + end if + ! transport neutron call transport() + ! Close particle track output file. + if (write_track) then + close(UNIT=UNIT_TRACK) + endif + ! write output if particle made it write(ou,*) 'Particle Successfully Transport:' write(ou,*) 'WEIGHT:', p % wgt @@ -150,4 +166,8 @@ contains end subroutine run_particle_restart + subroutine write_particle_track() + write(UNIT_TRACK) p % coord0 % xyz + end subroutine write_particle_track + end module particle_restart diff --git a/src/physics.F90 b/src/physics.F90 index e1c7f12c32..ba8eac3fa5 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -15,6 +15,7 @@ module physics use mesh, only: get_mesh_indices use output, only: write_message use particle_header, only: LocalCoord + use particle_restart, only: write_particle_track use particle_restart_write, only: write_particle_restart use random_lcg, only: prn use search, only: binary_search @@ -73,14 +74,13 @@ contains ! Force calculation of cross-sections by setting last energy to zero micro_xs % last_E = ZERO - if (run_mode == MODE_PARTICLE) then - open(UNIT=UNIT_TRACK, FILE='test', STATUS='replace', & - ACCESS='stream') - write(UNIT_TRACK) p % coord0 % xyz - end if - do while (p % alive) + ! Write particle track. + if (write_track) then + call write_particle_track() + endif + ! Calculate microscopic and macroscopic cross sections -- note: if the ! material is the same as the last material and the energy of the ! particle hasn't changed, we don't need to lookup cross sections again. @@ -185,10 +185,6 @@ contains end do - if (run_mode == MODE_PARTICLE) then - close(UNIT=UNIT_TRACK) - end if - end subroutine transport !=============================================================================== From 3bae132b6d3b29481fd9879a6fc1f4452887e7bb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 10 Jun 2013 22:08:27 -0400 Subject: [PATCH 03/84] Cleaned up Fortran implimentation of track writing and added track.py - a utility for converting binary track files to VTK files. --- src/utils/track.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 src/utils/track.py diff --git a/src/utils/track.py b/src/utils/track.py new file mode 100755 index 0000000000..7c836809ea --- /dev/null +++ b/src/utils/track.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python2 +"""Convert binary particle track to VTK poly data. + +Run 'track.py -help' for usage. + +""" + +import os +import argparse +import struct +import vtk + +if __name__ == '__main__': + # Create argument parser. + parser = argparse.ArgumentParser( + description='Convert binary particle track file to a .pvtp file.') + parser.add_argument('input', metavar='IN', type=argparse.FileType('rb'), + help='Input particle track data filename.') + parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, dest='out', + help='Output VTK poly data filename.') + # Parse commandline arguments. + args = parser.parse_args() + + # Make sure that the output filename ends with '.pvtp'. + if not args.out: + args.out = ''.join([os.path.splitext(args.input.name)[0], '.pvtp']) + elif os.path.splitext(args.out)[1] != '.pvtp': + args.out = ''.join([args.out, '.pvtp']) + + # Convert binary data into a list of coordinate triplets. + track = args.input.read() + coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) + for i in range(len(track)/24)] + + # Create VTK points. + points = vtk.vtkPoints() + for triplet in coords: + points.InsertNextPoint(triplet) + + # Create VTK line and assign points to line. + line = vtk.vtkPolyLine() + line.GetPointIds().SetNumberOfIds(points.GetNumberOfPoints()) + for i in range(points.GetNumberOfPoints()): + line.GetPointIds().SetId(i,i) + + cells = vtk.vtkCellArray() + cells.InsertNextCell(line) + + data = vtk.vtkPolyData() + data.SetPoints(points) + data.SetLines(cells) + + writer = vtk.vtkXMLPPolyDataWriter() + writer.SetInput(data) + writer.SetFileName(args.out) + writer.Write() + +##poly_mapper = vtk.vtkPolyDataMapper() +##poly_mapper.SetInputConnection(data.GetProducerPort()) +## +##poly_actor = vtk.vtkActor() +##poly_actor.SetMapper(poly_mapper) +## +##ren1 = vtk.vtkRenderer() +##ren1.AddActor(poly_actor) +##ren1.SetBackground(0.0, 0.0, 0.0) +## +##ren1.ResetCamera() +## +##ren_win = vtk.vtkRenderWindow() +##ren_win.AddRenderer(ren1) +##ren_win.SetSize(400, 400) +## +##iren = vtk.vtkRenderWindowInteractor() +##iren.SetRenderWindow(ren_win) +##iren.Initialize() +##iren.Start() From 883d69343c90a21dc9a7bb30c187822ebcf48af6 Mon Sep 17 00:00:00 2001 From: Sterling Date: Tue, 11 Jun 2013 21:20:34 -0400 Subject: [PATCH 04/84] Added comments to write_particle_track subroutine and gave track.py the ability to add multiple tracks to one file. --- src/particle_restart.F90 | 7 +++++ src/utils/track.py | 67 ++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 1af6e2d30c..15afeb4cb5 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -165,6 +165,13 @@ contains end subroutine run_particle_restart +!=============================================================================== +! WRITE_PARTICLE_TRACK outputs particle position to a binary file. This +! subroutine needs to be modified to work with HDF5 files. Perhaps it should +! also be made somehow more general so that it can output information other +! than just particle position. +!=============================================================================== + subroutine write_particle_track() write(UNIT_TRACK) p % coord0 % xyz end subroutine write_particle_track diff --git a/src/utils/track.py b/src/utils/track.py index 7c836809ea..a5b33b7258 100755 --- a/src/utils/track.py +++ b/src/utils/track.py @@ -10,51 +10,78 @@ import argparse import struct import vtk -if __name__ == '__main__': + +def _parse_args(): # Create argument parser. parser = argparse.ArgumentParser( description='Convert binary particle track file to a .pvtp file.') parser.add_argument('input', metavar='IN', type=argparse.FileType('rb'), + nargs='+', help='Input particle track data filename.') parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, dest='out', help='Output VTK poly data filename.') - # Parse commandline arguments. - args = parser.parse_args() + # Parse and return commandline arguments. + return parser.parse_args() + + +def main(): + # Parse commandline argumetns. + args = _parse_args() + # Make sure that the output filename ends with '.pvtp'. if not args.out: args.out = ''.join([os.path.splitext(args.input.name)[0], '.pvtp']) elif os.path.splitext(args.out)[1] != '.pvtp': args.out = ''.join([args.out, '.pvtp']) - # Convert binary data into a list of coordinate triplets. - track = args.input.read() - coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) - for i in range(len(track)/24)] - - # Create VTK points. points = vtk.vtkPoints() - for triplet in coords: - points.InsertNextPoint(triplet) - - # Create VTK line and assign points to line. - line = vtk.vtkPolyLine() - line.GetPointIds().SetNumberOfIds(points.GetNumberOfPoints()) - for i in range(points.GetNumberOfPoints()): - line.GetPointIds().SetId(i,i) - cells = vtk.vtkCellArray() - cells.InsertNextCell(line) + j = 0 + k = 0 + arr = vtk.vtkIntArray() + for fin in args.input: + track = fin.read() + coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) + for i in range(len(track)/24)] + n_points = len(coords) + i=0 + for triplet in coords: + points.InsertNextPoint(triplet) + arr.InsertTuple1(i,k) + i+=1 + + # Create VTK line and assign points to line. + line = vtk.vtkPolyLine() + line.GetPointIds().SetNumberOfIds(n_points) + for i in range(n_points): + line.GetPointIds().SetId(i, j+i) + + cells.InsertNextCell(line) + j += n_points + k += 1 + + points.SetData(arr) data = vtk.vtkPolyData() data.SetPoints(points) data.SetLines(cells) - + writer = vtk.vtkXMLPPolyDataWriter() writer.SetInput(data) writer.SetFileName(args.out) writer.Write() + + +if __name__ == '__main__': + main() + + +#### The following code is retained for debugging purposes. If 'data' in the +#### in the function 'main' is made a global variable, the following code will +#### automatically plot the tracks. + ##poly_mapper = vtk.vtkPolyDataMapper() ##poly_mapper.SetInputConnection(data.GetProducerPort()) ## From 44999762e7deedb1cb8bb7274ca33d3579a639ec Mon Sep 17 00:00:00 2001 From: Sterling Date: Wed, 12 Jun 2013 16:41:09 -0400 Subject: [PATCH 05/84] Made minor edits to implimentation. Moved particle track subroutines to the output module. Particle tracks can now be written in any run mode that calls transport. track.py should correctly handle any number of input particle tracks. --- src/DEPENDENCIES | 1 - src/output.F90 | 40 ++++++++++++++++++++++++++++++++++++++++ src/particle_restart.F90 | 26 -------------------------- src/physics.F90 | 22 ++++++++++++++++++---- src/utils/track.py | 36 +++++++++++++++++++++--------------- 5 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index b71354ccaa..fcabd989e0 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -297,7 +297,6 @@ physics.o: material_header.o physics.o: mesh.o physics.o: output.o physics.o: particle_header.o -physics.o: particle_restart.o physics.o: particle_restart_write.o physics.o: random_lcg.o physics.o: search.o diff --git a/src/output.F90 b/src/output.F90 index b8302226d7..e2114c8b48 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -1945,4 +1945,44 @@ contains end function get_label +!=============================================================================== +! INITIALIZE_PARTICLE_TRACK opens a particle track output file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. It +! should also probably write a header that identifies the file as a particle +! track and maybe adds particle identifying information (batch #, etc.). +!=============================================================================== + + subroutine initialize_particle_track() + character(MAX_FILE_LEN) :: filename + + filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_work)) // '.binary' + open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & + ACCESS='stream') + + end subroutine initialize_particle_track + +!=============================================================================== +! WRITE_PARTICLE_TRACK outputs particle position to a binary file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. Perhaps +! it should also be made somehow more general so that it can output +! information other than just particle position. +!=============================================================================== + + subroutine write_particle_track() + write(UNIT_TRACK) p % coord0 % xyz + end subroutine write_particle_track + +!=============================================================================== +! FINALIZE_PARTICLE_TRACK closes the particle track file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. +!=============================================================================== + + subroutine finalize_particle_track() + close(UNIT=UNIT_TRACK) + end subroutine finalize_particle_track + end module output diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 15afeb4cb5..39d8156fdb 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -11,7 +11,6 @@ module particle_restart use physics, only: transport use random_lcg, only: set_particle_seed use source, only: initialize_particle - use string, only: to_str #ifdef HDF5 use hdf5_interface @@ -20,7 +19,6 @@ module particle_restart implicit none private public :: run_particle_restart - public :: write_particle_track #ifdef HDF5 integer(HID_T) :: hdf5_particle_file @@ -144,36 +142,12 @@ contains current_gen - 1)*n_particles + p % id call set_particle_seed(particle_seed) - ! Open particle track output file. - if (write_track) then - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_work)) // '.binary' - open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & - ACCESS='stream') - end if - ! transport neutron call transport() - ! Close particle track output file. - if (write_track) then - close(UNIT=UNIT_TRACK) - endif - ! write output if particle made it call print_particle() end subroutine run_particle_restart -!=============================================================================== -! WRITE_PARTICLE_TRACK outputs particle position to a binary file. This -! subroutine needs to be modified to work with HDF5 files. Perhaps it should -! also be made somehow more general so that it can output information other -! than just particle position. -!=============================================================================== - - subroutine write_particle_track() - write(UNIT_TRACK) p % coord0 % xyz - end subroutine write_particle_track - end module particle_restart diff --git a/src/physics.F90 b/src/physics.F90 index ba8eac3fa5..a5f3318538 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -13,9 +13,11 @@ module physics use interpolation, only: interpolate_tab1 use material_header, only: Material use mesh, only: get_mesh_indices - use output, only: write_message + use output, only: write_message, initialize_particle_track, & + write_particle_track, & + finalize_particle_track use particle_header, only: LocalCoord - use particle_restart, only: write_particle_track +! use particle_restart, only: write_particle_track use particle_restart_write, only: write_particle_restart use random_lcg, only: prn use search, only: binary_search @@ -74,6 +76,11 @@ contains ! Force calculation of cross-sections by setting last energy to zero micro_xs % last_E = ZERO + ! Prepare to write out particle track. + if (write_track) then + call initialize_particle_track() + endif + do while (p % alive) ! Write particle track. @@ -106,9 +113,11 @@ contains coord % xyz = coord % xyz + distance * coord % uvw coord => coord % next end do + + ! Write particle track - if (run_mode == MODE_PARTICLE) then - write(UNIT_TRACK) p % coord0 % xyz + if (write_track) then + call write_particle_track() end if ! Score track-length tallies @@ -185,6 +194,11 @@ contains end do + ! Finish particle track. + if (write_track) then + call finalize_particle_track() + endif + end subroutine transport !=============================================================================== diff --git a/src/utils/track.py b/src/utils/track.py index a5b33b7258..1907d1ec0a 100755 --- a/src/utils/track.py +++ b/src/utils/track.py @@ -1,7 +1,19 @@ #!/usr/bin/env python2 """Convert binary particle track to VTK poly data. -Run 'track.py -help' for usage. +Usage information can be obtained by running 'track.py --help': + + usage: track.py [-h] [-o OUT] IN [IN ...] + + Convert binary particle track file to a .pvtp file. + + positional arguments: + IN Input particle track data filename(s). + + optional arguments: + -h, --help show this help message and exit + -o OUT, -out OUT, --out OUT + Output VTK poly data filename. """ @@ -17,13 +29,14 @@ def _parse_args(): description='Convert binary particle track file to a .pvtp file.') parser.add_argument('input', metavar='IN', type=argparse.FileType('rb'), nargs='+', - help='Input particle track data filename.') - parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, dest='out', + help='Input particle track data filename(s).') + parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, + dest='out', help='Output VTK poly data filename.') # Parse and return commandline arguments. return parser.parse_args() - + def main(): # Parse commandline argumetns. @@ -31,7 +44,7 @@ def main(): # Make sure that the output filename ends with '.pvtp'. if not args.out: - args.out = ''.join([os.path.splitext(args.input.name)[0], '.pvtp']) + args.out = 'tracks.pvtp' elif os.path.splitext(args.out)[1] != '.pvtp': args.out = ''.join([args.out, '.pvtp']) @@ -39,18 +52,14 @@ def main(): cells = vtk.vtkCellArray() j = 0 k = 0 - arr = vtk.vtkIntArray() for fin in args.input: track = fin.read() coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) for i in range(len(track)/24)] n_points = len(coords) - - i=0 + for triplet in coords: points.InsertNextPoint(triplet) - arr.InsertTuple1(i,k) - i+=1 # Create VTK line and assign points to line. line = vtk.vtkPolyLine() @@ -61,8 +70,7 @@ def main(): cells.InsertNextCell(line) j += n_points k += 1 - - points.SetData(arr) + global data data = vtk.vtkPolyData() data.SetPoints(points) data.SetLines(cells) @@ -78,9 +86,7 @@ if __name__ == '__main__': main() -#### The following code is retained for debugging purposes. If 'data' in the -#### in the function 'main' is made a global variable, the following code will -#### automatically plot the tracks. +#### The following code is retained for debugging purposes: ##poly_mapper = vtk.vtkPolyDataMapper() ##poly_mapper.SetInputConnection(data.GetProducerPort()) From f866b437d8741de457bcf8deb25a9f2c9e626126 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 17 Jun 2013 15:00:37 -0400 Subject: [PATCH 06/84] Added settings.xml track options and updated docs Tracks can now be made using the element in settings.xml and the documentation has been updated to include track plotting. --- docs/img/Tracks.png | Bin 0 -> 94057 bytes docs/source/usersguide/input.rst | 9 +++++++++ docs/source/usersguide/processing.rst | 25 ++++++++++++++++++++++--- src/global.F90 | 8 +++++--- src/initialize.F90 | 2 +- src/input_xml.F90 | 17 +++++++++++++++++ src/output.F90 | 3 ++- src/source.F90 | 16 ++++++++++++++++ src/templates/settings_t.xml | 1 + 9 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 docs/img/Tracks.png diff --git a/docs/img/Tracks.png b/docs/img/Tracks.png new file mode 100644 index 0000000000000000000000000000000000000000..39c83cd59557dc5942c9838adf79e9f91ef1fc72 GIT binary patch literal 94057 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 ZOPvQ@6B2T8{EcUP%2zqHctN05hNK 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%0AH;1+ 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

>rPy^m{&64m%@njLjSUXMiR*^4~rjsnKI$dJksZ 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 XOpW+RCHjDED@aRCU$tD>Hsb#PV-jyl literal 0 HcmV?d00001 diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index d9b43c8214..fcd4bb73c3 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -390,6 +390,15 @@ integers: the batch number, generation number, and particle number. *Default*: None +.. _track: + +```` Element +------------------- + +The ```` element specifies particles for which OpenMC will output binary files describing particle position at every step of its transport. This element should be followed by triplets of integers. Each triplet describes one particle. The integers in each triplet specify the batch number, generation number, and particle number, respectively. + + *Default*: None + ```` Element ------------------------ diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 7105e9f7d1..94b8a71be0 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -345,15 +345,34 @@ Getting Data into MATLAB ------------------------ There is currently no front-end utility to dump tally data to MATLAB files, but -the process is straightforward. First extract the data using a custom Python -script with statepoint.py, put the data into appropriately-shaped numpy arrays, +the process is straightforward. First extract the data using a custom Python script with statepoint.py, put the data into appropriately-shaped numpy arrays, and then use the `Scipy MATLAB IO routines `_ to save to a MAT file. Note that the data contained in the output from ``StatePoint.extract_result`` is already in a Numpy array that can be reshaped and dumped to MATLAB in one step. - +---------------------------- +Particle Track Visualization +---------------------------- + +.. image:: ../../img/Tracks.png + :height: 200px + +OpenMC can dump particle tracks—the position of particles as they are transported through the geometry. There are two ways to make OpenMC output tracks: all particle tracks through a commandline argument or specific particle tracks through settings.xml. + +Running OpenMC with the argument "-tr", "-track", or "--track" will cause a track file to be created for every particle transported in the code. + +The settings.xml file can dictate that specific particle tracks are output. These particles are specified withen a ''track'' element. The ''track'' element should contain triplets of integers specifying the batch, generation, and particle numbers, respectively. For example, to output the tracks for particles 3 and 4 of batch 1 and generation 2 the settings.xml file should contain: + +.. code-block:: xml + + + 1 2 3 + 1 2 4 + + +After running OpenMC, the directory should contain a file of the form "track_(batch #)_(work #).binary" for each particle tracked. These binary track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track binary files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. diff --git a/src/global.F90 b/src/global.F90 index a45704f399..79e6ecf71a 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -284,12 +284,14 @@ module global integer :: trace_gen integer(8) :: trace_particle + ! Particle tracks + logical :: write_track = .false. + logical :: write_all_tracks = .false. + integer, allocatable :: track_identifiers(:,:) + ! Particle restart run logical :: particle_restart_run = .false. - ! Particle track output - logical :: write_track = .false. - ! ============================================================================ ! CMFD VARIABLES diff --git a/src/initialize.F90 b/src/initialize.F90 index ce5c457e80..7038544daf 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -378,7 +378,7 @@ contains ! Handle options that would be based to PETSC i = i + 1 case ('-tr', '-track', '--track') - write_track = .true. + write_all_tracks = .true. case default message = "Unknown command line option: " // argv(i) call fatal_error() diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 45abaf9a2e..4ce0512139 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -52,6 +52,7 @@ contains integer :: i ! loop index integer :: n integer :: coeffs_reqd + integer :: n_tracks logical :: file_exists character(MAX_FILE_LEN) :: env_variable character(MAX_WORD_LEN) :: type @@ -383,6 +384,22 @@ contains trace_particle = trace_(3) end if + ! Particle tracks + if (associated(track_)) then + n_tracks = size(track_) + ! Make sure that there are three values per particle + if (mod(n_tracks, 3) /= 0) then + message = "Number of integers specified in 'track' is not divisible & + &by 3. Please provide 3 integers per particle to be tracked." + call fatal_error() + end if + n_tracks = n_tracks/3 + allocate(track_identifiers(3,n_tracks)) + do i=1, n_tracks + track_identifiers(1:3,i) = track_(3*(i-1)+1 : 3*(i-1)+4) + end do + end if + ! Shannon Entropy mesh if (size(entropy_) > 0) then ! Check to make sure enough values were supplied diff --git a/src/output.F90 b/src/output.F90 index 504630d799..c10891f265 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -1994,7 +1994,8 @@ contains character(MAX_FILE_LEN) :: filename filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_work)) // '.binary' + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.binary' open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & ACCESS='stream') diff --git a/src/source.F90 b/src/source.F90 index 9ffea50089..02e1c3b997 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -152,6 +152,7 @@ contains subroutine get_source_particle(index_source) integer(8), intent(in) :: index_source + integer :: i ! iterator index integer(8) :: particle_seed ! unique index for particle type(Bank), pointer :: src => null() @@ -175,6 +176,21 @@ contains if (current_batch == trace_batch .and. current_gen == trace_gen .and. & p % id == trace_particle) trace = .true. + ! Set particle track. + write_track = .false. + if (write_all_tracks) then + write_track = .true. + else if (allocated(track_identifiers)) then + do i=1, size(track_identifiers(1,:)) + if (current_batch == track_identifiers(1,i) .and. & + ¤t_gen == track_identifiers(2,i) .and. & + &p % id == track_identifiers(3,i)) then + write_track = .true. + exit + end if + end do + end if + end subroutine get_source_particle !=============================================================================== diff --git a/src/templates/settings_t.xml b/src/templates/settings_t.xml index 3afa61745e..3903a35143 100644 --- a/src/templates/settings_t.xml +++ b/src/templates/settings_t.xml @@ -59,6 +59,7 @@ + From e9a5d18f0e419d96f2d90533390e7829a60ef441 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 17 Jun 2013 15:23:09 -0400 Subject: [PATCH 07/84] Fixed a typo. --- docs/source/usersguide/processing.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 94b8a71be0..8d6fea0374 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -345,7 +345,8 @@ Getting Data into MATLAB ------------------------ There is currently no front-end utility to dump tally data to MATLAB files, but -the process is straightforward. First extract the data using a custom Python script with statepoint.py, put the data into appropriately-shaped numpy arrays, +the process is straightforward. First extract the data using a custom Python +script with statepoint.py, put the data into appropriately-shaped numpy arrays, and then use the `Scipy MATLAB IO routines `_ to save to a MAT file. Note that the data contained in the output from @@ -372,7 +373,7 @@ The settings.xml file can dictate that specific particle tracks are output. The 1 2 4 -After running OpenMC, the directory should contain a file of the form "track_(batch #)_(work #).binary" for each particle tracked. These binary track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track binary files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. +After running OpenMC, the directory should contain a file of the form "track_(batch #)_(generation #)_(particle #).binary" for each particle tracked. These binary track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track binary files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. From a164dfa69fb88f0a2875beeaee6ff60f1ad94012 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 18 Jun 2013 10:46:06 -0400 Subject: [PATCH 08/84] Small fixes Changed the '-tr' commandline option to '-k'. Removed redundant 'write_particle_track' call. Moved particle_track subroutines to their own module. Layed (commented out) groundwork for particle_track to use output_interface. --- docs/source/usersguide/processing.rst | 2 +- src/DEPENDENCIES | 5 +++ src/OBJECTS | 1 + src/initialize.F90 | 3 +- src/output.F90 | 42 +------------------- src/particle_track.F90 | 56 +++++++++++++++++++++++++++ src/physics.F90 | 12 ++---- 7 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 src/particle_track.F90 diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 8d6fea0374..f51f87685f 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -362,7 +362,7 @@ Particle Track Visualization OpenMC can dump particle tracks—the position of particles as they are transported through the geometry. There are two ways to make OpenMC output tracks: all particle tracks through a commandline argument or specific particle tracks through settings.xml. -Running OpenMC with the argument "-tr", "-track", or "--track" will cause a track file to be created for every particle transported in the code. +Running OpenMC with the argument "-k", "-track", or "--track" will cause a track file to be created for every particle transported in the code. The settings.xml file can dictate that specific particle tracks are output. These particles are specified withen a ''track'' element. The ''track'' element should contain triplets of integers specifying the batch, generation, and particle numbers, respectively. For example, to output the tracks for particles 3 and 4 of batch 1 and generation 2 the settings.xml file should contain: diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index e8cc541f4f..48ef958f49 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -272,6 +272,10 @@ output_interface.o: tally_header.o particle_header.o: constants.o +particle_track.o: constants.o +particle_track.o: global.o +particle_track.o: string.o + particle_restart.o: bank_header.o particle_restart.o: constants.o particle_restart.o: geometry_header.o @@ -302,6 +306,7 @@ physics.o: material_header.o physics.o: mesh.o physics.o: output.o physics.o: particle_header.o +physics.o: particle_track.o physics.o: particle_restart_write.o physics.o: random_lcg.o physics.o: search.o diff --git a/src/OBJECTS b/src/OBJECTS index b079906751..6ba5516d5f 100644 --- a/src/OBJECTS +++ b/src/OBJECTS @@ -43,6 +43,7 @@ mpiio_interface.o \ output.o \ output_interface.o \ particle_header.o \ +particle_track.o \ particle_restart.o \ particle_restart_write.o \ physics.o \ diff --git a/src/initialize.F90 b/src/initialize.F90 index 7038544daf..3d54173bea 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -377,8 +377,9 @@ contains case ('-eps_tol', '-ksp_gmres_restart') ! Handle options that would be based to PETSC i = i + 1 - case ('-tr', '-track', '--track') + case ('-k', '-track', '--track') write_all_tracks = .true. + i = i + 1 case default message = "Unknown command line option: " // argv(i) call fatal_error() diff --git a/src/output.F90 b/src/output.F90 index c10891f265..77b323ce6e 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -170,6 +170,7 @@ contains write(OUTPUT_UNIT,*) ' -r, --restart Restart a previous run from a state point' write(OUTPUT_UNIT,*) ' or a particle restart file' write(OUTPUT_UNIT,*) ' -t, --tallies Write tally results from state point' + write(OUTPUT_UNIT,*) ' -k, --tracks Write tracks for all particles' write(OUTPUT_UNIT,*) ' -v, --version Show version information' write(OUTPUT_UNIT,*) ' -?, --help Show this message' end if @@ -1982,45 +1983,4 @@ contains end function get_label -!=============================================================================== -! INITIALIZE_PARTICLE_TRACK opens a particle track output file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. It -! should also probably write a header that identifies the file as a particle -! track and maybe adds particle identifying information (batch #, etc.). -!=============================================================================== - - subroutine initialize_particle_track() - character(MAX_FILE_LEN) :: filename - - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & - // '.binary' - open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & - ACCESS='stream') - - end subroutine initialize_particle_track - -!=============================================================================== -! WRITE_PARTICLE_TRACK outputs particle position to a binary file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. Perhaps -! it should also be made somehow more general so that it can output -! information other than just particle position. -!=============================================================================== - - subroutine write_particle_track() - write(UNIT_TRACK) p % coord0 % xyz - end subroutine write_particle_track - -!=============================================================================== -! FINALIZE_PARTICLE_TRACK closes the particle track file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. -!=============================================================================== - - subroutine finalize_particle_track() - close(UNIT=UNIT_TRACK) - end subroutine finalize_particle_track - end module output diff --git a/src/particle_track.F90 b/src/particle_track.F90 new file mode 100644 index 0000000000..c4c218520e --- /dev/null +++ b/src/particle_track.F90 @@ -0,0 +1,56 @@ +module particle_track + + use constants + use global + !use output_interface only: file_create, file_close, write_double1Darray + use string, only: to_str + + implicit none + +contains + +!=============================================================================== +! INITIALIZE_PARTICLE_TRACK opens a particle track output file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. It +! should also probably write a header that identifies the file as a particle +! track and maybe adds particle identifying information (batch #, etc.). +!=============================================================================== + + subroutine initialize_particle_track() + character(MAX_FILE_LEN) :: filename + + filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.binary' + open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & + ACCESS='stream') + !file_create(filename, 'serial') + + end subroutine initialize_particle_track + +!=============================================================================== +! WRITE_PARTICLE_TRACK outputs particle position to a binary file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. Perhaps +! it should also be made somehow more general so that it can output +! information other than just particle position. +!=============================================================================== + + subroutine write_particle_track() + write(UNIT_TRACK) p % coord0 % xyz + !write_double1Darray(p % coord0 % xyz, 'coordinates', 3) + end subroutine write_particle_track + +!=============================================================================== +! FINALIZE_PARTICLE_TRACK closes the particle track file. +! +! TODO: This subroutine needs to be modified to work with HDF5 files. +!=============================================================================== + + subroutine finalize_particle_track() + close(UNIT=UNIT_TRACK) + !file_close('serial') + end subroutine finalize_particle_track + +end module particle_track diff --git a/src/physics.F90 b/src/physics.F90 index 8b67642003..35ecb3aa65 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -14,10 +14,11 @@ module physics use interpolation, only: interpolate_tab1 use material_header, only: Material use mesh, only: get_mesh_indices - use output, only: write_message, initialize_particle_track, & + use output, only: write_message + use particle_header, only: LocalCoord + use particle_track, only: initialize_particle_track, & write_particle_track, & finalize_particle_track - use particle_header, only: LocalCoord use particle_restart_write, only: write_particle_restart use random_lcg, only: prn use search, only: binary_search @@ -114,12 +115,6 @@ contains coord => coord % next end do - ! Write particle track - - if (write_track) then - call write_particle_track() - end if - ! Score track-length tallies if (active_tracklength_tallies % size() > 0) & call score_tracklength_tally(distance) @@ -196,6 +191,7 @@ contains ! Finish particle track. if (write_track) then + call write_particle_track() call finalize_particle_track() endif From 65bdb0b06cf6c396f6a3daf9fef9e27b1197ca4d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 18 Jun 2013 19:50:50 -0400 Subject: [PATCH 09/84] Update settings.xml RELAX NG schema for . --- src/templates/settings.rnc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/templates/settings.rnc b/src/templates/settings.rnc index 47dd690bcc..f5f723b8d8 100644 --- a/src/templates/settings.rnc +++ b/src/templates/settings.rnc @@ -99,6 +99,8 @@ element settings { element trace { list { xsd:positiveInteger+ } }? & + element track { list { xsd:positiveInteger+ } }? & + element verbosity { xsd:positiveInteger }? & element uniform_fs{ From 51a1bc028b88d1b0886e1a53173789f0453ccac3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 18 Jun 2013 19:52:55 -0400 Subject: [PATCH 10/84] Update man page with -k, --track. --- man/man1/openmc.1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index eb8981f38f..d07653d2a9 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -24,6 +24,9 @@ Run in plotting mode Restart a previous run from a state point or a particle restart file named \fIbinaryFile\fP. .TP +.B "\-k\fR, \fP\-\-track" +Write tracks for all particles. +.TP .B "\-v\fR, \fP\-\-version" Show version information. .TP From 3a0fdefacb19c27cb41aa1526e3d0231cb2892a4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 18 Jun 2013 19:56:32 -0400 Subject: [PATCH 11/84] Minor fixes. --- src/output.F90 | 2 +- src/source.F90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 77b323ce6e..76c48ec219 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -170,7 +170,7 @@ contains write(OUTPUT_UNIT,*) ' -r, --restart Restart a previous run from a state point' write(OUTPUT_UNIT,*) ' or a particle restart file' write(OUTPUT_UNIT,*) ' -t, --tallies Write tally results from state point' - write(OUTPUT_UNIT,*) ' -k, --tracks Write tracks for all particles' + write(OUTPUT_UNIT,*) ' -k, --track Write tracks for all particles' write(OUTPUT_UNIT,*) ' -v, --version Show version information' write(OUTPUT_UNIT,*) ' -?, --help Show this message' end if diff --git a/src/source.F90 b/src/source.F90 index 02e1c3b997..3e7443703b 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -187,7 +187,7 @@ contains &p % id == track_identifiers(3,i)) then write_track = .true. exit - end if + end if end do end if From 58738986c71ab25a5ae46cfcbddc83c21ec719c4 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 21 Jun 2013 09:34:19 -0400 Subject: [PATCH 12/84] Changed commandline flag from -k to -t --- docs/source/usersguide/processing.rst | 2 +- man/man1/openmc.1 | 2 +- src/initialize.F90 | 2 +- src/output.F90 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index f51f87685f..bd0d19a1a4 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -362,7 +362,7 @@ Particle Track Visualization OpenMC can dump particle tracks—the position of particles as they are transported through the geometry. There are two ways to make OpenMC output tracks: all particle tracks through a commandline argument or specific particle tracks through settings.xml. -Running OpenMC with the argument "-k", "-track", or "--track" will cause a track file to be created for every particle transported in the code. +Running OpenMC with the argument "-t", "-track", or "--track" will cause a track file to be created for every particle transported in the code. The settings.xml file can dictate that specific particle tracks are output. These particles are specified withen a ''track'' element. The ''track'' element should contain triplets of integers specifying the batch, generation, and particle numbers, respectively. For example, to output the tracks for particles 3 and 4 of batch 1 and generation 2 the settings.xml file should contain: diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index 326860e5e4..56ae873a48 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -24,7 +24,7 @@ Run in plotting mode Restart a previous run from a state point or a particle restart file named \fIbinaryFile\fP. .TP -.B "\-k\fR, \fP\-\-track" +.B "\-t\fR, \fP\-\-track" Write tracks for all particles. .TP .B "\-v\fR, \fP\-\-version" diff --git a/src/initialize.F90 b/src/initialize.F90 index ad9c0ada23..cecb38719e 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -369,7 +369,7 @@ contains case ('-eps_tol', '-ksp_gmres_restart') ! Handle options that would be based to PETSC i = i + 1 - case ('-k', '-track', '--track') + case ('-t', '-track', '--track') write_all_tracks = .true. i = i + 1 case default diff --git a/src/output.F90 b/src/output.F90 index 57ab50c7f7..9afc676841 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -170,7 +170,7 @@ contains write(OUTPUT_UNIT,*) ' -p, --plot Run in plotting mode' write(OUTPUT_UNIT,*) ' -r, --restart Restart a previous run from a state point' write(OUTPUT_UNIT,*) ' or a particle restart file' - write(OUTPUT_UNIT,*) ' -k, --track Write tracks for all particles' + write(OUTPUT_UNIT,*) ' -t, --track Write tracks for all particles' write(OUTPUT_UNIT,*) ' -v, --version Show version information' write(OUTPUT_UNIT,*) ' -?, --help Show this message' end if From 73826aa2fd0c6ffef194387e8fa335aba7c8f9f6 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 24 Jun 2013 14:58:22 -0400 Subject: [PATCH 13/84] Deallocated track_identifiers array --- src/global.F90 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/global.F90 b/src/global.F90 index 79e6ecf71a..b2b445bdbd 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -448,6 +448,9 @@ contains call active_tracklength_tallies % clear() call active_current_tallies % clear() call active_tallies % clear() + + ! Deallocate track_identifiers + if (allocated(track_identifiers)) deallocate(track_identifiers) ! Deallocate dictionaries call cell_dict % clear() From e0936553cb6c9e73130e332c20f1b6cd0ecd62c4 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 6 Jul 2013 20:34:09 -0400 Subject: [PATCH 14/84] Added groundwork for HDF5 support Currently there is an indexing error with the HDF write statement. --- src/Makefile | 4 +- src/particle_track.F90 | 96 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/Makefile b/src/Makefile index 05cb7df6e8..c4a9c3dbef 100644 --- a/src/Makefile +++ b/src/Makefile @@ -21,7 +21,7 @@ DEBUG = no PROFILE = no OPTIMIZE = no MPI = no -HDF5 = no +HDF5 = yes PETSC = no #=============================================================================== @@ -29,7 +29,7 @@ PETSC = no #=============================================================================== MPI_DIR = /opt/mpich/3.0.4-$(COMPILER) -HDF5_DIR = /opt/hdf5/1.8.11-$(COMPILER) +HDF5_DIR = /usr/local/hdf5 PHDF5_DIR = /opt/phdf5/1.8.11-$(COMPILER) PETSC_DIR = /opt/petsc/3.3-p6-$(COMPILER) diff --git a/src/particle_track.F90 b/src/particle_track.F90 index c4c218520e..bc11497fbd 100644 --- a/src/particle_track.F90 +++ b/src/particle_track.F90 @@ -5,8 +5,22 @@ module particle_track !use output_interface only: file_create, file_close, write_double1Darray use string, only: to_str +#ifdef HDF5 + use hdf5 +#endif + implicit none +#ifdef HDF5 + integer, private :: hdf5_err ! HDF5 error code + integer(HID_T), private :: track_dset ! dataset handle + integer(HID_T), private :: track_dspace ! master dataspace handle + integer(HID_T), private :: track_selspace ! selected dataspace handle + integer(HID_T), private :: track_fh ! HDF5 file handle + integer(HID_T), private :: cparms ! chunk parameters + integer(HSIZE_T), private :: n_tracks ! total number of tracks +#endif + contains !=============================================================================== @@ -19,13 +33,35 @@ contains subroutine initialize_particle_track() character(MAX_FILE_LEN) :: filename + ! Replace the following with '#ifdef HDF5'. +#if 0 + integer(HSIZE_T) :: dims(2), max_dims(2), i + integer(HID_T) :: cparms + integer(HID_T) :: dapl +#endif filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & - // '.binary' - open(UNIT=UNIT_TRACK, FILE=filename, STATUS='replace', & - ACCESS='stream') - !file_create(filename, 'serial') + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) + + ! Replace the following with '#ifdef HDF5'. +#if 0 + filename = filename // '.h5' + call h5fcreate_f(filename, H5F_ACC_TRUNC_F, track_fh, hdf5_err) + dims = (/3, 1/) + i = 3 + max_dims = (/i, H5S_UNLIMITED_F/) + call h5screate_simple_f(2, dims, track_dspace, hdf5_err, max_dims) + call h5pcreate_f(H5P_DATASET_CREATE_F, cparms, hdf5_err) + call h5pset_chunk_f(cparms, 2, dims, hdf5_err) + call h5pset_fill_value_f(cparms, H5T_NATIVE_DOUBLE, 0, hdf5_err) + call h5dcreate_f(track_fh, 'coordinates', H5T_NATIVE_DOUBLE, track_dspace, & + track_dset, hdf5_err, cparms) + n_tracks = 1 +#else + filename = filename // '.binary' + open(UNIT=UNIT_TRACK, FILE=filename, ACTION="write", & + STATUS='replace', ACCESS='stream') +#endif end subroutine initialize_particle_track @@ -38,8 +74,40 @@ contains !=============================================================================== subroutine write_particle_track() + ! Replace the following with '#ifdef HDF5'. +#if 0 + integer(HSIZE_T) :: dims(2), max_dims(2), offset(2), i + integer(HSIZE_T), parameter :: cnt(2) = (/3, 1/), write_dims(2) = (/3, 1/) + real(8) :: coords(3, 1) + integer(HSIZE_T) :: test + + i = 3 + dims = (/i, n_tracks/) + call h5dget_space_f(track_dset, track_dspace, hdf5_err) + call h5dset_extent_f(track_dset, dims, hdf5_err) + call h5dget_space_f(track_dset, track_dspace, hdf5_err) + i = 1 + offset = (/i, n_tracks/) + call h5sselect_hyperslab_f(track_dspace, H5S_SELECT_SET_F, offset, cnt, & + hdf5_err) + !call h5sget_select_npoints_f(track_dspace, test, hdf5_err) + print *, test + coords(:, 1) = p % coord0 % xyz + print *, coords + if (n_tracks<20) then + call h5dwrite_f(track_dset, H5T_NATIVE_DOUBLE, coords, & + write_dims, hdf5_err, file_space_id=track_dspace) + end if + !call h5sselect_hyperslab_f(track_dspace, H5S_SELECT_SET_F, offset, dims, & + ! hdf5_err) + !i = 3 + n_tracks = n_tracks + 1 + !dims = (/i, n_tracks/) + !call h5sset_extent_simple_f(track_dspace, 2, dims, max_dims, hdf5_err) + !call h5dset_extent_f(track_dset, dims, hdf5_err) +#else write(UNIT_TRACK) p % coord0 % xyz - !write_double1Darray(p % coord0 % xyz, 'coordinates', 3) +#endif end subroutine write_particle_track !=============================================================================== @@ -49,8 +117,22 @@ contains !=============================================================================== subroutine finalize_particle_track() + ! Replace the following with '#ifdef HDF5'. +#if 0 + integer(HSIZE_T) :: dims(2), max_dims(2), i + + call h5sget_simple_extent_dims_f(track_dspace, dims, max_dims, hdf5_err) + !i = 3 + !dims = (/i, dims(2)-1/) + !call h5dextend_f(track_dset, dims, hdf5_err) + + call h5dclose_f(track_dset, hdf5_err) + call h5sclose_f(track_dspace, hdf5_err) + call h5pclose_f(cparms, hdf5_err) + call h5fclose_f(track_fh, hdf5_err) +#else close(UNIT=UNIT_TRACK) - !file_close('serial') +#endif end subroutine finalize_particle_track end module particle_track From 52233d0173494368ebe87210e748cf5403a382c3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 8 Jul 2013 15:10:50 -0400 Subject: [PATCH 15/84] Implimented HDF5 support. --- docs/source/usersguide/processing.rst | 2 +- src/DEPENDENCIES | 1 + src/Makefile | 4 +- src/particle_track.F90 | 136 +++++++++++++++----------- src/utils/track.py | 43 +++++--- 5 files changed, 112 insertions(+), 74 deletions(-) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index bd0d19a1a4..c12041214a 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -373,7 +373,7 @@ The settings.xml file can dictate that specific particle tracks are output. The 1 2 4 -After running OpenMC, the directory should contain a file of the form "track_(batch #)_(generation #)_(particle #).binary" for each particle tracked. These binary track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track binary files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. +After running OpenMC, the directory should contain a file of the form "track_(batch #)_(generation #)_(particle #).(binary or h5)" for each particle tracked. These track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index ea81f9ce9f..064acd707c 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -275,6 +275,7 @@ particle_header.o: constants.o particle_track.o: constants.o particle_track.o: global.o +particle_track.o: output_interface.o particle_track.o: string.o particle_restart.o: bank_header.o diff --git a/src/Makefile b/src/Makefile index c4a9c3dbef..05cb7df6e8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -21,7 +21,7 @@ DEBUG = no PROFILE = no OPTIMIZE = no MPI = no -HDF5 = yes +HDF5 = no PETSC = no #=============================================================================== @@ -29,7 +29,7 @@ PETSC = no #=============================================================================== MPI_DIR = /opt/mpich/3.0.4-$(COMPILER) -HDF5_DIR = /usr/local/hdf5 +HDF5_DIR = /opt/hdf5/1.8.11-$(COMPILER) PHDF5_DIR = /opt/phdf5/1.8.11-$(COMPILER) PETSC_DIR = /opt/petsc/3.3-p6-$(COMPILER) diff --git a/src/particle_track.F90 b/src/particle_track.F90 index bc11497fbd..852a07e100 100644 --- a/src/particle_track.F90 +++ b/src/particle_track.F90 @@ -1,110 +1,136 @@ +!=============================================================================== +! PARTICLE_TRACK handles output of particle tracks to disk. +! +! TODO: This module writes binary output files via a stream access i.e. it +! writes the particle's location to disk everytime write_particle_track is +! called. But HDF5 does not allow for a simple implimentation of stream access. +! An attempt was made to write HDF5 files in a stream fashion using an +! extendable dataset, but it always failed at the write call because of an out- +! of-bounds error. The extendable dataset code is commented out by enclosing it +! within "#if 0"/"#endif" blocks, and has been replaced by code that writes +! particle coordinates to an ever-expanding array which is written to disk in +! its entirety when finalize_particle_track is called. +! +! In the interest of consistency, maybe this module should be changed so that it +! uses the extendable HDF5 dataset, or it writes binary output all in one call +! using the output_interface (like the current HDF5 implimentation). +!=============================================================================== + module particle_track use constants use global - !use output_interface only: file_create, file_close, write_double1Darray - use string, only: to_str + use string, only: to_str -#ifdef HDF5 +#if 0 use hdf5 #endif +#ifdef HDF5 + use output_interface, only: file_create, file_close, write_data +#endif implicit none +#if 0 + integer, private :: hdf5_err ! HDF error code + integer(HID_T), private :: track_dset ! dataset handle + integer(HID_T), private :: track_dspace ! dataspace handle + integer(HID_T), private :: track_fh ! HDF file handle + integer(HID_T), private :: cparms ! chunk parameters + integer(HSIZE_T), private :: n_tracks ! total number of tracks +#endif #ifdef HDF5 - integer, private :: hdf5_err ! HDF5 error code - integer(HID_T), private :: track_dset ! dataset handle - integer(HID_T), private :: track_dspace ! master dataspace handle - integer(HID_T), private :: track_selspace ! selected dataspace handle - integer(HID_T), private :: track_fh ! HDF5 file handle - integer(HID_T), private :: cparms ! chunk parameters - integer(HSIZE_T), private :: n_tracks ! total number of tracks + character(MAX_FILE_LEN), private :: fname ! file name + integer, private :: n_tracks ! total number of tracks + real(8), private, allocatable :: coords(:,:) ! track coordinates #endif contains !=============================================================================== ! INITIALIZE_PARTICLE_TRACK opens a particle track output file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. It -! should also probably write a header that identifies the file as a particle -! track and maybe adds particle identifying information (batch #, etc.). !=============================================================================== subroutine initialize_particle_track() character(MAX_FILE_LEN) :: filename - ! Replace the following with '#ifdef HDF5'. #if 0 - integer(HSIZE_T) :: dims(2), max_dims(2), i + integer(HSIZE_T) :: dims(2), max_dims(2), i, j(1) integer(HID_T) :: cparms - integer(HID_T) :: dapl -#endif - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) - - ! Replace the following with '#ifdef HDF5'. -#if 0 - filename = filename // '.h5' + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.h5' + n_tracks = 0 + ! Create file. call h5fcreate_f(filename, H5F_ACC_TRUNC_F, track_fh, hdf5_err) + ! Create a dataspace with an unlimited max dimension. dims = (/3, 1/) i = 3 max_dims = (/i, H5S_UNLIMITED_F/) call h5screate_simple_f(2, dims, track_dspace, hdf5_err, max_dims) + ! Set dataspace chunking. call h5pcreate_f(H5P_DATASET_CREATE_F, cparms, hdf5_err) call h5pset_chunk_f(cparms, 2, dims, hdf5_err) + ! Set fill value. call h5pset_fill_value_f(cparms, H5T_NATIVE_DOUBLE, 0, hdf5_err) + ! Create dataset. call h5dcreate_f(track_fh, 'coordinates', H5T_NATIVE_DOUBLE, track_dspace, & track_dset, hdf5_err, cparms) - n_tracks = 1 +#endif +#ifdef HDF5 + filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.h5' + n_tracks = 0 + fname = filename + allocate(coords(3,1)) #else - filename = filename // '.binary' + filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.binary' open(UNIT=UNIT_TRACK, FILE=filename, ACTION="write", & STATUS='replace', ACCESS='stream') #endif - end subroutine initialize_particle_track !=============================================================================== ! WRITE_PARTICLE_TRACK outputs particle position to a binary file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. Perhaps -! it should also be made somehow more general so that it can output -! information other than just particle position. !=============================================================================== subroutine write_particle_track() - ! Replace the following with '#ifdef HDF5'. #if 0 integer(HSIZE_T) :: dims(2), max_dims(2), offset(2), i integer(HSIZE_T), parameter :: cnt(2) = (/3, 1/), write_dims(2) = (/3, 1/) - real(8) :: coords(3, 1) - integer(HSIZE_T) :: test + real(8) :: coords(3) + ! There is another set of track coordinates. Incriment the counter. + n_tracks = n_tracks + 1 + ! Make the dataset the right size to fit the track coordinates. i = 3 dims = (/i, n_tracks/) - call h5dget_space_f(track_dset, track_dspace, hdf5_err) call h5dset_extent_f(track_dset, dims, hdf5_err) + ! Get the dataspace with the updated dimensions. call h5dget_space_f(track_dset, track_dspace, hdf5_err) + ! Select the hyperslab where the latest coordinates will be written. i = 1 offset = (/i, n_tracks/) + if (n_tracks < 20) then call h5sselect_hyperslab_f(track_dspace, H5S_SELECT_SET_F, offset, cnt, & hdf5_err) - !call h5sget_select_npoints_f(track_dspace, test, hdf5_err) - print *, test - coords(:, 1) = p % coord0 % xyz - print *, coords - if (n_tracks<20) then + endif + ! Write the coordinates to the dataset. + coords = p % coord0 % xyz call h5dwrite_f(track_dset, H5T_NATIVE_DOUBLE, coords, & write_dims, hdf5_err, file_space_id=track_dspace) - end if - !call h5sselect_hyperslab_f(track_dspace, H5S_SELECT_SET_F, offset, dims, & - ! hdf5_err) - !i = 3 +#endif +#ifdef HDF5 + real(8) :: old_coords(3, n_tracks) + + old_coords = coords n_tracks = n_tracks + 1 - !dims = (/i, n_tracks/) - !call h5sset_extent_simple_f(track_dspace, 2, dims, max_dims, hdf5_err) - !call h5dset_extent_f(track_dset, dims, hdf5_err) + deallocate(coords) + allocate(coords(3, n_tracks)) + coords(:, 1:n_tracks-1) = old_coords + coords(:, n_tracks) = p % coord0 % xyz #else write(UNIT_TRACK) p % coord0 % xyz #endif @@ -112,24 +138,20 @@ contains !=============================================================================== ! FINALIZE_PARTICLE_TRACK closes the particle track file. -! -! TODO: This subroutine needs to be modified to work with HDF5 files. !=============================================================================== subroutine finalize_particle_track() - ! Replace the following with '#ifdef HDF5'. #if 0 - integer(HSIZE_T) :: dims(2), max_dims(2), i - - call h5sget_simple_extent_dims_f(track_dspace, dims, max_dims, hdf5_err) - !i = 3 - !dims = (/i, dims(2)-1/) - !call h5dextend_f(track_dset, dims, hdf5_err) - call h5dclose_f(track_dset, hdf5_err) call h5sclose_f(track_dspace, hdf5_err) call h5pclose_f(cparms, hdf5_err) call h5fclose_f(track_fh, hdf5_err) +#endif +#ifdef HDF5 + call file_create(fname, 'serial') + call write_data(coords, 'coordinates', length=(/3, n_tracks/)) + call file_close('serial') + deallocate(coords) #else close(UNIT=UNIT_TRACK) #endif diff --git a/src/utils/track.py b/src/utils/track.py index 1907d1ec0a..f0b1f23e5a 100755 --- a/src/utils/track.py +++ b/src/utils/track.py @@ -5,7 +5,7 @@ Usage information can be obtained by running 'track.py --help': usage: track.py [-h] [-o OUT] IN [IN ...] - Convert binary particle track file to a .pvtp file. + Convert particle track file to a .pvtp file. positional arguments: IN Input particle track data filename(s). @@ -26,9 +26,8 @@ import vtk def _parse_args(): # Create argument parser. parser = argparse.ArgumentParser( - description='Convert binary particle track file to a .pvtp file.') - parser.add_argument('input', metavar='IN', type=argparse.FileType('rb'), - nargs='+', + description='Convert particle track file to a .pvtp file.') + parser.add_argument('input', metavar='IN', type=str, nargs='+', help='Input particle track data filename(s).') parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, dest='out', @@ -39,8 +38,15 @@ def _parse_args(): def main(): - # Parse commandline argumetns. + # Parse commandline arguments. args = _parse_args() + + # Check input file extensions. + for fname in args.input: + if len(fname) > 3 and fname[-3:] == '.h5': continue + if len(fname) > 7 and fname[-7:] == '.binary': continue + raise ValueError("Input file names must either end with '.h5' or " + "'.binary'.") # Make sure that the output filename ends with '.pvtp'. if not args.out: @@ -48,19 +54,28 @@ def main(): elif os.path.splitext(args.out)[1] != '.pvtp': args.out = ''.join([args.out, '.pvtp']) + # Import HDF library if HDF files are present + for fname in args.input: + if fname[-3:] != '.h5': continue + import h5py + break + points = vtk.vtkPoints() cells = vtk.vtkCellArray() j = 0 k = 0 - for fin in args.input: - track = fin.read() - coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) - for i in range(len(track)/24)] - n_points = len(coords) - - for triplet in coords: - points.InsertNextPoint(triplet) - + for fname in args.input: + if len(fname) > 7 and fname[-7:] == '.binary': + track = open(fname, 'rb').read() + coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) + for i in range(len(track)/24)] + n_points = len(coords) + for triplet in coords: points.InsertNextPoint(triplet) + elif fname[-3:] == '.h5': + coords = h5py.File(fname).get('coordinates') + n_points = coords.shape[0] + for i in range(n_points): points.InsertNextPoint(coords[i,:]) + # Create VTK line and assign points to line. line = vtk.vtkPolyLine() line.GetPointIds().SetNumberOfIds(n_points) From 06c3bd3690140c4a7778f01df410c748e69f0619 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 9 Jul 2013 11:04:04 -0400 Subject: [PATCH 16/84] Switched to consistent use of output_interface --- src/particle_track.F90 | 159 ++++++++++++----------------------------- 1 file changed, 47 insertions(+), 112 deletions(-) diff --git a/src/particle_track.F90 b/src/particle_track.F90 index 852a07e100..c0d7894ce3 100644 --- a/src/particle_track.F90 +++ b/src/particle_track.F90 @@ -1,160 +1,95 @@ !=============================================================================== -! PARTICLE_TRACK handles output of particle tracks to disk. -! -! TODO: This module writes binary output files via a stream access i.e. it -! writes the particle's location to disk everytime write_particle_track is -! called. But HDF5 does not allow for a simple implimentation of stream access. -! An attempt was made to write HDF5 files in a stream fashion using an -! extendable dataset, but it always failed at the write call because of an out- -! of-bounds error. The extendable dataset code is commented out by enclosing it -! within "#if 0"/"#endif" blocks, and has been replaced by code that writes -! particle coordinates to an ever-expanding array which is written to disk in -! its entirety when finalize_particle_track is called. -! -! In the interest of consistency, maybe this module should be changed so that it -! uses the extendable HDF5 dataset, or it writes binary output all in one call -! using the output_interface (like the current HDF5 implimentation). +! PARTICLE_TRACK handles output of particle tracks (the paths taken by particles +! as they are transported through the geometry). !=============================================================================== module particle_track use constants use global - use string, only: to_str - -#if 0 - use hdf5 -#endif -#ifdef HDF5 use output_interface, only: file_create, file_close, write_data -#endif + use string, only: to_str implicit none -#if 0 - integer, private :: hdf5_err ! HDF error code - integer(HID_T), private :: track_dset ! dataset handle - integer(HID_T), private :: track_dspace ! dataspace handle - integer(HID_T), private :: track_fh ! HDF file handle - integer(HID_T), private :: cparms ! chunk parameters - integer(HSIZE_T), private :: n_tracks ! total number of tracks -#endif -#ifdef HDF5 - character(MAX_FILE_LEN), private :: fname ! file name integer, private :: n_tracks ! total number of tracks real(8), private, allocatable :: coords(:,:) ! track coordinates -#endif contains !=============================================================================== -! INITIALIZE_PARTICLE_TRACK opens a particle track output file. +! INITIALIZE_PARTICLE_TRACK !=============================================================================== subroutine initialize_particle_track() - character(MAX_FILE_LEN) :: filename -#if 0 - integer(HSIZE_T) :: dims(2), max_dims(2), i, j(1) - integer(HID_T) :: cparms - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & - // '.h5' n_tracks = 0 - ! Create file. - call h5fcreate_f(filename, H5F_ACC_TRUNC_F, track_fh, hdf5_err) - ! Create a dataspace with an unlimited max dimension. - dims = (/3, 1/) - i = 3 - max_dims = (/i, H5S_UNLIMITED_F/) - call h5screate_simple_f(2, dims, track_dspace, hdf5_err, max_dims) - ! Set dataspace chunking. - call h5pcreate_f(H5P_DATASET_CREATE_F, cparms, hdf5_err) - call h5pset_chunk_f(cparms, 2, dims, hdf5_err) - ! Set fill value. - call h5pset_fill_value_f(cparms, H5T_NATIVE_DOUBLE, 0, hdf5_err) - ! Create dataset. - call h5dcreate_f(track_fh, 'coordinates', H5T_NATIVE_DOUBLE, track_dspace, & - track_dset, hdf5_err, cparms) -#endif -#ifdef HDF5 - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & - // '.h5' - n_tracks = 0 - fname = filename - allocate(coords(3,1)) -#else - filename = trim(path_output) // 'track_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & - // '.binary' - open(UNIT=UNIT_TRACK, FILE=filename, ACTION="write", & - STATUS='replace', ACCESS='stream') -#endif + allocate(coords(1,1)) end subroutine initialize_particle_track !=============================================================================== -! WRITE_PARTICLE_TRACK outputs particle position to a binary file. +! WRITE_PARTICLE_TRACK copies particle position to an array. !=============================================================================== subroutine write_particle_track() -#if 0 - integer(HSIZE_T) :: dims(2), max_dims(2), offset(2), i - integer(HSIZE_T), parameter :: cnt(2) = (/3, 1/), write_dims(2) = (/3, 1/) - real(8) :: coords(3) - - ! There is another set of track coordinates. Incriment the counter. - n_tracks = n_tracks + 1 - ! Make the dataset the right size to fit the track coordinates. - i = 3 - dims = (/i, n_tracks/) - call h5dset_extent_f(track_dset, dims, hdf5_err) - ! Get the dataspace with the updated dimensions. - call h5dget_space_f(track_dset, track_dspace, hdf5_err) - ! Select the hyperslab where the latest coordinates will be written. - i = 1 - offset = (/i, n_tracks/) - if (n_tracks < 20) then - call h5sselect_hyperslab_f(track_dspace, H5S_SELECT_SET_F, offset, cnt, & - hdf5_err) - endif - ! Write the coordinates to the dataset. - coords = p % coord0 % xyz - call h5dwrite_f(track_dset, H5T_NATIVE_DOUBLE, coords, & - write_dims, hdf5_err, file_space_id=track_dspace) -#endif -#ifdef HDF5 real(8) :: old_coords(3, n_tracks) + ! Save the coordinates gathered in previous calls. old_coords = coords + + ! Add another column to coords. n_tracks = n_tracks + 1 deallocate(coords) allocate(coords(3, n_tracks)) + + ! Put the old coordinates back into the array. coords(:, 1:n_tracks-1) = old_coords + + ! Write current coordinates into the newest column. coords(:, n_tracks) = p % coord0 % xyz -#else - write(UNIT_TRACK) p % coord0 % xyz -#endif end subroutine write_particle_track !=============================================================================== -! FINALIZE_PARTICLE_TRACK closes the particle track file. +! FINALIZE_PARTICLE_TRACK writes the particle track array to disk. +! +! output_interface currently does not support writing 2D binary arrays so there +! are two different versions of this subroutine; an HDF version which simply +! writes the array and a binary version which flattens the array into a 1D shape +! before writing. !=============================================================================== - subroutine finalize_particle_track() -#if 0 - call h5dclose_f(track_dset, hdf5_err) - call h5sclose_f(track_dspace, hdf5_err) - call h5pclose_f(cparms, hdf5_err) - call h5fclose_f(track_fh, hdf5_err) -#endif #ifdef HDF5 + + subroutine finalize_particle_track() + character(MAX_FILE_LEN) :: fname + + fname = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.h5' call file_create(fname, 'serial') call write_data(coords, 'coordinates', length=(/3, n_tracks/)) call file_close('serial') deallocate(coords) -#else - close(UNIT=UNIT_TRACK) -#endif end subroutine finalize_particle_track +#else + + subroutine finalize_particle_track() + character(MAX_FILE_LEN) :: fname + integer :: i + real(8) :: flat_coords(3*n_tracks) + + fname = trim(path_output) // 'track_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & + // '.binary' + call file_create(fname, 'serial') + do i=1, n_tracks + flat_coords(3*i-2 : 3*i) = coords(:,i) + end do + call write_data(flat_coords, 'coordinates', length=3*n_tracks) + call file_close('serial') + deallocate(coords) + end subroutine finalize_particle_track + +#endif + end module particle_track From b03786d5363c6b8442b56e8d067d1f9440225bef Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Aug 2013 23:39:07 -0500 Subject: [PATCH 17/84] Simple implementation of random number streams. Setup separate streams for tracking and tallies. --- src/random_lcg.F90 | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/random_lcg.F90 b/src/random_lcg.F90 index 3130d42a53..12b85c2c7a 100644 --- a/src/random_lcg.F90 +++ b/src/random_lcg.F90 @@ -5,8 +5,13 @@ module random_lcg private save + ! Random number streams + integer, parameter :: N_STREAMS = 2 + integer, parameter :: STREAM_TRACKING = 1 + integer, parameter :: STREAM_TALLIES = 2 + integer(8) :: prn_seed0 ! original seed - integer(8) :: prn_seed ! current seed + integer(8) :: prn_seed(N_STREAMS) ! current seed integer(8) :: prn_mult ! multiplication factor, g integer(8) :: prn_add ! additive factor, c integer :: prn_bits ! number of bits, M @@ -14,11 +19,14 @@ module random_lcg integer(8) :: prn_mask ! 2^M - 1 integer(8) :: prn_stride ! stride between particles real(8) :: prn_norm ! 2^(-M) + integer :: stream ! current RNG stream public :: prn public :: initialize_prng public :: set_particle_seed public :: prn_skip + public :: prn_set_stream + public :: STREAM_TRACKING, STREAM_TALLIES contains @@ -33,12 +41,12 @@ contains ! This algorithm uses bit-masking to find the next integer(8) value to be ! used to calculate the random number - prn_seed = iand(prn_mult*prn_seed + prn_add, prn_mask) + prn_seed(stream) = iand(prn_mult*prn_seed(stream) + prn_add, prn_mask) ! Once the integer is calculated, we just need to divide by 2**m, ! represented here as multiplying by a pre-calculated factor - pseudo_rn = prn_seed * prn_norm + pseudo_rn = prn_seed(stream) * prn_norm end function prn @@ -51,8 +59,13 @@ contains use global, only: seed + integer :: i + + stream = STREAM_TRACKING prn_seed0 = seed - prn_seed = prn_seed + do i = 1, N_STREAMS + prn_seed(i) = prn_seed0 + i - 1 + end do prn_mult = 2806196910506780709_8 prn_add = 1_8 prn_bits = 63 @@ -72,7 +85,11 @@ contains integer(8), intent(in) :: id - prn_seed = prn_skip_ahead(id*prn_stride, prn_seed0) + integer :: i + + do i = 1, N_STREAMS + prn_seed(i) = prn_skip_ahead(id*prn_stride, prn_seed0 + i - 1) + end do end subroutine set_particle_seed @@ -84,7 +101,7 @@ contains integer(8), intent(in) :: n ! number of seeds to skip - prn_seed = prn_skip_ahead(n, prn_seed) + prn_seed(stream) = prn_skip_ahead(n, prn_seed(stream)) end subroutine prn_skip @@ -149,4 +166,18 @@ contains end function prn_skip_ahead +!=============================================================================== +! PRN_SET_STREAM changes the random number stream. If random numbers are needed +! in routines not used directly for tracking (e.g. physics), this allows the +! numbers to be generated without affecting reproducibility of the physics. +!=============================================================================== + + subroutine prn_set_stream(i) + + integer, intent(in) :: i + + stream = i + + end subroutine prn_set_stream + end module random_lcg From f945e20dcb12b4a89c1fd74a8f4cc50dea654894 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 21 Sep 2013 16:50:44 -0400 Subject: [PATCH 18/84] Changed 'write_track' from global var to attribute --- src/DEPENDENCIES | 2 +- src/constants.F90 | 1 - src/global.F90 | 1 - src/particle_header.F90 | 3 +++ src/particle_track.F90 | 1 - src/source.F90 | 6 +++--- src/tracking.F90 | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index 933dabb93d..0c23afa917 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -372,9 +372,9 @@ output.o: plot_header.o output.o: string.o output.o: tally_header.o -particle_track.o: constants.o particle_track.o: global.o particle_track.o: output_interface.o +particle_track.o: particle_header.o particle_track.o: string.o physics.o: ace_header.o diff --git a/src/constants.F90 b/src/constants.F90 index ef7625b3b6..46ae8e9c1a 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -365,7 +365,6 @@ module constants integer, parameter :: CMFD_BALANCE = 15 ! unit # for writing cmfd balance file integer, parameter :: UNIT_PARTICLE = 16 ! unit # for writing particle restart integer, parameter :: UNIT_OUTPUT = 17 ! unit # for writing output - integer, parameter :: UNIT_TRACK = 18 ! unit # for writing particle tracks !============================================================================= ! CMFD CONSTANTS diff --git a/src/global.F90 b/src/global.F90 index b15fabfb13..f803d4c2d3 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -277,7 +277,6 @@ module global integer(8) :: trace_particle ! Particle tracks - logical :: write_track = .false. logical :: write_all_tracks = .false. integer, allocatable :: track_identifiers(:,:) diff --git a/src/particle_header.F90 b/src/particle_header.F90 index 201de518eb..b9762960ed 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -76,6 +76,9 @@ module particle_header ! Statistical data integer :: n_collision ! # of collisions + ! Track output + logical :: write_track + contains procedure :: initialize => initialize_particle procedure :: clear => clear_particle diff --git a/src/particle_track.F90 b/src/particle_track.F90 index 5bc392b0f0..4053dd1d77 100644 --- a/src/particle_track.F90 +++ b/src/particle_track.F90 @@ -5,7 +5,6 @@ module particle_track - use constants use global use output_interface, only: BinaryOutput use particle_header, only: Particle diff --git a/src/source.F90 b/src/source.F90 index 25e1553a81..bbf86d4ada 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -178,15 +178,15 @@ contains p % id == trace_particle) trace = .true. ! Set particle track. - write_track = .false. + p % write_track = .false. if (write_all_tracks) then - write_track = .true. + p % write_track = .true. else if (allocated(track_identifiers)) then do i=1, size(track_identifiers(1,:)) if (current_batch == track_identifiers(1,i) .and. & ¤t_gen == track_identifiers(2,i) .and. & &p % id == track_identifiers(3,i)) then - write_track = .true. + p % write_track = .true. exit end if end do diff --git a/src/tracking.F90 b/src/tracking.F90 index 2c535e290f..4a22e8ccad 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -67,14 +67,14 @@ contains micro_xs % last_E = ZERO ! Prepare to write out particle track. - if (write_track) then + if (p % write_track) then call initialize_particle_track() endif do while (p % alive) ! Write particle track. - if (write_track) call write_particle_track(p) + if (p % write_track) call write_particle_track(p) if (check_overlaps) call check_cell_overlap(p) @@ -200,7 +200,7 @@ contains end do ! Finish particle track output. - if (write_track) then + if (p % write_track) then call write_particle_track(p) call finalize_particle_track(p) endif From ca0ace41cbc6b25aff6f1b05545d7853f4743067 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 21 Sep 2013 17:32:53 -0400 Subject: [PATCH 19/84] Moved 'particle_track' to 'track_output' --- src/DEPENDENCIES | 10 +++--- src/{particle_track.F90 => track_output.F90} | 37 ++++---------------- src/tracking.F90 | 4 +-- 3 files changed, 13 insertions(+), 38 deletions(-) rename src/{particle_track.F90 => track_output.F90} (72%) diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index 0c23afa917..2ece570ae6 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -310,7 +310,7 @@ tracking.o: geometry_header.o tracking.o: global.o tracking.o: output.o tracking.o: particle_header.o -tracking.o: particle_track.o +tracking.o: track_output.o tracking.o: physics.o tracking.o: random_lcg.o tracking.o: string.o @@ -372,10 +372,10 @@ output.o: plot_header.o output.o: string.o output.o: tally_header.o -particle_track.o: global.o -particle_track.o: output_interface.o -particle_track.o: particle_header.o -particle_track.o: string.o +track_output.o: global.o +track_output.o: output_interface.o +track_output.o: particle_header.o +track_output.o: string.o physics.o: ace_header.o physics.o: constants.o diff --git a/src/particle_track.F90 b/src/track_output.F90 similarity index 72% rename from src/particle_track.F90 rename to src/track_output.F90 index 4053dd1d77..5681a4de48 100644 --- a/src/particle_track.F90 +++ b/src/track_output.F90 @@ -1,9 +1,9 @@ !=============================================================================== -! PARTICLE_TRACK handles output of particle tracks (the paths taken by particles +! TRACK_OUTPUT handles output of particle tracks (the paths taken by particles ! as they are transported through the geometry). !=============================================================================== -module particle_track +module track_output use global use output_interface, only: BinaryOutput @@ -52,52 +52,27 @@ contains !=============================================================================== ! FINALIZE_PARTICLE_TRACK writes the particle track array to disk. -! -! output_interface currently does not support writing 2D binary arrays so there -! are two different versions of this subroutine; an HDF version which simply -! writes the array and a binary version which flattens the array into a 1D shape -! before writing. !=============================================================================== -#ifdef HDF5 - subroutine finalize_particle_track(p) type(Particle), intent(in) :: p character(MAX_FILE_LEN) :: fname type(BinaryOutput) :: binout +#ifdef HDF5 fname = trim(path_output) // 'track_' // trim(to_str(current_batch)) & // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & // '.h5' - call file_create(fname) - call write_data(coords, 'coordinates', length=(/3, n_tracks/)) - call file_close() - deallocate(coords) - end subroutine finalize_particle_track - #else - - subroutine finalize_particle_track(p) - type(Particle), intent(in) :: p - - character(MAX_FILE_LEN) :: fname - type(BinaryOutput) :: binout - integer :: i - real(8) :: flat_coords(3*n_tracks) - fname = trim(path_output) // 'track_' // trim(to_str(current_batch)) & // '_' // trim(to_str(current_gen)) // '_' // trim(to_str(p % id)) & // '.binary' +#endif call binout % file_create(fname) - do i=1, n_tracks - flat_coords(3*i-2 : 3*i) = coords(:,i) - end do - call binout % write_data(flat_coords, 'coordinates', length=3*n_tracks) + call binout % write_data(coords, 'coordinates', length=(/3, n_tracks/)) call binout % file_close() deallocate(coords) end subroutine finalize_particle_track -#endif - -end module particle_track +end module track_output diff --git a/src/tracking.F90 b/src/tracking.F90 index 4a22e8ccad..e8d3f7b1a4 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -8,13 +8,13 @@ module tracking use global use output, only: write_message use particle_header, only: LocalCoord, Particle - use particle_track, only: initialize_particle_track, write_particle_track, & - finalize_particle_track use physics, only: collision use random_lcg, only: prn use string, only: to_str use tally, only: score_analog_tally, score_tracklength_tally, & score_surface_current + use track_output, only: initialize_particle_track, write_particle_track, & + finalize_particle_track contains From 32c03c44ae666702f4252c82249771763f1d6551 Mon Sep 17 00:00:00 2001 From: Tuomas Viitanen Date: Wed, 25 Sep 2013 16:48:48 +0300 Subject: [PATCH 20/84] Added a check for the name of the ACE data to be read. --- src/ace.F90 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ace.F90 b/src/ace.F90 index c37a546c01..209f5daa33 100644 --- a/src/ace.F90 +++ b/src/ace.F90 @@ -243,6 +243,15 @@ contains ! Read first line of header read(UNIT=in, FMT='(A10,2G12.0,1X,A10)') name, awr, kT, date_ + + ! Check that correct xs was found + ! (if XS listing (cross_sections.xml) is broken, this may not be the case) + + if( trim(adjustl(name)) /= trim(adjustl(listing % name)) ) then + message = "XS listing entry " // trim(listing % name) // " did not & + &match ACE data, " // trim(name) // " found instead." + call fatal_error() + end if ! Read more header and NXS and JXS read(UNIT=in, FMT=100) comment, mat, & From be6a8d22ecb1c37151c86b380b84318848b7c15d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 2 Oct 2013 21:21:05 -0400 Subject: [PATCH 21/84] Improved track.py readability and PEP 8 compliance --- src/utils/track.py | 68 ++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/src/utils/track.py b/src/utils/track.py index f0b1f23e5a..5fe20ce537 100755 --- a/src/utils/track.py +++ b/src/utils/track.py @@ -8,12 +8,11 @@ Usage information can be obtained by running 'track.py --help': Convert particle track file to a .pvtp file. positional arguments: - IN Input particle track data filename(s). + IN Input particle track data filename(s). optional arguments: - -h, --help show this help message and exit - -o OUT, -out OUT, --out OUT - Output VTK poly data filename. + -h, --help show this help message and exit + -o OUT, --out OUT Output VTK poly data filename. """ @@ -29,8 +28,7 @@ def _parse_args(): description='Convert particle track file to a .pvtp file.') parser.add_argument('input', metavar='IN', type=str, nargs='+', help='Input particle track data filename(s).') - parser.add_argument('-o', '-out', '--out', metavar='OUT', type=str, - dest='out', + parser.add_argument('-o', '--out', metavar='OUT', type=str, dest='out', help='Output VTK poly data filename.') # Parse and return commandline arguments. @@ -43,10 +41,9 @@ def main(): # Check input file extensions. for fname in args.input: - if len(fname) > 3 and fname[-3:] == '.h5': continue - if len(fname) > 7 and fname[-7:] == '.binary': continue - raise ValueError("Input file names must either end with '.h5' or " - "'.binary'.") + if not (fname.endswith('.h5') or fname.endswith('.binary')): + raise ValueError("Input file names must either end iwth '.h5' or" + "'.binary'.") # Make sure that the output filename ends with '.pvtp'. if not args.out: @@ -56,36 +53,37 @@ def main(): # Import HDF library if HDF files are present for fname in args.input: - if fname[-3:] != '.h5': continue - import h5py - break + if fname.endswith('.h5'): + import h5py + break + # Initialize data arrays and offset. points = vtk.vtkPoints() cells = vtk.vtkCellArray() - j = 0 - k = 0 + point_offset = 0 for fname in args.input: - if len(fname) > 7 and fname[-7:] == '.binary': + # Write coordinate values to points array. + if fname.endswith('.binary'): track = open(fname, 'rb').read() coords = [struct.unpack("ddd", track[24*i : 24*(i+1)]) for i in range(len(track)/24)] n_points = len(coords) - for triplet in coords: points.InsertNextPoint(triplet) - elif fname[-3:] == '.h5': + for triplet in coords: + points.InsertNextPoint(triplet) + else: coords = h5py.File(fname).get('coordinates') n_points = coords.shape[0] - for i in range(n_points): points.InsertNextPoint(coords[i,:]) + for i in range(n_points): + points.InsertNextPoint(coords[i,:]) # Create VTK line and assign points to line. line = vtk.vtkPolyLine() line.GetPointIds().SetNumberOfIds(n_points) for i in range(n_points): - line.GetPointIds().SetId(i, j+i) + line.GetPointIds().SetId(i, point_offset+i) cells.InsertNextCell(line) - j += n_points - k += 1 - global data + point_offset += n_points data = vtk.vtkPolyData() data.SetPoints(points) data.SetLines(cells) @@ -99,27 +97,3 @@ def main(): if __name__ == '__main__': main() - - -#### The following code is retained for debugging purposes: - -##poly_mapper = vtk.vtkPolyDataMapper() -##poly_mapper.SetInputConnection(data.GetProducerPort()) -## -##poly_actor = vtk.vtkActor() -##poly_actor.SetMapper(poly_mapper) -## -##ren1 = vtk.vtkRenderer() -##ren1.AddActor(poly_actor) -##ren1.SetBackground(0.0, 0.0, 0.0) -## -##ren1.ResetCamera() -## -##ren_win = vtk.vtkRenderWindow() -##ren_win.AddRenderer(ren1) -##ren_win.SetSize(400, 400) -## -##iren = vtk.vtkRenderWindowInteractor() -##iren.SetRenderWindow(ren_win) -##iren.Initialize() -##iren.Start() From beb39a44bbaf9a21f6c9534fc86378a1de1057b5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 4 Oct 2013 10:17:30 -0400 Subject: [PATCH 22/84] Use move_alloc to expand array --- src/track_output.F90 | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/track_output.F90 b/src/track_output.F90 index 5681a4de48..4b02dfe9a2 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -33,18 +33,13 @@ contains subroutine write_particle_track(p) type(Particle), intent(in) :: p - real(8) :: old_coords(3, n_tracks) - - ! Save the coordinates gathered in previous calls. - old_coords = coords + real(8), allocatable :: new_coords(:, :) ! Add another column to coords. n_tracks = n_tracks + 1 - deallocate(coords) - allocate(coords(3, n_tracks)) - - ! Put the old coordinates back into the array. - coords(:, 1:n_tracks-1) = old_coords + allocate(new_coords(3, n_tracks)) + new_coords(:, 1:n_tracks-1) = coords + call move_alloc(FROM=new_coords, TO=coords) ! Write current coordinates into the newest column. coords(:, n_tracks) = p % coord0 % xyz From bfc172f26485487bd780a37d233d0f4961df4bca Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 10 Oct 2013 00:02:26 -0400 Subject: [PATCH 23/84] Added track output test. Also fixed a typo in track.py --- src/utils/track.py | 2 +- tests/test_track_output/geometry.xml | 305 +++++++++++++++++++ tests/test_track_output/materials.xml | 95 ++++++ tests/test_track_output/settings.xml | 30 ++ tests/test_track_output/test_track_output.py | 80 +++++ tests/test_track_output/true_poly.pvtp | 9 + 6 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 tests/test_track_output/geometry.xml create mode 100644 tests/test_track_output/materials.xml create mode 100644 tests/test_track_output/settings.xml create mode 100644 tests/test_track_output/test_track_output.py create mode 100644 tests/test_track_output/true_poly.pvtp diff --git a/src/utils/track.py b/src/utils/track.py index 5fe20ce537..a2dca34e14 100755 --- a/src/utils/track.py +++ b/src/utils/track.py @@ -42,7 +42,7 @@ def main(): # Check input file extensions. for fname in args.input: if not (fname.endswith('.h5') or fname.endswith('.binary')): - raise ValueError("Input file names must either end iwth '.h5' or" + raise ValueError("Input file names must either end with '.h5' or" "'.binary'.") # Make sure that the output filename ends with '.pvtp'. diff --git a/tests/test_track_output/geometry.xml b/tests/test_track_output/geometry.xml new file mode 100644 index 0000000000..f3566ab17f --- /dev/null +++ b/tests/test_track_output/geometry.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 1 2 2 2 2 2 2 2 1 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 1 2 2 2 2 2 2 2 1 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 1 2 2 1 2 2 2 1 2 2 1 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 3 2 2 2 2 2 2 2 1 2 2 2 + 2 2 2 2 2 1 2 2 2 1 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + + + + + + + + + + -12.2682 -12.2682 + 1.63576 1.63576 + + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + + + + + + + + + + + + + + + + + + -85.8774 -85.8774 + 24.5364 24.5364 + + 999 999 130 140 150 999 999 + 999 220 230 240 250 260 999 + 130 320 777 222 333 360 150 + 410 240 444 111 666 240 470 + 510 520 888 555 998 560 570 + 999 620 630 240 650 660 999 + 999 999 510 740 570 999 999 + + + + + + + + diff --git a/tests/test_track_output/materials.xml b/tests/test_track_output/materials.xml new file mode 100644 index 0000000000..7befbddf54 --- /dev/null +++ b/tests/test_track_output/materials.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_track_output/settings.xml b/tests/test_track_output/settings.xml new file mode 100644 index 0000000000..420bc5949b --- /dev/null +++ b/tests/test_track_output/settings.xml @@ -0,0 +1,30 @@ + + + + + + + + + + 8 + 1 + 1000 + + + + + + + + + -1 -1 -1 1 1 1 + + + + + 1 1 1 + 1 1 2 + + + diff --git a/tests/test_track_output/test_track_output.py b/tests/test_track_output/test_track_output.py new file mode 100644 index 0000000000..d652d97e3f --- /dev/null +++ b/tests/test_track_output/test_track_output.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +import os +from subprocess import Popen, STDOUT, PIPE, call +import filecmp +from nose_mpi import NoseMPI +import glob + +pwd = os.path.dirname(__file__) + +def setup(): + os.putenv('PWD', pwd) + os.chdir(pwd) + +def test_run(): + openmc_path = pwd + '/../../src/openmc' + if int(NoseMPI.mpi_np) > 0: + proc = Popen([NoseMPI.mpi_exec, '-np', NoseMPI.mpi_np, openmc_path], + stderr=STDOUT, stdout=PIPE) + else: + proc = Popen([openmc_path], stderr=STDOUT, stdout=PIPE) + returncode = proc.wait() + print(proc.communicate()[0]) + assert returncode == 0 + +def test_created_outputs(): + outputs = [glob.glob(''.join((pwd, '/track_1_1_1.*')))] + outputs.append(glob.glob(''.join((pwd, '/track_1_1_2.*')))) + for files in outputs: + assert len(files) == 1 + assert files[0].endswith('binary') or files[0].endswith('h5') + +##def test_outputs(): +## outputs = [glob.glob(''.join((pwd, '/track_1_1_1.*')))[0]] +## outputs.append(glob.glob(''.join((pwd, '/track_1_1_2.*')))[0]) +## if outputs[0].endswith('binary'): +## metrics = [''.join((pwd, '/track_1_1_1_true.binary')), +## ''.join((pwd, '/track_1_1_2_true.binary'))] +## else: +## metrics = [''.join((pwd, '/track_1_1_1_true.h5')), +## ''.join((pwd, '/track_1_1_2_true.h5'))] +## for i in range(2): +## output = outputs[i] +## metric = metrics[i] +## compare = filecmp.cmp(output, metric) +## if not compare: +## if outputs[0].endswith('binary'): +## extension = 'binary' +## else: +## extension = 'h5' +## os.rename(output, ''.join((pwd, '/track_1_1_{}_error.{}'.format( +## i+1, extension)))) +## assert compare + +def test_outputs(): + call(['../../src/utils/track.py', '-o', 'poly'] + + glob.glob(''.join((pwd, '/track*')))) + poly = ''.join((pwd, '/poly.pvtp')) + assert os.path.isfile(poly) + metric = ''.join((pwd, '/true_poly.pvtp')) + compare = filecmp.cmp(poly, metric) + if not compare: + os.rename('poly.pvtp', 'poly_error.pvtp') + assert compare + +##def test_results(): +## statepoint = glob.glob(pwd + '/statepoint.10.*') +## call(['python', 'results.py', statepoint[0]]) +## compare = filecmp.cmp('results_test.dat', 'results_true.dat') +## if not compare: +## os.rename('results_test.dat', 'results_error.dat') +## assert compare + +def teardown(): + temp_files = glob.glob(''.join((pwd, '/statepoint*'))) + temp_files = temp_files + glob.glob(''.join((pwd, '/track*'))) + temp_files = temp_files + glob.glob(''.join((pwd, '/poly*'))) + for f in temp_files: + if os.path.exists(f): + os.remove(f) diff --git a/tests/test_track_output/true_poly.pvtp b/tests/test_track_output/true_poly.pvtp new file mode 100644 index 0000000000..6ded87a0ec --- /dev/null +++ b/tests/test_track_output/true_poly.pvtp @@ -0,0 +1,9 @@ + + + + + + + + + From c1d5725d41f54dfa783cf07a93aac30610899b76 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 Oct 2013 03:18:46 -0400 Subject: [PATCH 24/84] Removed error file from teardown scope Also removed scaffold code --- tests/test_track_output/test_track_output.py | 32 +------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/tests/test_track_output/test_track_output.py b/tests/test_track_output/test_track_output.py index d652d97e3f..4600c89315 100644 --- a/tests/test_track_output/test_track_output.py +++ b/tests/test_track_output/test_track_output.py @@ -30,28 +30,6 @@ def test_created_outputs(): assert len(files) == 1 assert files[0].endswith('binary') or files[0].endswith('h5') -##def test_outputs(): -## outputs = [glob.glob(''.join((pwd, '/track_1_1_1.*')))[0]] -## outputs.append(glob.glob(''.join((pwd, '/track_1_1_2.*')))[0]) -## if outputs[0].endswith('binary'): -## metrics = [''.join((pwd, '/track_1_1_1_true.binary')), -## ''.join((pwd, '/track_1_1_2_true.binary'))] -## else: -## metrics = [''.join((pwd, '/track_1_1_1_true.h5')), -## ''.join((pwd, '/track_1_1_2_true.h5'))] -## for i in range(2): -## output = outputs[i] -## metric = metrics[i] -## compare = filecmp.cmp(output, metric) -## if not compare: -## if outputs[0].endswith('binary'): -## extension = 'binary' -## else: -## extension = 'h5' -## os.rename(output, ''.join((pwd, '/track_1_1_{}_error.{}'.format( -## i+1, extension)))) -## assert compare - def test_outputs(): call(['../../src/utils/track.py', '-o', 'poly'] + glob.glob(''.join((pwd, '/track*')))) @@ -60,17 +38,9 @@ def test_outputs(): metric = ''.join((pwd, '/true_poly.pvtp')) compare = filecmp.cmp(poly, metric) if not compare: - os.rename('poly.pvtp', 'poly_error.pvtp') + os.rename('poly.pvtp', 'error_poly.pvtp') assert compare -##def test_results(): -## statepoint = glob.glob(pwd + '/statepoint.10.*') -## call(['python', 'results.py', statepoint[0]]) -## compare = filecmp.cmp('results_test.dat', 'results_true.dat') -## if not compare: -## os.rename('results_test.dat', 'results_error.dat') -## assert compare - def teardown(): temp_files = glob.glob(''.join((pwd, '/statepoint*'))) temp_files = temp_files + glob.glob(''.join((pwd, '/track*'))) From c87291ab9238b45c6cad2080847f9b8de0e81f5c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 Oct 2013 03:52:23 -0400 Subject: [PATCH 25/84] Added documentation. --- docs/source/usersguide/install.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 159c689e98..527ec6b302 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -342,6 +342,7 @@ OpenMC accepts the following command line flags: -r, --restart file Restart a previous run from a state point or a particle restart file -s, --threads N Run with *N* OpenMP threads +-t, --track Write tracks for all particles -v, --version Show version information ----------------------------------------------------- From b4bc149737930471b9f6c9cf9bd1f4bcfca490dd Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 Oct 2013 03:57:29 -0400 Subject: [PATCH 26/84] Fixed a merge error --- src/source.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/source.F90 b/src/source.F90 index c3e706b7e3..2082ad6dda 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -157,6 +157,7 @@ contains integer(8) :: particle_seed ! unique index for particle integer :: i type(Bank), pointer, save :: src => null() +!$omp threadprivate(src) ! set defaults call p % initialize() From 4edef2dd2d7bd7714e1a03de8af00929f1e36f7c Mon Sep 17 00:00:00 2001 From: walshjon Date: Wed, 23 Oct 2013 14:26:22 -0700 Subject: [PATCH 27/84] removed extraneous forced recalculation of S(a,b) xs --- src/ace_header.F90 | 5 +++-- src/cross_section.F90 | 11 ++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ace_header.F90 b/src/ace_header.F90 index d075c22889..5e2f39a007 100644 --- a/src/ace_header.F90 +++ b/src/ace_header.F90 @@ -214,8 +214,9 @@ module ace_header real(8) :: kappa_fission ! microscopic energy-released from fission ! Information for S(a,b) use - integer :: index_sab ! index in sab_tables (zero means no table) - real(8) :: elastic_sab ! microscopic elastic scattering on S(a,b) table + integer :: index_sab ! index in sab_tables (zero means no table) + integer :: last_index_sab = 0 ! index in sab_tables last used by this nuclide + real(8) :: elastic_sab ! microscopic elastic scattering on S(a,b) table ! Information for URR probability table use logical :: use_ptable ! in URR range with probability tables? diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 4c9ffb8c88..299033590d 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -93,6 +93,8 @@ contains ! Calculate microscopic cross section for this nuclide if (p % E /= micro_xs(i_nuclide) % last_E) then call calculate_nuclide_xs(i_nuclide, i_sab, p % E) + else if (i_sab /= micro_xs(i_nuclide) % last_index_sab) then + call calculate_nuclide_xs(i_nuclide, i_sab, p % E) end if ! ======================================================================== @@ -235,13 +237,8 @@ contains end if end if - ! Set last evaluated energy -- if we're in S(a,b) region, force - ! re-calculation of cross-section - if (i_sab == 0) then - micro_xs(i_nuclide) % last_E = E - else - micro_xs(i_nuclide) % last_E = ZERO - end if + micro_xs(i_nuclide) % last_E = E + micro_xs(i_nuclide) % last_index_sab = i_sab end subroutine calculate_nuclide_xs From 2e45b2f57797bf675e64ae413b5593b6ebb5b724 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 19 Oct 2013 11:21:20 -0400 Subject: [PATCH 28/84] Update release notes and conf.py. --- docs/source/conf.py | 2 +- docs/source/releasenotes/index.rst | 1 + docs/source/releasenotes/notes_0.5.3.rst | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f3553c33e1..8752b48986 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,7 +48,7 @@ copyright = u'2011-2013, Massachusetts Institute of Technology' # The short X.Y version. version = "0.5" # The full version, including alpha/beta/rc tags. -release = "0.5.2" +release = "0.5.3" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/releasenotes/index.rst b/docs/source/releasenotes/index.rst index 876d953ff8..9164fa5943 100644 --- a/docs/source/releasenotes/index.rst +++ b/docs/source/releasenotes/index.rst @@ -10,6 +10,7 @@ bugs fixed, and known issues for each successive release. .. toctree:: :maxdepth: 1 + notes_0.5.3 notes_0.5.2 notes_0.5.1 notes_0.5.0 diff --git a/docs/source/releasenotes/notes_0.5.3.rst b/docs/source/releasenotes/notes_0.5.3.rst index fb7b1aa79a..c9c7aee38d 100644 --- a/docs/source/releasenotes/notes_0.5.3.rst +++ b/docs/source/releasenotes/notes_0.5.3.rst @@ -21,12 +21,20 @@ the problem at hand (mostly on the number of nuclides in the problem). New Features ------------ -- Special run mode --tallies removed. +- Output interface enhanced to allow multiple files handles to be opened +- Particle restart file linked to output interface - Particle restarts and state point restarts are both identified with the -r command line flag. -- New regression test suite. -- All memory leaks fixed. -- Shared-memory parallelism with OpenMP. +- Particle instance no longer global, passed to all physics routines +- Physics routines refactored to rely less on global memory, more arguments + passed in +- CMFD routines refactored and now can compute dominance ratio on the fly +- PETSc 3.4.2 or higher must be used and compiled with fortran datatype support +- Memory leaks fixed except for ones from xml-fortran package +- Test suite enhanced to test output with different compiler options +- Description of OpenMC development workflow added +- OpenMP shared-memory parallelism added +- Special run mode --tallies removed. --------- Bug Fixes @@ -35,7 +43,7 @@ Bug Fixes - 2b1e8a_: Normalize direction vector after reflecting particle. - 5853d2_: Set blank default for cross section listing alias. - e178c7_: Fix infinite loop with words greater than 80 characters in write_message. -- c18a6e_: Chcek for valid secondary mode on S(a,b) tables. +- c18a6e_: Check for valid secondary mode on S(a,b) tables. - 82c456_: Fix bug where last process could have zero particles. .. _2b1e8a: https://github.com/mit-crpg/openmc/commit/2b1e8a From 96f2035254eb006a99cd3f25f4c8fe4dfe1e3f47 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 6 Nov 2013 12:41:14 +0100 Subject: [PATCH 29/84] Formatting changes from previous merge. --- src/ace.F90 | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ace.F90 b/src/ace.F90 index 209f5daa33..d54aa83c28 100644 --- a/src/ace.F90 +++ b/src/ace.F90 @@ -243,15 +243,14 @@ contains ! Read first line of header read(UNIT=in, FMT='(A10,2G12.0,1X,A10)') name, awr, kT, date_ - - ! Check that correct xs was found - ! (if XS listing (cross_sections.xml) is broken, this may not be the case) - - if( trim(adjustl(name)) /= trim(adjustl(listing % name)) ) then - message = "XS listing entry " // trim(listing % name) // " did not & - &match ACE data, " // trim(name) // " found instead." - call fatal_error() - end if + + ! Check that correct xs was found -- if cross_sections.xml is broken, the + ! location of the table may be wrong + if(adjustl(name) /= adjustl(listing % name)) then + message = "XS listing entry " // trim(listing % name) // " did not & + &match ACE data, " // trim(name) // " found instead." + call fatal_error() + end if ! Read more header and NXS and JXS read(UNIT=in, FMT=100) comment, mat, & From 4fc6643e0ac9175ed9fa063194f80e3305b784d0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 7 Nov 2013 10:04:27 +0100 Subject: [PATCH 30/84] Fix --track in man page. --- man/man1/openmc.1 | 1 + 1 file changed, 1 insertion(+) diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index 64e97ce21c..cac8d59087 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -26,6 +26,7 @@ Restart a previous run from a state point or a particle restart file named .TP .BI \-s " N" "\fR,\fP \-\-threads" " N" Use \fIN\fP OpenMP threads. +.TP .B "\-t\fR, \fP\-\-track" Write tracks for all particles. .TP From 5b11dc668701e1808ea5c4344a1f09a3e6448887 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 7 Nov 2013 10:04:51 +0100 Subject: [PATCH 31/84] Add line breaks in processing.rst. --- docs/source/usersguide/processing.rst | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index c12041214a..780fe37938 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -360,11 +360,19 @@ Particle Track Visualization .. image:: ../../img/Tracks.png :height: 200px -OpenMC can dump particle tracks—the position of particles as they are transported through the geometry. There are two ways to make OpenMC output tracks: all particle tracks through a commandline argument or specific particle tracks through settings.xml. +OpenMC can dump particle tracks—the position of particles as they are +transported through the geometry. There are two ways to make OpenMC output +tracks: all particle tracks through a commandline argument or specific particle +tracks through settings.xml. -Running OpenMC with the argument "-t", "-track", or "--track" will cause a track file to be created for every particle transported in the code. +Running OpenMC with the argument "-t", "-track", or "--track" will cause a track +file to be created for every particle transported in the code. -The settings.xml file can dictate that specific particle tracks are output. These particles are specified withen a ''track'' element. The ''track'' element should contain triplets of integers specifying the batch, generation, and particle numbers, respectively. For example, to output the tracks for particles 3 and 4 of batch 1 and generation 2 the settings.xml file should contain: +The settings.xml file can dictate that specific particle tracks are output. +These particles are specified withen a ''track'' element. The ''track'' element +should contain triplets of integers specifying the batch, generation, and +particle numbers, respectively. For example, to output the tracks for particles +3 and 4 of batch 1 and generation 2 the settings.xml file should contain: .. code-block:: xml @@ -373,9 +381,13 @@ The settings.xml file can dictate that specific particle tracks are output. The 1 2 4 -After running OpenMC, the directory should contain a file of the form "track_(batch #)_(generation #)_(particle #).(binary or h5)" for each particle tracked. These track files can be converted into VTK poly data files with the "track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" where OUT is the optional output filename and IN is one or more filenames describing track files. The default output name is "track.pvtp". A common usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. - - - - - +After running OpenMC, the directory should contain a file of the form +"track_(batch #)_(generation #)_(particle #).(binary or h5)" for each particle +tracked. These track files can be converted into VTK poly data files with the +"track.py" utility. The usage of track.py is of the form "track.py [-o OUT] IN" +where OUT is the optional output filename and IN is one or more filenames +describing track files. The default output name is "track.pvtp". A common +usage of track.py is "track.py track*.binary" which will use the data from all +binary track files in the directory to write a "track.pvtp" VTK output file. +The .pvtp file can then be read and plotted by 3d visualization programs such as +Paraview. From 19c52d915a30585ae461b76310227ff18e508a8b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 7 Nov 2013 10:05:39 +0100 Subject: [PATCH 32/84] Fix material in test_track_output. --- tests/test_track_output/materials.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_track_output/materials.xml b/tests/test_track_output/materials.xml index 7befbddf54..bbf880d960 100644 --- a/tests/test_track_output/materials.xml +++ b/tests/test_track_output/materials.xml @@ -8,7 +8,7 @@ - + From 727451319d6313d27480d333cbe626b0230ca71a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 7 Nov 2013 10:17:51 +0100 Subject: [PATCH 33/84] Skip one test in test_track_output if vtk Python module is not available. --- tests/test_track_output/test_track_output.py | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_track_output/test_track_output.py b/tests/test_track_output/test_track_output.py index 4600c89315..0e7674154c 100644 --- a/tests/test_track_output/test_track_output.py +++ b/tests/test_track_output/test_track_output.py @@ -3,26 +3,32 @@ import os from subprocess import Popen, STDOUT, PIPE, call import filecmp -from nose_mpi import NoseMPI import glob +from nose.plugins.skip import SkipTest + +from nose_mpi import NoseMPI + pwd = os.path.dirname(__file__) -def setup(): + +def setup(): os.putenv('PWD', pwd) os.chdir(pwd) + def test_run(): openmc_path = pwd + '/../../src/openmc' if int(NoseMPI.mpi_np) > 0: proc = Popen([NoseMPI.mpi_exec, '-np', NoseMPI.mpi_np, openmc_path], - stderr=STDOUT, stdout=PIPE) + stderr=STDOUT, stdout=PIPE) else: proc = Popen([openmc_path], stderr=STDOUT, stdout=PIPE) returncode = proc.wait() print(proc.communicate()[0]) assert returncode == 0 + def test_created_outputs(): outputs = [glob.glob(''.join((pwd, '/track_1_1_1.*')))] outputs.append(glob.glob(''.join((pwd, '/track_1_1_2.*')))) @@ -30,7 +36,15 @@ def test_created_outputs(): assert len(files) == 1 assert files[0].endswith('binary') or files[0].endswith('h5') + def test_outputs(): + # If vtk python module is not available, we can't run track.py so skip this + # test + try: + import vtk + except ImportError: + raise SkipTest + call(['../../src/utils/track.py', '-o', 'poly'] + glob.glob(''.join((pwd, '/track*')))) poly = ''.join((pwd, '/poly.pvtp')) @@ -41,6 +55,7 @@ def test_outputs(): os.rename('poly.pvtp', 'error_poly.pvtp') assert compare + def teardown(): temp_files = glob.glob(''.join((pwd, '/statepoint*'))) temp_files = temp_files + glob.glob(''.join((pwd, '/track*'))) From b9bd67d2cd61fe13e2081df8f2db0c904f3c35a8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 7 Nov 2013 10:23:15 +0100 Subject: [PATCH 34/84] Run fewer particles for test_track_output. --- tests/test_track_output/settings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_track_output/settings.xml b/tests/test_track_output/settings.xml index 420bc5949b..ef74341dbb 100644 --- a/tests/test_track_output/settings.xml +++ b/tests/test_track_output/settings.xml @@ -7,9 +7,9 @@ - 8 - 1 - 1000 + 2 + 0 + 100 From 99c76e9377f738330108890b9c07ee5ad2a20b42 Mon Sep 17 00:00:00 2001 From: Bryan Herman Date: Thu, 7 Nov 2013 13:10:01 -0500 Subject: [PATCH 35/84] fixed description of CMFD mesh --- docs/source/usersguide/input.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 9ec51ca21e..3b389c4381 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -1196,13 +1196,9 @@ with "false". ```` Element ------------------ -If a structured mesh is desired as a filter for a tally, it must be specified in -a separate element with the tag name ````. This element has the following +The CMFD mesh is a structured Cartesian mesh. This element has the following attributes/sub-elements: - :type: - The type of structured mesh. Only "rectangular" is currently supported. - :lower_left: The lower-left corner of the structured mesh. If only two coordinate are given, it is assumed that the mesh is an x-y mesh. From bda4fd0cb6e0762e84a65ec97bcf5b5f5a661c33 Mon Sep 17 00:00:00 2001 From: Bryan Herman Date: Thu, 14 Nov 2013 08:59:06 -0500 Subject: [PATCH 36/84] fixed preallocation of cmfd loss matrix when not using coremap overlay --- src/cmfd_loss_operator.F90 | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cmfd_loss_operator.F90 b/src/cmfd_loss_operator.F90 index 6e9917ac5b..69be017644 100644 --- a/src/cmfd_loss_operator.F90 +++ b/src/cmfd_loss_operator.F90 @@ -26,8 +26,10 @@ contains integer :: nnz ! number of nonzeros in matrix integer :: n_i ! number of interior cells integer :: n_c ! number of corner cells - integer :: n_s ! number side cells + integer :: n_s ! number of side cells + integer :: n_e ! number of edge cells integer :: nz_c ! number of non-zero corner cells + integer :: nz_e ! number of non-zero edge cells integer :: nz_s ! number of non-zero side cells integer :: nz_i ! number of non-zero interior cells @@ -48,13 +50,16 @@ contains if (cmfd_coremap) then nnz = preallocate_loss_matrix(nx, ny, nz, ng, n) else ! structured Cartesian grid - n_c = 4 ! define # of corners - n_s = 2*(nx + ny) - 8 ! define # of sides - n_i = nx*ny - (n_c + n_S) ! define # of interiors - nz_c = ng*n_c*(3 + ng - 1) ! define # nonzero corners - nz_s = ng*n_s*(4 + ng - 1) ! define # nonzero sides - nz_i = ng*n_i*(5 + ng - 1) ! define # nonzero interiors - nnz = nz_c + nz_s + nz_i + n_c = 8 ! define # of corners + n_e = 4*(nx - 2) + 4*(ny - 2) + 4*(nz - 2) ! define # of edges + n_s = 2*(nx - 2)*(ny - 2) + 2*(nx - 2)*(nz - 2) & + + 2*(ny - 2)*(nz - 2) ! define # of sides + n_i = nx*ny*nz - (n_c + n_e + n_s) ! define # of interiors + nz_c = ng*n_c*(4 + ng - 1) ! define # nonzero corners + nz_e = ng*n_e*(5 + ng - 1) ! define # nonzero edges + nz_s = ng*n_s*(6 + ng - 1) ! define # nonzero sides + nz_i = ng*n_i*(7 + ng - 1) ! define # nonzero interiors + nnz = nz_c + nz_e + nz_s + nz_i end if ! Configure loss matrix From 740b0072cb23651f0a65fb38251fe017963ca883 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Nov 2013 19:11:31 -0500 Subject: [PATCH 37/84] Particle track data should be threadprivate. --- src/track_output.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/track_output.F90 b/src/track_output.F90 index 4b02dfe9a2..8b9c17b89a 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -14,6 +14,7 @@ module track_output integer, private :: n_tracks ! total number of tracks real(8), private, allocatable :: coords(:,:) ! track coordinates +!$omp threadprivate(n_tracks, coords) contains From a70fdd1b868f493fd4a58cd07d6a467aba24eec8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Nov 2013 19:14:21 -0500 Subject: [PATCH 38/84] Set default for p % write_track. --- src/particle_header.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/particle_header.F90 b/src/particle_header.F90 index b9762960ed..e1c696ff16 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -77,7 +77,7 @@ module particle_header integer :: n_collision ! # of collisions ! Track output - logical :: write_track + logical :: write_track = .false. contains procedure :: initialize => initialize_particle From ef004c457941b430fc84ee6c2ad23baba7d12b98 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Nov 2013 19:18:47 -0500 Subject: [PATCH 39/84] Update description of in user's guide. --- docs/source/usersguide/input.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 3b389c4381..1ae061f4c8 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -636,9 +636,10 @@ Each ```` element can have the following attributes or sub-elements: --------------------- The ```` can be used to represent repeating structures (e.g. fuel pins -in an assembly) or other geometry which naturally fits into a two-dimensional -structured mesh. Each cell within the lattice is filled with a specified -universe. A ```` accepts the following attributes or sub-elements: +in an assembly) or other geometry which naturally fits into a two- or +three-dimensional structured mesh. Each cell within the lattice is filled with a +specified universe. A ```` accepts the following attributes or +sub-elements: :id: A unique integer that can be used to identify the surface. @@ -650,18 +651,19 @@ universe. A ```` accepts the following attributes or sub-elements: *Default*: rectangular :dimension: - Two integers representing the number of lattice cells in the x- and y- - directions, respectively. + Two or three integers representing the number of lattice cells in the x- and + y- (and z-) directions, respectively. *Default*: None :lower_left: - The coordinates of the lower-left corner of the lattice. + The coordinates of the lower-left corner of the lattice. If the lattice is + two-dimensional, only the x- and y-coordinates are specified. *Default*: None :width: - The width of the lattice cell in the x- and y- directions. + The width of the lattice cell in the x- and y- (and z-) directions. *Default*: None From b83cdf207d235c95c4430e83f79a0751aa2a55e5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Nov 2013 19:54:26 -0500 Subject: [PATCH 40/84] Fix bugs in particle track output. --- src/input_xml.F90 | 2 +- src/track_output.F90 | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 14b49292ee..9bedbc8b3c 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -414,7 +414,7 @@ contains n_tracks = n_tracks/3 allocate(track_identifiers(3,n_tracks)) do i=1, n_tracks - track_identifiers(1:3,i) = track_(3*(i-1)+1 : 3*(i-1)+4) + track_identifiers(1:3,i) = track_(3*(i-1)+1 : 3*(i-1)+3) end do end if diff --git a/src/track_output.F90 b/src/track_output.F90 index 8b9c17b89a..021cbb8ca6 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -24,7 +24,6 @@ contains subroutine initialize_particle_track() n_tracks = 0 - allocate(coords(1,1)) end subroutine initialize_particle_track !=============================================================================== @@ -34,13 +33,17 @@ contains subroutine write_particle_track(p) type(Particle), intent(in) :: p - real(8), allocatable :: new_coords(:, :) + real(8), allocatable :: new_coords(:, :) - ! Add another column to coords. + ! Add another column to coords n_tracks = n_tracks + 1 - allocate(new_coords(3, n_tracks)) - new_coords(:, 1:n_tracks-1) = coords - call move_alloc(FROM=new_coords, TO=coords) + if (allocated(coords)) then + allocate(new_coords(3, n_tracks)) + new_coords(:, 1:n_tracks-1) = coords + call move_alloc(FROM=new_coords, TO=coords) + else + allocate(coords(3,1)) + end if ! Write current coordinates into the newest column. coords(:, n_tracks) = p % coord0 % xyz From 250b5f412fb275545852fc7438f3359869ec28b7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Nov 2013 21:08:14 -0500 Subject: [PATCH 41/84] Update list of publications. --- docs/source/publications.rst | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index d757b9c3ce..a7100fc18b 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -4,6 +4,53 @@ Publications ============ +- Benoit Forget, Sheng Xu, and Kord Smith, "Direct Doppler broadening in Monte + Carlo simulations using the multipole representation," *Ann. Nucl. Energy*, + **64**, 78--85 (2014). ``_ + +- Andrew Siegel, Kord Smith, Kyle Felker, Paul Romano, Benoit Forget, and Peter + Beckman, "Improved cache performance in Monte Carlo transport calculations + using energy banding," *Comput. Phys. Commun.* + (2013). ``_ + +- Jonathan A. Walsh, Benoit Forget, and Kord S. Smith, "Validation of OpenMC + Reactor Physics Simulations with the B&W 1810 Series Benchmarks," + *Trans. Am. Nucl. Soc.*, **109**, 1301--1304 (2013). + +- Bryan R. Herman, Benoit Forget, and Kord Smith, "Utilizing CMFD in OpenMC to + Estimate Dominance Ratio and Adjoint," *Trans. Am. Nucl. Soc.*, **109**, + 1389-1392 (2013). + +- Timothy P. Burke, Brian C. Kiedrowski, and William R. Martin, "Flux and + Reaction Rate Kernel Density Estimators in OpenMC," *Trans. Am. Nucl. Soc.*, + **109**, 683-686 (2013). + +- Paul K. Romano, Benoit Forget, Kord Smith, and Andrew Siegel, "On the user of + tally servers in Monte Carlo simulations of light-water reactors," + *Proc. Joint International Conference on Supercomputing in Nuclear + Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). + +- Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit + Forget, and Kord Smith, "OpenMC: A State-of-the-Art Monte Carlo Code for + Research and Development," *Proc. Joint International Conference on + Supercomputing in Nuclear Applications and Monte Carlo*, Paris, France, + Oct. 27--31 (2013). + +- Kyle G. Felker, Andrew R. Siegel, Kord S. Smith, Paul K. Romano, and Benoit + Forget, "The energy band memory server algorithm for parallel Monte Carlo + calculations," *Proc. Joint International Conference on Supercomputing in + Nuclear Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). + +- John R. Tramm and Andrew R. Siegel, "Memory Bottlenecks and Memory Contention + in Multi-Core Monte Carlo Transport Codes," *Proc. Joint International + Conference on Supercomputing in Nuclear Applications and Monte Carlo*, Paris, + France, Oct. 27--31 (2013). + +- Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, + "Multi-core performance studies of a Monte Carlo neutron transport code," + *Int. J. High Perform. Comput. Appl.* + (2013). ``_ + - Paul K. Romano, Andrew R. Siegel, Benoit Forget, and Kord Smith, "Data decomposition of Monte Carlo particle transport simulations via tally servers," *J. Comput. Phys.*, **252**, 20--36 From 686ccb913d53b76aba530629fbc367c0b99f1c17 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 19 Nov 2013 19:35:26 -0500 Subject: [PATCH 42/84] Remove 'subject to change' on 0.5.3 release notes. --- docs/source/releasenotes/notes_0.5.3.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/source/releasenotes/notes_0.5.3.rst b/docs/source/releasenotes/notes_0.5.3.rst index c9c7aee38d..6d93f9aece 100644 --- a/docs/source/releasenotes/notes_0.5.3.rst +++ b/docs/source/releasenotes/notes_0.5.3.rst @@ -4,10 +4,6 @@ Release Notes for OpenMC 0.5.3 ============================== -.. note:: - These release notes are for an upcoming release of OpenMC and are still - subject to change. - ------------------- System Requirements ------------------- From 6d04cdd905c3dd2022ee535bbf2be4bf3c9c6ec8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Nov 2013 18:42:26 -0500 Subject: [PATCH 43/84] Fix typo in list of publications. --- docs/source/publications.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index a7100fc18b..c17e652c7f 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -25,7 +25,7 @@ Publications Reaction Rate Kernel Density Estimators in OpenMC," *Trans. Am. Nucl. Soc.*, **109**, 683-686 (2013). -- Paul K. Romano, Benoit Forget, Kord Smith, and Andrew Siegel, "On the user of +- Paul K. Romano, Benoit Forget, Kord Smith, and Andrew Siegel, "On the use of tally servers in Monte Carlo simulations of light-water reactors," *Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). From 772da9ba6bd0b0add9d70913f01aae3e595586c3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Nov 2013 11:26:32 -0500 Subject: [PATCH 44/84] Added statepoint.py source site processing example in user's guide. Closes #221 on github. --- docs/source/usersguide/processing.rst | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 780fe37938..043041ef42 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -391,3 +391,51 @@ usage of track.py is "track.py track*.binary" which will use the data from all binary track files in the directory to write a "track.pvtp" VTK output file. The .pvtp file can then be read and plotted by 3d visualization programs such as Paraview. + +---------------------- +Source Site Processing +---------------------- + +For eigenvalue problems, OpenMC will store information on the fission source +sites in the statepoint file by default. For each source site, the weight, +position, sampled direction, and sampled energy are stored. To extract this data +from a statepoint file, the statepoint.py Python module can be used. Below is an +example of an interactive ipython session using the statepoint.py Python module: + +.. code-block:: python + + In [1]: import statepoint + + In [2]: sp = statepoint.StatePoint('statepoint.100.h5') + + In [3]: sp.read_source() + + In [4]: len(sp.source) + Out[4]: 1000 + + In [5]: sp.source[0:10] + Out[5]: + [, + , + , + , + , + , + , + , + , + ] + + In [6]: site = sp.source[0] + + In [7]: site.weight + Out[7]: 1.0 + + In [8]: site.xyz + Out[8]: array([ 2.21980946, -8.92686048, 87.93720485]) + + In [9]: site.uvw + Out[9]: array([ 0.06740523, 0.50612814, 0.85982024]) + + In [10]: site.E + Out[10]: 0.93292326356564159 From 0d866a08a7526872b35a8e5cb157c068b33b8612 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Nov 2013 12:12:54 -0500 Subject: [PATCH 45/84] Modify XML input parsing instructions in developer's guide. --- docs/source/devguide/index.rst | 2 +- docs/source/devguide/xml-fortran.rst | 40 ---------------------------- docs/source/devguide/xml-parsing.rst | 38 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 docs/source/devguide/xml-fortran.rst create mode 100644 docs/source/devguide/xml-parsing.rst diff --git a/docs/source/devguide/index.rst b/docs/source/devguide/index.rst index 1eb7436d03..1ceba324c1 100644 --- a/docs/source/devguide/index.rst +++ b/docs/source/devguide/index.rst @@ -15,6 +15,6 @@ as debugging. structures styleguide workflow - xml-fortran + xml-parsing statepoint voxel diff --git a/docs/source/devguide/xml-fortran.rst b/docs/source/devguide/xml-fortran.rst deleted file mode 100644 index 456cd662f1..0000000000 --- a/docs/source/devguide/xml-fortran.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. _devguide_xml-fortran: - -========================= -xml-fortran Input Parsing -========================= - -OpenMC relies on the xml-fortran package for reading and intrepreting the XML -input files for geometry, materials, settings, tallies, etc. The use of an XML -format makes writing input files considerably more flexible than would otherwise -be possible. - -With the xml-fortran package, extending the user input files to include new tags -is fairly straightforward. A "template" file exists for each diferent type of -input file that tells xml-fortran what to expect in a file. These template files -can be found in the src/templates directory. The steps for modifying/adding -input are as follows: - -1. Add a ````` tag to the desired template file, -e.g. src/templates/geometry_t.xml. See the `xml-fortran documentation`_ for a -description of the acceptable fields. - -2. In the input_xml module, any input given in your new tag will be read -automatically through a call to, e.g. read_xml_file_geometry_t. Whatever -variable name you specified should have the data available. - -3. Add code in the appropriate subroutine to check the variable for any possible -errors. - -4. Add a variable in OpenMC to copy the temporary variable into if there are no -errors. - -A set of `RELAX NG`_ schemata exists that enables real-time validation of input -files when using the GNU Emacs text editor. You should also modify the RELAX NG -schema for the template you changed (e.g. src/templates/geometry.rnc) so that -those who use Emacs can confirm whether their input is valid before they -run. You will need to be familiar with RELAX NG `compact syntax`_. - -.. _xml-fortran documentation: http://xml-fortran.sourceforge.net/documentation.html -.. _RELAX NG: http://relaxng.org/ -.. _compact syntax: http://relaxng.org/compact-tutorial-20030326.html diff --git a/docs/source/devguide/xml-parsing.rst b/docs/source/devguide/xml-parsing.rst new file mode 100644 index 0000000000..8310d53e4e --- /dev/null +++ b/docs/source/devguide/xml-parsing.rst @@ -0,0 +1,38 @@ +.. _devguide_xml-parsing: + +================= +XML Input Parsing +================= + +OpenMC relies on the FoX_ Fortran XML library for reading and intrepreting the +XML input files for geometry, materials, settings, tallies, etc. The use of an +XML format makes writing input files considerably more flexible than would +otherwise be possible. + +With the FoX library, extending the user input files to include new tags is +fairly straightforward. The steps for modifying/adding input are as follows: + +1. Add appropriate calls to procedures from the `xml_interface module`_, such as +``check_for_node``, ``get_node_value``, and ``get_node_array``. All input +reading is performed in the `input_xml module`_. + +2. Make sure that your input can be categorized as one of the datatypes from +`XML Schema Part 2`_ and that parsing of the data appropriately reflects +this. For example, for a boolean_ value, true can be represented either by "true" +or by "1". + +3. Add code to check the variable for any possible errors. + +A set of `RELAX NG`_ schemata exists that enables real-time validation of input +files when using the GNU Emacs text editor. You should also modify the RELAX NG +schema for the file you changed (e.g. src/relaxng/geometry.rnc) so that +those who use Emacs can confirm whether their input is valid before they +run. You will need to be familiar with RELAX NG `compact syntax`_. + +.. _FoX: https://github.com/andreww/fox +.. _xml_interface module: https://github.com/mit-crpg/openmc/blob/develop/src/xml_interface.F90 +.. _input_xml module: https://github.com/mit-crpg/openmc/blob/develop/src/input_xml.F90 +.. _XML Schema Part 2: http://www.w3.org/TR/xmlschema-2/ +.. _boolean: http://www.w3.org/TR/xmlschema-2/#boolean +.. _RELAX NG: http://relaxng.org/ +.. _compact syntax: http://relaxng.org/compact-tutorial-20030326.html From 152c4d4b2f4d89c5077e9b49cb050f256ebe08e6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Nov 2013 12:18:54 -0500 Subject: [PATCH 46/84] Removed errors related to xml-fortran from troubleshooting guide. --- docs/source/usersguide/troubleshoot.rst | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/docs/source/usersguide/troubleshoot.rst b/docs/source/usersguide/troubleshoot.rst index 49cf41c8de..a13272aa65 100644 --- a/docs/source/usersguide/troubleshoot.rst +++ b/docs/source/usersguide/troubleshoot.rst @@ -19,25 +19,6 @@ you are using a compiler that does not support type-bound procedures from Fortran 2003. This affects any version of gfortran prior to 4.6. Downloading and installing the latest gfortran_ compiler should resolve this problem. -Fatal Error: Wrong module version '4' (expected '9') for file 'xml_data_cmfd_t.mod' opened at (1) -************************************************************************************************* - -The `.mod` modules files that are created by gfortran are versioned and -sometimes are usually not backwards compatible. If gfortran is upgraded and the -modules files for xml-fortran source files are not deleted, this error may -occur. To fix this, clear out all module and object files with :program:`make -distclean` and then recompiling. - -Fatal Error: File 'xml_data_cmfd_t.mod' opened at (1) is not a GFORTRAN module file -*********************************************************************************** - -When OpenMC compiles, the first thing it needs to do is compile source in the -xml-fortran subdirectory. If you compiled everything with a compiler other than -gfortran, performed a :program:`make clean`, and then tried to :program:`make` -with gfortran, the xml-fortran modules would have been compiled with a different -compiler. To fix this, try clearing out all module and object files with -:program:`make distclean` and then recompiling. - gfortran: unrecognized option '-cpp' ************************************ From acb16a3e884b387bd2bf1eb75e25876a334ab9fe Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Nov 2013 13:47:46 -0500 Subject: [PATCH 47/84] Prevent unused variable warning in cmfd_input. --- src/cmfd_input.F90 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index ac3d59824e..120db5f649 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -20,7 +20,9 @@ contains use cmfd_header, only: allocate_cmfd +#ifdef PETSC integer :: new_comm ! new mpi communicator +#endif integer :: color ! color group of processor ! Read in cmfd input file @@ -34,17 +36,17 @@ contains end if ! Split up procs -# ifdef PETSC +#ifdef PETSC call MPI_COMM_SPLIT(MPI_COMM_WORLD, color, 0, new_comm, mpi_err) -# endif +#endif ! assign to PETSc -# ifdef PETSC +#ifdef PETSC PETSC_COMM_WORLD = new_comm ! Initialize PETSc on all procs call PetscInitialize(PETSC_NULL_CHARACTER, mpi_err) -# endif +#endif ! Initialize timers call time_cmfd % reset() From 740b60e72ef4c2d8ae9ffff59e252bcadf894282 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Thu, 5 Dec 2013 20:07:14 -0500 Subject: [PATCH 48/84] sample_external_source now checks to make sure the sampled xyz exists within the problem domain. If it does not, the source site is resampled. --- src/DEPENDENCIES | 1 + src/source.F90 | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/DEPENDENCIES b/src/DEPENDENCIES index b4fe997bbc..579bf26fb1 100644 --- a/src/DEPENDENCIES +++ b/src/DEPENDENCIES @@ -329,6 +329,7 @@ solver_interface.o: vector_header.o source.o: bank_header.o source.o: constants.o source.o: error.o +source.o: geometry.o source.o: geometry_header.o source.o: global.o source.o: math.o diff --git a/src/source.F90 b/src/source.F90 index 2082ad6dda..3d4de842a0 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -3,6 +3,7 @@ module source use bank_header, only: Bank use constants use error, only: fatal_error + use geometry, only: find_cell use geometry_header, only: BASE_UNIVERSE use global use math, only: maxwell_spectrum, watt_spectrum @@ -73,6 +74,8 @@ contains real(8) :: p_max(3) ! maximum coordinates of source real(8) :: a ! Arbitrary parameter 'a' real(8) :: b ! Arbitrary parameter 'b' + logical :: found ! Does the source particle exist within geometry? + type(Particle) :: p ! Temporary particle for using find_cell ! Set weight to one by default site % wgt = ONE @@ -80,11 +83,24 @@ contains ! Sample position select case (external_source % type_space) case (SRC_SPACE_BOX) - ! Coordinates sampled uniformly over a box - p_min = external_source % params_space(1:3) - p_max = external_source % params_space(4:6) - r = (/ (prn(), i = 1,3) /) - site % xyz = p_min + r*(p_max - p_min) + ! Set particle defaults + call p % initialize() + ! Repeat sampling source location until a good site has been found + found = .false. + do while (.not.found) + ! Coordinates sampled uniformly over a box + p_min = external_source % params_space(1:3) + p_max = external_source % params_space(4:6) + r = (/ (prn(), i = 1,3) /) + site % xyz = p_min + r*(p_max - p_min) + + ! Fill p with needed data + p % coord0 % xyz = site % xyz + + ! Now search to see if location exists in geometry + call find_cell(p, found) + + end do case (SRC_SPACE_POINT) ! Point source @@ -146,7 +162,7 @@ contains end subroutine sample_external_source !=============================================================================== -! GET_SOURCE_PARTICLE returns the next source particle +! GET_SOURCE_PARTICLE returns the next source particle !=============================================================================== subroutine get_source_particle(p, index_source) From c3cd394f7a27968b2eeab61427cc4840746df139 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Thu, 5 Dec 2013 20:13:25 -0500 Subject: [PATCH 49/84] Prettied up the comments. --- src/source.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source.F90 b/src/source.F90 index 3d4de842a0..57870206de 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -75,7 +75,7 @@ contains real(8) :: a ! Arbitrary parameter 'a' real(8) :: b ! Arbitrary parameter 'b' logical :: found ! Does the source particle exist within geometry? - type(Particle) :: p ! Temporary particle for using find_cell + type(Particle) :: p ! Temporary particle for using find_cell ! Set weight to one by default site % wgt = ONE From 7cc648036b6771ce1d8645d172ddecf56d8a0b10 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Mon, 9 Dec 2013 09:42:00 -0500 Subject: [PATCH 50/84] Added limit to number of external source resamples. --- src/constants.F90 | 20 ++++++++++++-------- src/source.F90 | 10 +++++++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index e9999c0bdd..564e0baedd 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -20,7 +20,7 @@ module constants FILETYPE_PARTICLE_RESTART = -2 ! ============================================================================ - ! ADJUSTABLE PARAMETERS + ! ADJUSTABLE PARAMETERS ! NOTE: This is the only section of the constants module that should ever be ! adjusted. Modifying constants in other sections may cause the code to fail. @@ -50,6 +50,10 @@ module constants integer, parameter :: MAX_WORD_LEN = 150 integer, parameter :: MAX_FILE_LEN = 255 + ! Maximum number of external source spatial resamples to encounter before an + ! error is thrown. + integer, parameter :: MAX_EXTSRC_RESAMPLES = 10000 + ! ============================================================================ ! PHYSICAL CONSTANTS @@ -110,9 +114,9 @@ module constants ! Surface types integer, parameter :: & - SURF_PX = 1, & ! Plane parallel to x-plane - SURF_PY = 2, & ! Plane parallel to y-plane - SURF_PZ = 3, & ! Plane parallel to z-plane + SURF_PX = 1, & ! Plane parallel to x-plane + SURF_PY = 2, & ! Plane parallel to y-plane + SURF_PZ = 3, & ! Plane parallel to z-plane SURF_PLANE = 4, & ! Arbitrary plane SURF_CYL_X = 5, & ! Cylinder along x-axis SURF_CYL_Y = 6, & ! Cylinder along y-axis @@ -143,7 +147,7 @@ module constants ELECTRON = 3 ! Angular distribution type - integer, parameter :: & + integer, parameter :: & ANGLE_ISOTROPIC = 1, & ! Isotropic angular distribution ANGLE_32_EQUI = 2, & ! 32 equiprobable bins ANGLE_TABULAR = 3 ! Tabular angular distribution @@ -275,7 +279,7 @@ module constants SCORE_KAPPA_FISSION = -12, & ! fission energy production rate SCORE_CURRENT = -13, & ! partial current SCORE_EVENTS = -14 ! number of events - + ! Maximum scattering order supported integer, parameter :: SCATT_ORDER_MAX = 10 character(len=*), parameter :: SCATT_ORDER_MAX_PNSTR = "scatter-p10" @@ -322,7 +326,7 @@ module constants ! Source angular distribution types integer, parameter :: & - SRC_ANGLE_ISOTROPIC = 1, & ! Isotropic angular + SRC_ANGLE_ISOTROPIC = 1, & ! Isotropic angular SRC_ANGLE_MONO = 2, & ! Monodirectional source SRC_ANGLE_TABULAR = 3 ! Tabular distribution @@ -332,7 +336,7 @@ module constants SRC_ENERGY_MAXWELL = 2, & ! Maxwell fission spectrum SRC_ENERGY_WATT = 3, & ! Watt fission spectrum SRC_ENERGY_TABULAR = 4 ! Tabular distribution - + ! ============================================================================ ! MISCELLANEOUS CONSTANTS diff --git a/src/source.F90 b/src/source.F90 index 57870206de..4fcdd0074a 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -76,6 +76,7 @@ contains real(8) :: b ! Arbitrary parameter 'b' logical :: found ! Does the source particle exist within geometry? type(Particle) :: p ! Temporary particle for using find_cell + integer, save :: num_resamples = 0 ! Number of resamples encountered ! Set weight to one by default site % wgt = ONE @@ -99,7 +100,14 @@ contains ! Now search to see if location exists in geometry call find_cell(p, found) - + if (.not. found) then + num_resamples = num_resamples + 1 + if (num_resamples == MAX_EXTSRC_RESAMPLES) then + message = "Maximum number of external source spatial resamples & + &reached!" + call fatal_error() + end if + end if end do case (SRC_SPACE_POINT) From 8884fb9e3f1657b2b5e0c5b4e8e4a3af5adfbe68 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Tue, 10 Dec 2013 05:58:11 -0500 Subject: [PATCH 51/84] Added changes to allow for the entire list of zaids to be checked. --- src/ace.F90 | 30 +++++++----- src/ace_header.F90 | 113 +++++++++++++++++++++++---------------------- src/output.F90 | 66 ++++++++++++++------------ 3 files changed, 112 insertions(+), 97 deletions(-) diff --git a/src/ace.F90 b/src/ace.F90 index d54aa83c28..d5fa012d36 100644 --- a/src/ace.F90 +++ b/src/ace.F90 @@ -115,9 +115,9 @@ contains ! search through the list of nuclides for one which has a matching zaid sab => sab_tables(mat % i_sab_tables(k)) - ! Loop through nuclides and find match + ! Loop through nuclides and find match FIND_NUCLIDE: do j = 1, mat % n_nuclides - if (nuclides(mat % nuclide(j)) % zaid == sab % zaid) then + if (any(sab % zaid == nuclides(mat % nuclide(j)) % zaid)) then mat % i_sab_nuclides(k) = j exit FIND_NUCLIDE end if @@ -161,16 +161,16 @@ contains mat % i_sab_tables(m) = temp_table end do SORT_SAB end if - + ! Deallocate temporary arrays for names of nuclides and S(a,b) tables if (allocated(mat % names)) deallocate(mat % names) if (allocated(mat % sab_names)) deallocate(mat % sab_names) end do MATERIAL_LOOP2 - + ! Avoid some valgrind leak errors call already_read % clear() - + end subroutine read_xs !=============================================================================== @@ -253,7 +253,7 @@ contains end if ! Read more header and NXS and JXS - read(UNIT=in, FMT=100) comment, mat, & + read(UNIT=in, FMT=100) comment, mat, & (zaids(i), awrs(i), i=1,16), NXS, JXS 100 format(A70,A10/4(I7,F11.0)/4(I7,F11.0)/4(I7,F11.0)/4(I7,F11.0)/& ,8I9/8I9/8I9/8I9/8I9/8I9) @@ -277,7 +277,7 @@ contains ACCESS='direct', RECL=record_length) ! Read all header information - read(UNIT=in, REC=location) name, awr, kT, date_, & + read(UNIT=in, REC=location) name, awr, kT, date_, & comment, mat, (zaids(i), awrs(i), i=1,16), NXS, JXS ! determine table length @@ -333,7 +333,15 @@ contains sab % name = name sab % awr = awr sab % kT = kT - sab % zaid = zaids(1) + ! Find sab % n_zaid + do i = 1, 16 + if (zaids(i) == 0) then + sab % n_zaid = i - 1 + exit + end if + end do + allocate(sab % zaid(sab % n_zaid)) + sab % zaid = zaids(1: sab % n_zaid) call read_thermal_data(sab) end select @@ -406,7 +414,7 @@ contains integer :: LNU ! type of nu data (polynomial or tabular) integer :: NC ! number of polynomial coefficients integer :: NR ! number of interpolation regions - integer :: NE ! number of energies + integer :: NE ! number of energies integer :: NPCR ! number of delayed neutron precursor groups integer :: LED ! location of energy distribution locators integer :: LDIS ! location of all energy distributions @@ -809,7 +817,7 @@ contains LED = JXS(10) - ! Loop over all reactions + ! Loop over all reactions do i = 1, NXS(5) rxn => nuc % reactions(i+1) ! skip over elastic scattering rxn % has_energy_dist = .true. @@ -840,7 +848,7 @@ contains integer :: LDIS ! location of all energy distributions integer :: LNW ! location of next energy distribution if multiple - integer :: LAW ! secondary energy distribution law + integer :: LAW ! secondary energy distribution law integer :: NR ! number of interpolation regions integer :: NE ! number of incoming energies integer :: IDAT ! location of first energy distribution for given MT diff --git a/src/ace_header.F90 b/src/ace_header.F90 index 5e2f39a007..26fbf4cbca 100644 --- a/src/ace_header.F90 +++ b/src/ace_header.F90 @@ -16,7 +16,7 @@ module ace_header integer, allocatable :: type(:) ! type of distribution integer, allocatable :: location(:) ! location of each table real(8), allocatable :: data(:) ! angular distribution data - + ! Type-Bound procedures contains procedure :: clear => distangle_clear ! Deallocates DistAngle @@ -35,7 +35,7 @@ module ace_header ! For reactions that may have multiple energy distributions such as (n.2n), ! this pointer allows multiple laws to be stored type(DistEnergy), pointer :: next => null() - + ! Type-Bound procedures contains procedure :: clear => distenergy_clear ! Deallocates DistEnergy @@ -57,7 +57,7 @@ module ace_header logical :: has_energy_dist ! Energy distribution present? type(DistAngle) :: adist ! Secondary angular distribution type(DistEnergy), pointer :: edist => null() ! Secondary energy distribution - + ! Type-Bound procedures contains procedure :: clear => reaction_clear ! Deallocates Reaction @@ -76,7 +76,7 @@ module ace_header logical :: multiply_smooth ! multiply by smooth cross section? real(8), allocatable :: energy(:) ! incident energies real(8), allocatable :: prob(:,:,:) ! actual probabibility tables - + ! Type-Bound procedures contains procedure :: clear => urrdata_clear ! Deallocates UrrData @@ -137,7 +137,7 @@ module ace_header ! Reactions integer :: n_reaction ! # of reactions type(Reaction), pointer :: reactions(:) => null() - + ! Type-Bound procedures contains procedure :: clear => nuclide_clear ! Deallocates Nuclide @@ -147,12 +147,13 @@ module ace_header ! SALPHABETA contains S(a,b) data for thermal neutron scattering, typically off ! of light isotopes such as water, graphite, Be, etc !=============================================================================== - + type SAlphaBeta - character(10) :: name ! name of table, e.g. lwtr.10t - integer :: zaid ! Z and A identifier, e.g. 6012 for Carbon-12 - real(8) :: awr ! weight of nucleus in neutron masses - real(8) :: kT ! temperature in MeV (k*T) + character(10) :: name ! name of table, e.g. lwtr.10t + real(8) :: awr ! weight of nucleus in neutron masses + real(8) :: kT ! temperature in MeV (k*T) + integer :: n_zaid ! Number of valid zaids + integer, allocatable :: zaid(:) ! List of valid Z and A identifiers, e.g. 6012 ! threshold for S(a,b) treatment (usually ~4 eV) real(8) :: threshold_inelastic @@ -164,7 +165,7 @@ module ace_header integer :: n_inelastic_mu ! # of outgoing angles for inelastic integer :: secondary_mode ! secondary mode (equal/skewed) real(8), allocatable :: inelastic_e_in(:) - real(8), allocatable :: inelastic_sigma(:) + real(8), allocatable :: inelastic_sigma(:) real(8), allocatable :: inelastic_e_out(:,:) real(8), allocatable :: inelastic_mu(:,:,:) @@ -240,125 +241,125 @@ module ace_header !=============================================================================== ! DISTANGLE_CLEAR resets and deallocates data in Reaction. -!=============================================================================== - +!=============================================================================== + subroutine distangle_clear(this) - + class(DistAngle), intent(inout) :: this ! The DistAngle object to clear - + if (allocated(this % energy)) & deallocate(this % energy, this % type, this % location, this % data) - - end subroutine distangle_clear + + end subroutine distangle_clear !=============================================================================== ! DISTENERGY_CLEAR resets and deallocates data in DistEnergy. -!=============================================================================== - +!=============================================================================== + recursive subroutine distenergy_clear(this) - + class(DistEnergy), intent(inout) :: this ! The DistEnergy object to clear - + ! Clear p_valid call this % p_valid % clear() - + if (allocated(this % data)) & deallocate(this % data) - + if (associated(this % next)) then ! recursively clear this item call this % next % clear() deallocate(this % next) end if - + end subroutine distenergy_clear - + !=============================================================================== ! REACTION_CLEAR resets and deallocates data in Reaction. -!=============================================================================== - +!=============================================================================== + subroutine reaction_clear(this) - + class(Reaction), intent(inout) :: this ! The Reaction object to clear - + if (allocated(this % sigma)) & deallocate(this % sigma) - + if (associated(this % edist)) then call this % edist % clear() deallocate(this % edist) end if - + call this % adist % clear() - - end subroutine reaction_clear - + + end subroutine reaction_clear + !=============================================================================== ! URRDATA_CLEAR resets and deallocates data in Reaction. -!=============================================================================== - +!=============================================================================== + subroutine urrdata_clear(this) - + class(UrrData), intent(inout) :: this ! The UrrData object to clear - + if (allocated(this % energy)) & deallocate(this % energy, this % prob) - - end subroutine urrdata_clear + + end subroutine urrdata_clear !=============================================================================== ! NUCLIDE_CLEAR resets and deallocates data in Nuclide. -!=============================================================================== - +!=============================================================================== + subroutine nuclide_clear(this) - + class(Nuclide), intent(inout) :: this ! The Nuclide object to clear - + integer :: i ! Loop counter - + if (allocated(this % grid_index)) & deallocate(this % grid_index) - + if (allocated(this % energy)) & deallocate(this % total, this % elastic, this % fission, & this % nu_fission, this % absorption) if (allocated(this % heating)) & deallocate(this % heating) - + if (allocated(this % index_fission)) & deallocate(this % index_fission) - + if (allocated(this % nu_t_data)) & deallocate(this % nu_t_data) - + if (allocated(this % nu_p_data)) & deallocate(this % nu_p_data) - + if (allocated(this % nu_d_data)) & deallocate(this % nu_d_data) - + if (allocated(this % nu_d_precursor_data)) & deallocate(this % nu_d_precursor_data) - + if (associated(this % nu_d_edist)) then do i = 1, size(this % nu_d_edist) call this % nu_d_edist(i) % clear() end do deallocate(this % nu_d_edist) end if - + if (associated(this % urr_data)) then call this % urr_data % clear() deallocate(this % urr_data) end if - + if (associated(this % reactions)) then do i = 1, size(this % reactions) call this % reactions(i) % clear() end do deallocate(this % reactions) end if - - end subroutine nuclide_clear + + end subroutine nuclide_clear end module ace_header diff --git a/src/output.F90 b/src/output.F90 index c60b96bf26..e4fcfecef5 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -126,7 +126,7 @@ contains ! print header based on level select case (header_level) case (1) - write(UNIT=unit_, FMT='(/3(1X,A/))') repeat('=', 75), & + write(UNIT=unit_, FMT='(/3(1X,A/))') repeat('=', 75), & repeat('=', n) // '> ' // trim(line) // ' <' // & repeat('=', m), repeat('=', 75) case (2) @@ -180,7 +180,7 @@ contains end subroutine print_usage !=============================================================================== -! WRITE_MESSAGE displays an informational message to the log file and the +! WRITE_MESSAGE displays an informational message to the log file and the ! standard output stream. !=============================================================================== @@ -215,7 +215,7 @@ contains ! Determine last space in current line last_space = index(message(i_start+1:i_start+line_wrap), & ' ', BACK=.true.) - if (last_space == 0) then + if (last_space == 0) then i_end = min(length + 1, i_start+line_wrap) - 1 write(ou, fmt='(1X,A)') message(i_start+1:i_end) else @@ -1030,6 +1030,7 @@ contains integer :: size_sab ! memory used by S(a,b) table integer :: unit_ ! unit to write to + integer :: i ! Loop counter for parsing through sab % zaid ! set default unit for writing information if (present(unit)) then @@ -1040,7 +1041,12 @@ contains ! Basic S(a,b) table information write(unit_,*) 'S(a,b) Table ' // trim(sab % name) - write(unit_,*) ' zaid = ' // trim(to_str(sab % zaid)) + write(unit_,'(A)',advance="no") ' zaids = ' + do i = 1, sab % n_zaid - 1 + ! This could use some fancy handling for if the line becomes too long + write(unit_,'(A)',advance="no") trim(to_str(sab % zaid(sab % n_zaid))) // ', ' + end do + write(unit_,*) trim(to_str(sab % zaid(sab % n_zaid))) write(unit_,*) ' awr = ' // trim(to_str(sab % awr)) write(unit_,*) ' kT = ' // trim(to_str(sab % kT)) @@ -1242,7 +1248,7 @@ contains end select end if write(UNIT=ou, FMT=*) - + write(UNIT=ou, FMT='(2X,A9,3X)', ADVANCE='NO') "=========" write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" if (entropy_on) write(UNIT=ou, FMT='(A8,3X)', ADVANCE='NO') "========" @@ -1272,7 +1278,7 @@ contains if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & entropy(overall_gen) - if (overall_gen - n_inactive*gen_per_batch > 1) then + if (overall_gen - n_inactive*gen_per_batch > 1) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5," +/-",F8.5)', ADVANCE='NO') & keff, keff_std end if @@ -1300,7 +1306,7 @@ contains entropy(current_batch*gen_per_batch) ! write out accumulated k-effective if after first active batch - if (overall_gen - n_inactive*gen_per_batch > 1) then + if (overall_gen - n_inactive*gen_per_batch > 1) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5," +/-",F8.5)', ADVANCE='NO') & keff, keff_std else @@ -1310,7 +1316,7 @@ contains ! write out cmfd keff if it is active and other display info if (cmfd_on) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - cmfd % k_cmfd(current_batch) + cmfd % k_cmfd(current_batch) select case(trim(cmfd_display)) case('entropy') write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & @@ -1349,7 +1355,7 @@ contains ! Plot id write(ou,100) "Plot ID:", trim(to_str(pl % id)) - + ! Plot type if (pl % type == PLOT_TYPE_SLICE) then write(ou,100) "Plot Type:", "Slice" @@ -1387,7 +1393,7 @@ contains trim(to_str(pl % pixels(2))) else if (pl % type == PLOT_TYPE_VOXEL) then write(ou,100) "Voxels:", trim(to_str(pl % pixels(1))) // " " // & - trim(to_str(pl % pixels(2))) // " " // trim(to_str(pl % pixels(3))) + trim(to_str(pl % pixels(2))) // " " // trim(to_str(pl % pixels(3))) end if write(ou,*) @@ -1539,7 +1545,7 @@ contains write(ou,100) 'Cell ID','No. Overlap Checks' - do i = 1, n_cells + do i = 1, n_cells write(ou,101) cells(i) % id, overlap_check_cnt(i) if (overlap_check_cnt(i) < 10) num_sparse = num_sparse + 1 end do @@ -1573,7 +1579,7 @@ contains integer :: k ! loop index for scoring bins integer :: n ! loop index for nuclides integer :: l ! loop index for user scores - integer :: type ! type of tally filter + integer :: type ! type of tally filter integer :: indent ! number of spaces to preceed output integer :: filter_index ! index in results array for filters integer :: score_index ! scoring bin index @@ -1749,13 +1755,13 @@ contains score_name = 'P' // trim(to_str(t % scatt_order(k))) // & ' Scattering Moment' end if - write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & repeat(" ", indent), score_name, & to_str(t % results(score_index,filter_index) % sum), & trim(to_str(t % results(score_index,filter_index) % sum_sq)) else if (t % score_bins(k) == SCORE_SCATTER_PN) then score_name = "Scattering Rate" - write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & repeat(" ", indent), score_name, & to_str(t % results(score_index,filter_index) % sum), & trim(to_str(t % results(score_index,filter_index) % sum_sq)) @@ -1763,7 +1769,7 @@ contains score_index = score_index + 1 score_name = 'P' // trim(to_str(n_order)) // & ' Scattering Moment' - write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & repeat(" ", indent), score_name, & to_str(t % results(score_index,filter_index) % sum), & trim(to_str(t % results(score_index,filter_index) % sum_sq)) @@ -1775,7 +1781,7 @@ contains else score_name = score_names(abs(t % score_bins(k))) end if - write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(1X,2A,1X,A,"+/- ",A)') & repeat(" ", indent), score_name, & to_str(t % results(score_index,filter_index) % sum), & trim(to_str(t % results(score_index,filter_index) % sum_sq)) @@ -1813,8 +1819,8 @@ contains integer :: i_filter_ein ! index for incoming energy filter integer :: i_filter_surf ! index for surface filter integer :: n ! number of incoming energy bins - integer :: len1 ! length of string - integer :: len2 ! length of string + integer :: len1 ! length of string + integer :: len2 ! length of string integer :: filter_index ! index in results array for filters logical :: print_ebin ! should incoming energy bin be displayed? character(MAX_LINE_LEN) :: string @@ -1864,14 +1870,14 @@ contains mesh_indices_to_bin(m, (/ i-1, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Left", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_RIGHT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Left", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) @@ -1881,14 +1887,14 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_RIGHT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Right", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_RIGHT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Right", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) @@ -1898,14 +1904,14 @@ contains mesh_indices_to_bin(m, (/ i, j-1, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Back", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_FRONT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Back", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) @@ -1915,14 +1921,14 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_FRONT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Front", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_FRONT filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Front", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) @@ -1932,14 +1938,14 @@ contains mesh_indices_to_bin(m, (/ i, j, k-1 /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Bottom", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_TOP filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Bottom", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) @@ -1949,14 +1955,14 @@ contains mesh_indices_to_bin(m, (/ i, j, k /) + 1, .true.) matching_bins(i_filter_surf) = IN_TOP filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Incoming Current from Top", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) matching_bins(i_filter_surf) = OUT_TOP filter_index = sum((matching_bins(1:t%n_filters) - 1) * t % stride) + 1 - write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & + write(UNIT=UNIT_TALLY, FMT='(5X,A,T35,A,"+/- ",A)') & "Outgoing Current to Top", & to_str(t % results(1,filter_index) % sum), & trim(to_str(t % results(1,filter_index) % sum_sq)) From ee1edca155ab185f664aa6805ee81c671af0ac53 Mon Sep 17 00:00:00 2001 From: Bryan Herman Date: Tue, 10 Dec 2013 09:06:40 -0500 Subject: [PATCH 52/84] fixed source_write option in input reading --- src/input_xml.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 05f4ae0c56..8cd93f0e10 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -635,8 +635,8 @@ contains if (check_for_node(node_sp, "source_write")) then call get_node_value(node_sp, "source_write", temp_str) call lower_case(temp_str) - if (trim(temp_str) == 'true' .or. & - trim(temp_str) == '1') source_separate = .true. + if (trim(temp_str) == 'false' .or. & + trim(temp_str) == '0') source_write = .false. end if else ! If no tag was present, by default write state point at From b6395729a4ba6e33b321fd08635d8f73baeb307f Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Tue, 10 Dec 2013 11:51:40 -0500 Subject: [PATCH 53/84] Added better printing of the new zaid list for cross_sections.out; now it will go to a new line before hitting the 80-char limit. --- src/output.F90 | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index e4fcfecef5..1507aa4cd5 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -1028,9 +1028,10 @@ contains type(SAlphaBeta), pointer :: sab integer, optional :: unit - integer :: size_sab ! memory used by S(a,b) table - integer :: unit_ ! unit to write to - integer :: i ! Loop counter for parsing through sab % zaid + integer :: size_sab ! memory used by S(a,b) table + integer :: unit_ ! unit to write to + integer :: i ! Loop counter for parsing through sab % zaid + integer :: char_count ! Counter for the number of characters on a line ! set default unit for writing information if (present(unit)) then @@ -1041,12 +1042,30 @@ contains ! Basic S(a,b) table information write(unit_,*) 'S(a,b) Table ' // trim(sab % name) - write(unit_,'(A)',advance="no") ' zaids = ' - do i = 1, sab % n_zaid - 1 - ! This could use some fancy handling for if the line becomes too long - write(unit_,'(A)',advance="no") trim(to_str(sab % zaid(sab % n_zaid))) // ', ' + write(unit_,'(A)',advance="no") ' zaids = ' + ! Initialize the counter based on the above string + char_count = 11 + do i = 1, sab % n_zaid + ! Deal with a line thats too long + if (char_count >= 73) then ! 73 = 80 - (5 ZAID chars + 1 space + 1 comma) + ! End the line + write(unit_,*) "" + ! Add 11 leading blanks + write(unit_,'(A)', advance="no") " " + ! reset the counter to 11 + char_count = 11 + end if + if (i < sab % n_zaid) then + ! Include a comma + write(unit_,'(A)',advance="no") trim(to_str(sab % zaid(i))) // ", " + char_count = char_count + len(trim(to_str(sab % zaid(i)))) + 2 + else + ! Don't include a comma, since we are all done + write(unit_,'(A)',advance="no") trim(to_str(sab % zaid(i))) + end if + end do - write(unit_,*) trim(to_str(sab % zaid(sab % n_zaid))) + write(unit_,*) "" ! Move to next line write(unit_,*) ' awr = ' // trim(to_str(sab % awr)) write(unit_,*) ' kT = ' // trim(to_str(sab % kT)) From 0a05d4594aeb57a0179048c3bb4ccdd00a990b31 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 10 Dec 2013 16:53:11 -0500 Subject: [PATCH 54/84] Fix bug in doppler broadening (not used). --- src/doppler.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doppler.F90 b/src/doppler.F90 index 12fb34bbfe..e888eb2b18 100644 --- a/src/doppler.F90 +++ b/src/doppler.F90 @@ -87,7 +87,7 @@ contains ! ======================================================================= ! EXTEND CROSS SECTION TO 0 ASSUMING 1/V SHAPE - if (k == 1 .and. a >= 4.0) then + if (k == 1 .and. a >= -4.0) then ! Since x = 0, this implies that a = -y F_b = F_a a = -y @@ -101,7 +101,7 @@ contains end if ! ======================================================================= - ! EVALUATE FIRST TERM FROM x(k) - y = 0 to -4 + ! EVALUATE FIRST TERM FROM x(k) - y = 0 to 4 k = i b = ZERO From 1bdf693d798b7b4597d65bee5753296bb8829823 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 10 Dec 2013 22:33:59 -0500 Subject: [PATCH 55/84] Fix use statements in several cmfd modules. --- src/cmfd_data.F90 | 2 +- src/cmfd_execute.F90 | 12 +++++++----- src/cmfd_power_solver.F90 | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index d0e80aaa90..8dd0fa35ff 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -934,7 +934,7 @@ contains subroutine compute_effective_downscatter() use constants, only: ZERO, CMFD_NOACCEL - use global, only: cmfd, cmfd_downscatter + use global, only: cmfd integer :: nx ! number of mesh cells in x direction integer :: ny ! number of mesh cells in y direction diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 index 7a35a12144..6234d9fcf9 100644 --- a/src/cmfd_execute.F90 +++ b/src/cmfd_execute.F90 @@ -117,9 +117,9 @@ contains subroutine process_cmfd_options() +#ifdef PETSC use global, only: cmfd_snes_monitor, cmfd_ksp_monitor, mpi_err -#ifdef PETSC ! Check for snes monitor if (cmfd_snes_monitor) call PetscOptionsSetValue("-snes_monitor", & "stdout", mpi_err) @@ -138,9 +138,10 @@ contains subroutine calc_fission_source() use constants, only: CMFD_NOACCEL, ZERO, TWO - use global, only: cmfd, cmfd_coremap, master, mpi_err, entropy_on, & - current_batch + use global, only: cmfd, cmfd_coremap, master, entropy_on, current_batch + #ifdef MPI + use global, only: mpi_err use mpi #endif @@ -261,13 +262,14 @@ contains use constants, only: ZERO, ONE use error, only: warning, fatal_error - use global, only: n_particles, meshes, source_bank, work, & - n_user_meshes, message, cmfd, master, mpi_err + use global, only: meshes, source_bank, work, n_user_meshes, message, & + cmfd, master use mesh_header, only: StructuredMesh use mesh, only: count_bank_sites, get_mesh_indices use search, only: binary_search #ifdef MPI + use global, only: mpi_err use mpi #endif diff --git a/src/cmfd_power_solver.F90 b/src/cmfd_power_solver.F90 index c1309b72cd..f2761d0a1f 100644 --- a/src/cmfd_power_solver.F90 +++ b/src/cmfd_power_solver.F90 @@ -102,7 +102,9 @@ contains subroutine init_data(adjoint) use constants, only: ONE, ZERO +#ifdef PETSC use global, only: cmfd_write_matrices +#endif logical, intent(in) :: adjoint ! adjoint calcualtion From ca4ec3b3cd1e2dd6d1a07e461b2f5ef504262fc3 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Wed, 11 Dec 2013 09:47:49 -0500 Subject: [PATCH 56/84] Added ZrH as the top reflector of the test_salphabeta_multiple test in order to include an S(a,b) library which can be applied to multiple problems in our test suite. --- tests/test_salphabeta_multiple/geometry.xml | 6 ++++-- tests/test_salphabeta_multiple/materials.xml | 18 +++++++++++++++++- .../test_salphabeta_multiple/results_true.dat | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/test_salphabeta_multiple/geometry.xml b/tests/test_salphabeta_multiple/geometry.xml index 2d427d8cb3..63f69f7438 100644 --- a/tests/test_salphabeta_multiple/geometry.xml +++ b/tests/test_salphabeta_multiple/geometry.xml @@ -44,9 +44,11 @@ - + - + + + diff --git a/tests/test_salphabeta_multiple/materials.xml b/tests/test_salphabeta_multiple/materials.xml index 97ff56c05f..6e975bde97 100644 --- a/tests/test_salphabeta_multiple/materials.xml +++ b/tests/test_salphabeta_multiple/materials.xml @@ -1,7 +1,8 @@ - + 70c @@ -111,4 +112,19 @@ + + + + + + + + + + + + + + + diff --git a/tests/test_salphabeta_multiple/results_true.dat b/tests/test_salphabeta_multiple/results_true.dat index f26a309aae..bbd61cdb53 100644 --- a/tests/test_salphabeta_multiple/results_true.dat +++ b/tests/test_salphabeta_multiple/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.013747E+00 3.067630E-02 +9.761880E-01 9.170415E-03 From ec805b8b6f2439c37d70c0c1b9fe955068fd4172 Mon Sep 17 00:00:00 2001 From: Bryan Herman Date: Fri, 13 Dec 2013 11:40:04 -0500 Subject: [PATCH 57/84] XML only looks at first generation of children for tagname --- src/xml/dom/FoX_dom.F90 | 1 + src/xml/dom/m_dom_dom.F90 | 161 ++++++++++++++++++++++++++++++++++++++ src/xml_interface.F90 | 10 +-- 3 files changed, 167 insertions(+), 5 deletions(-) diff --git a/src/xml/dom/FoX_dom.F90 b/src/xml/dom/FoX_dom.F90 index 6e1ac36a7b..4e2fc1e9cc 100644 --- a/src/xml/dom/FoX_dom.F90 +++ b/src/xml/dom/FoX_dom.F90 @@ -74,6 +74,7 @@ module FoX_dom public :: createAttribute public :: createEntityReference public :: getElementsByTagName + public :: getChildrenByTagName public :: getElementById public :: importNode diff --git a/src/xml/dom/m_dom_dom.F90 b/src/xml/dom/m_dom_dom.F90 index 01f6265b07..ec5b788d9b 100644 --- a/src/xml/dom/m_dom_dom.F90 +++ b/src/xml/dom/m_dom_dom.F90 @@ -349,6 +349,7 @@ module m_dom_dom public :: createEntityReference public :: createEmptyEntityReference public :: getElementsByTagName + public :: getChildrenByTagName public :: importNode public :: createElementNS public :: createAttributeNS @@ -6908,6 +6909,166 @@ endif end function getElementsByTagName + function getChildrenByTagName(doc, tagName, name, ex)result(list) + type(DOMException), intent(out), optional :: ex + type(Node), pointer :: doc + character(len=*), intent(in), optional :: tagName, name + type(NodeList), pointer :: list + + type(NodeListPtr), pointer :: nll(:), temp_nll(:) + type(Node), pointer :: arg, this, treeroot + logical :: doneChildren, doneAttributes, allElements + integer :: i, i_tree + + if (.not.associated(doc)) then + if (getFoX_checks().or.FoX_NODE_IS_NULL<200) then + call throw_exception(FoX_NODE_IS_NULL, "getElementsByTagName", ex) + if (present(ex)) then + if (inException(ex)) then + return + endif + endif +endif + + endif + + if (doc%nodeType==DOCUMENT_NODE) then + if (present(name).or..not.present(tagName)) then + if (getFoX_checks().or.FoX_INVALID_NODE<200) then + call throw_exception(FoX_INVALID_NODE, "getElementsByTagName", ex) + if (present(ex)) then + if (inException(ex)) then + return + endif + endif +endif + + endif + elseif (doc%nodeType==ELEMENT_NODE) then + if (present(name).or..not.present(tagName)) then + if (getFoX_checks().or.FoX_INVALID_NODE<200) then + call throw_exception(FoX_INVALID_NODE, "getElementsByTagName", ex) + if (present(ex)) then + if (inException(ex)) then + return + endif + endif +endif + + endif + else + if (getFoX_checks().or.FoX_INVALID_NODE<200) then + call throw_exception(FoX_INVALID_NODE, "getElementsByTagName", ex) + if (present(ex)) then + if (inException(ex)) then + return + endif + endif +endif + + endif + + if (doc%nodeType==DOCUMENT_NODE) then + arg => getDocumentElement(doc) + else + arg => doc + endif + + allocate(list) + allocate(list%nodes(0)) + list%element => doc + if (present(name)) list%nodeName => vs_str_alloc(name) + if (present(tagName)) list%nodeName => vs_str_alloc(tagName) + + allElements = (str_vs(list%nodeName)=="*") + + if (doc%nodeType==DOCUMENT_NODE) then + nll => doc%docExtras%nodelists + elseif (doc%nodeType==ELEMENT_NODE) then + nll => doc%ownerDocument%docExtras%nodelists + endif + allocate(temp_nll(size(nll)+1)) + do i = 1, size(nll) + temp_nll(i)%this => nll(i)%this + enddo + temp_nll(i)%this => list + deallocate(nll) + if (doc%nodeType==DOCUMENT_NODE) then + doc%docExtras%nodelists => temp_nll + elseif (doc%nodeType==ELEMENT_NODE) then + doc%ownerDocument%docExtras%nodelists => temp_nll + endif + + treeroot => arg + + i_tree = 0 + doneChildren = .false. + doneAttributes = .false. + this => treeroot + do + if (.not.doneChildren.and..not.(getNodeType(this)==ELEMENT_NODE.and.doneAttributes)) then + if (this%nodeType==ELEMENT_NODE) then + if ((allElements .or. str_vs(this%nodeName)==tagName) & + .and..not.(getNodeType(doc)==ELEMENT_NODE.and.associated(this, arg))) & + call append(list, this) + doneAttributes = .true. + endif + + else + if (getNodeType(this)==ELEMENT_NODE.and..not.doneChildren) then + doneAttributes = .true. + else + + endif + endif + + + if (.not.doneChildren) then + if (getNodeType(this)==ELEMENT_NODE.and..not.doneAttributes) then + if (getLength(getAttributes(this))>0) then + this => item(getAttributes(this), 0) + else + doneAttributes = .true. + endif + elseif (hasChildNodes(this) .and. .not. associated(getParentNode(this), treeroot)) then + this => getFirstChild(this) + doneChildren = .false. + doneAttributes = .false. + else + doneChildren = .true. + doneAttributes = .false. + endif + + else ! if doneChildren + + if (associated(this, treeroot)) exit + if (getNodeType(this)==ATTRIBUTE_NODE) then + if (i_tree item(getAttributes(getOwnerElement(this)), i_tree) + doneChildren = .false. + else + i_tree= 0 + this => getOwnerElement(this) + doneAttributes = .true. + doneChildren = .false. + endif + elseif (associated(getNextSibling(this))) then + + this => getNextSibling(this) + doneChildren = .false. + doneAttributes = .false. + else + this => getParentNode(this) + endif + endif + + enddo + + + + end function getChildrenByTagName + function importNode(doc , arg, deep , ex)result(np) type(DOMException), intent(out), optional :: ex type(Node), pointer :: doc diff --git a/src/xml_interface.F90 b/src/xml_interface.F90 index 4752c08734..870b4288be 100644 --- a/src/xml_interface.F90 +++ b/src/xml_interface.F90 @@ -72,7 +72,7 @@ contains ! node name. This should only be used for checking a single occurance of a ! sub-element node. To check for sub-element nodes that repeat, use ! get_node_list and get_list_size. This is to minimize number of calls -! to getElementsByTagName. +! to getChildrenByTagName. !=============================================================================== function check_for_node(ptr, node_name) result(found) @@ -94,7 +94,7 @@ contains if (associated(temp_ptr)) return ! Check for a sub-element - elem_list => getElementsByTagName(ptr, trim(node_name)) + elem_list => getChildrenByTagName(ptr, trim(node_name)) ! Get the length of the list if (getLength(elem_list) == 0) then @@ -124,7 +124,7 @@ contains found_ = .false. ! Check for a sub-element - elem_list => getElementsByTagName(in_ptr, trim(node_name)) + elem_list => getChildrenByTagName(in_ptr, trim(node_name)) ! Get the length of the list if (getLength(elem_list) == 0) return @@ -149,7 +149,7 @@ contains type(NodeList), pointer, intent(out) :: out_ptr ! Check for a sub-element - out_ptr => getElementsByTagName(in_ptr, trim(node_name)) + out_ptr => getChildrenByTagName(in_ptr, trim(node_name)) end subroutine get_node_list @@ -525,7 +525,7 @@ contains if (associated(out_ptr)) return ! Check for a sub-element - elem_list => getElementsByTagName(in_ptr, trim(node_name)) + elem_list => getChildrenByTagName(in_ptr, trim(node_name)) ! Get the length of the list if (getLength(elem_list) == 0) then From fecc07709aa8095e05e1e351d9b3196bc00f66cd Mon Sep 17 00:00:00 2001 From: Bryan Herman Date: Fri, 13 Dec 2013 11:41:11 -0500 Subject: [PATCH 58/84] added flag for compiling XML on BGQ --- src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 03c370e07a..05fa826f44 100644 --- a/src/Makefile +++ b/src/Makefile @@ -218,7 +218,7 @@ endif ifeq ($(MACHINE),bluegene) F90 = /bgsys/drivers/ppcfloor/comm/xl/bin/mpixlf2003 - F90FLAGS = -WF,-DNO_F2008,-DMPI -O3 + F90FLAGS = -WF,-DNO_F2008,-DMPI,-DRESTRICTED_ASSOCIATED_BUG -O3 LDFLAGS = -lmpich.cnkf90 endif @@ -233,7 +233,7 @@ endif ifeq ($(MACHINE),bluegeneq) F90 = mpixlf2003 - F90FLAGS = -WF,-DNO_F2008,-DMPI -O5 + F90FLAGS = -WF,-DNO_F2008,-DMPI,-DRESTRICTED_ASSOCIATED_BUG -O5 endif #=============================================================================== From 549bdad2e371e6afb78353b7dec8914bacbdaee8 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sat, 14 Dec 2013 10:18:35 -0500 Subject: [PATCH 59/84] Added ability to read in continuous secondary energy distributions for inelastic S(a,b) collisions. Still have to implement new collision physics in physics.F90 --- src/ace.F90 | 83 ++++++++++++++++++++++++++++++++-------------- src/ace_header.F90 | 24 ++++++++++++-- src/constants.F90 | 3 +- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/ace.F90 b/src/ace.F90 index d5fa012d36..ff8582470d 100644 --- a/src/ace.F90 +++ b/src/ace.F90 @@ -1195,15 +1195,10 @@ contains integer :: NE_out ! number of outgoing energies integer :: NMU ! number of outgoing angles integer :: JXS4 ! location of elastic energy table + real(8), allocatable :: LOCC(:) ! Location of inelastic data - ! read secondary energy mode for inelastic scattering and check + ! read secondary energy mode for inelastic scattering table % secondary_mode = NXS(7) - if (table % secondary_mode /= SAB_SECONDARY_EQUAL .and. & - table % secondary_mode /= SAB_SECONDARY_SKEWED) then - message = "Unsupported secondary mode on S(a,b) table " // & - trim(adjustl(table % name)) // ": " // to_str(table % secondary_mode) - call fatal_error() - end if ! read number of inelastic energies and allocate arrays NE_in = int(XSS(JXS(1))) @@ -1221,29 +1216,67 @@ contains ! allocate space for outgoing energy/angle for inelastic ! scattering - NE_out = NXS(4) - NMU = NXS(3) + 1 - table % n_inelastic_e_out = NE_out - table % n_inelastic_mu = NMU - allocate(table % inelastic_e_out(NE_out, NE_in)) - allocate(table % inelastic_mu(NMU, NE_out, NE_in)) + if (table % secondary_mode == SAB_SECONDARY_EQUAL .or. & + table % secondary_mode == SAB_SECONDARY_SKEWED) then + NMU = NXS(3) + 1 + table % n_inelastic_mu = NMU + NE_out = NXS(4) + table % n_inelastic_e_out = NE_out + allocate(table % inelastic_e_out(NE_out, NE_in)) + allocate(table % inelastic_mu(NMU, NE_out, NE_in)) + else if (table % secondary_mode == SAB_SECONDARY_CONT) then + NMU = NXS(3) - 1 + table % n_inelastic_mu = NMU + allocate(table % inelastic_data(NE_in)) + allocate(LOCC(NE_in)) + ! NE_out will be determined later + end if ! read outgoing energy/angle distribution for inelastic scattering - lc = JXS(3) - 1 - do i = 1, NE_in - do j = 1, NE_out - ! read outgoing energy - table % inelastic_e_out(j,i) = XSS(lc + 1) + if (table % secondary_mode == SAB_SECONDARY_EQUAL .or. & + table % secondary_mode == SAB_SECONDARY_SKEWED) then + lc = JXS(3) - 1 + do i = 1, NE_in + do j = 1, NE_out + ! read outgoing energy + table % inelastic_e_out(j,i) = XSS(lc + 1) - ! read outgoing angles for this outgoing energy - do k = 1, NMU - table % inelastic_mu(k,j,i) = XSS(lc + 1 + k) + ! read outgoing angles for this outgoing energy + do k = 1, NMU + table % inelastic_mu(k,j,i) = XSS(lc + 1 + k) + end do + + ! advance pointer + lc = lc + 1 + NMU end do - - ! advance pointer - lc = lc + 1 + NMU end do - end do + else if (table % secondary_mode == SAB_SECONDARY_CONT) then + ! Get the location pointers to each Ein's DistEnergySAB data + LOCC = get_real(NE_in) + ! Get the number of outgoing energies and allocate space accordingly + do i = 1, NE_in + NE_out = XSS(XSS_index + i - 1) + table % inelastic_data(i) % n_e_out = NE_out + allocate(table % inelastic_data(i) % e_out (NE_out)) + allocate(table % inelastic_data(i) % e_out_pdf (NE_out)) + allocate(table % inelastic_data(i) % e_out_cdf (NE_out)) + allocate(table % inelastic_data(i) % mu (NMU, NE_out)) + end do + + ! Now we can fill the inelastic_data(i) attributes + do i = 1, NE_in + XSS_index = LOCC(i) + NE_out = table % inelastic_data(i) % n_e_out + do j = 1, NE_out + table % inelastic_data(i) % e_out(j) = XSS(XSS_index + 1) + table % inelastic_data(i) % e_out_pdf(j) = XSS(XSS_index + 2) + table % inelastic_data(i) % e_out_cdf(j) = XSS(XSS_index + 3) + table % inelastic_data(i) % mu(:, j) = & + XSS(XSS_index + 4: XSS_index + 4 + NMU - 1) + XSS_index = XSS_index + 4 + NMU - 1 + end do + end do + end if ! read number of elastic energies and allocate arrays JXS4 = JXS(4) diff --git a/src/ace_header.F90 b/src/ace_header.F90 index 26fbf4cbca..e5bf1bb3a4 100644 --- a/src/ace_header.F90 +++ b/src/ace_header.F90 @@ -32,7 +32,7 @@ module ace_header type(Tab1) :: p_valid ! probability of law validity real(8), allocatable :: data(:) ! energy distribution data - ! For reactions that may have multiple energy distributions such as (n.2n), + ! For reactions that may have multiple energy distributions such as (n,2n), ! this pointer allows multiple laws to be stored type(DistEnergy), pointer :: next => null() @@ -143,6 +143,20 @@ module ace_header procedure :: clear => nuclide_clear ! Deallocates Nuclide end type Nuclide +!=============================================================================== +! DISTENERGYSAB contains the secondary energy/angle distributions for inelastic +! thermal scattering collisions which utilize a continuous secondary energy +! representation. +!=============================================================================== + + type DistEnergySab + integer :: n_e_out + real(8), allocatable :: e_out(:) + real(8), allocatable :: e_out_pdf(:) + real(8), allocatable :: e_out_cdf(:) + real(8), allocatable :: mu(:,:) + end type DistEnergySab + !=============================================================================== ! SALPHABETA contains S(a,b) data for thermal neutron scattering, typically off ! of light isotopes such as water, graphite, Be, etc @@ -163,11 +177,17 @@ module ace_header integer :: n_inelastic_e_in ! # of incoming E for inelastic integer :: n_inelastic_e_out ! # of outgoing E for inelastic integer :: n_inelastic_mu ! # of outgoing angles for inelastic - integer :: secondary_mode ! secondary mode (equal/skewed) + integer :: secondary_mode ! secondary mode (equal/skewed/continuous) real(8), allocatable :: inelastic_e_in(:) real(8), allocatable :: inelastic_sigma(:) + ! The following are used only if secondary_mode is 0 or 1 real(8), allocatable :: inelastic_e_out(:,:) real(8), allocatable :: inelastic_mu(:,:,:) + ! The following is used only if secondary_mode is 3 + ! The different implementation is necessary because the continuous + ! representation has a variable number of outgoing energy points for each + ! incoming energy + type(DistEnergySab), allocatable :: inelastic_data(:) ! One for each Ein ! Elastic scattering data integer :: elastic_mode ! elastic mode (discrete/exact) diff --git a/src/constants.F90 b/src/constants.F90 index 564e0baedd..6fea8cbc16 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -155,7 +155,8 @@ module constants ! Secondary energy mode for S(a,b) inelastic scattering integer, parameter :: & SAB_SECONDARY_EQUAL = 0, & ! Equally-likely outgoing energy bins - SAB_SECONDARY_SKEWED = 1 ! Skewed outgoing energy bins + SAB_SECONDARY_SKEWED = 1, & ! Skewed outgoing energy bins + SAB_SECONDARY_CONT = 2 ! Continuous, linear-linear interpolation ! Elastic mode for S(a,b) elastic scattering integer, parameter :: & From 8f5ce5ed5ca6ff4913c7e8b80def71c673fd98a2 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 15 Dec 2013 13:45:18 -0500 Subject: [PATCH 60/84] Corrected an error message in physics.F90:sample_energy, which referred to Law 44 instead of the correct Law 61. --- src/physics.F90 | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/physics.F90 b/src/physics.F90 index fb1f891943..5771377c86 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -47,7 +47,7 @@ contains if (verbosity >= 10 .or. trace) then message = " " // trim(reaction_name(p % event_MT)) // " with " // & trim(adjustl(nuclides(p % event_nuclide) % name)) // & - ". Energy = " // trim(to_str(p % E * 1e6_8)) // " eV." + ". Energy = " // trim(to_str(p % E * 1e6_8)) // " eV." call write_message() end if @@ -158,7 +158,7 @@ contains call fatal_error() end if - ! Find atom density + ! Find atom density i_nuclide = mat % nuclide(i) atom_density = mat % atom_density(i) @@ -209,7 +209,7 @@ contains i_reaction = nuc % index_fission(1) return end if - + ! Get grid index and interpolatoin factor and sample fission cdf i_grid = micro_xs(i_nuclide) % index_grid f = micro_xs(i_nuclide) % interp_factor @@ -226,7 +226,7 @@ contains if (i_grid < rxn % threshold) cycle ! add to cumulative probability - prob = prob + ((ONE - f)*rxn%sigma(i_grid - rxn%threshold + 1) & + prob = prob + ((ONE - f)*rxn%sigma(i_grid - rxn%threshold + 1) & + f*(rxn%sigma(i_grid - rxn%threshold + 2))) ! Create fission bank sites if fission occus @@ -382,7 +382,7 @@ contains if (i_grid < rxn % threshold) cycle ! add to cumulative probability - prob = prob + ((ONE - f)*rxn%sigma(i_grid - rxn%threshold + 1) & + prob = prob + ((ONE - f)*rxn%sigma(i_grid - rxn%threshold + 1) & + f*(rxn%sigma(i_grid - rxn%threshold + 2))) end do @@ -513,7 +513,7 @@ contains f = ZERO else i = binary_search(sab % elastic_e_in, sab % n_elastic_e_in, E) - f = (E - sab%elastic_e_in(i)) / & + f = (E - sab%elastic_e_in(i)) / & (sab%elastic_e_in(i+1) - sab%elastic_e_in(i)) end if @@ -563,7 +563,7 @@ contains f = ZERO else i = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E) - f = (E - sab%inelastic_e_in(i)) / & + f = (E - sab%inelastic_e_in(i)) / & (sab%inelastic_e_in(i+1) - sab%inelastic_e_in(i)) end if @@ -974,7 +974,7 @@ contains E_cm = E ! determine outgoing energy in lab - E = E_cm + (E_in + TWO * mu * (A+ONE) * sqrt(E_in * E_cm)) & + E = E_cm + (E_in + TWO * mu * (A+ONE) * sqrt(E_in * E_cm)) & / ((A+ONE)*(A+ONE)) ! determine outgoing angle in lab @@ -1037,7 +1037,7 @@ contains r = ONE else i = binary_search(rxn % adist % energy, n, E) - r = (E - rxn % adist % energy(i)) / & + r = (E - rxn % adist % energy(i)) / & (rxn % adist % energy(i+1) - rxn % adist % energy(i)) end if @@ -1166,7 +1166,7 @@ contains end if end function rotate_angle - + !=============================================================================== ! SAMPLE_ENERGY samples an outgoing energy distribution, either for a secondary ! neutron from a collision or for a prompt/delayed fission neutron @@ -1322,7 +1322,7 @@ contains ! ======================================================================= ! CONTINUOUS TABULAR DISTRIBUTION - ! read number of interpolation regions and incoming energies + ! read number of interpolation regions and incoming energies NR = int(edist % data(1)) NE = int(edist % data(2 + 2*NR)) if (NR == 1) then @@ -1348,7 +1348,7 @@ contains r = ONE else i = binary_search(edist % data(lc+1:lc+NE), NE, E_in) - r = (E_in - edist%data(lc+i)) / & + r = (E_in - edist%data(lc+i)) / & (edist%data(lc+i+1) - edist%data(lc+i)) end if @@ -1452,7 +1452,7 @@ contains ! ======================================================================= ! MAXWELL FISSION SPECTRUM - ! read number of interpolation regions and incoming energies + ! read number of interpolation regions and incoming energies NR = int(edist % data(1)) NE = int(edist % data(2 + 2*NR)) @@ -1484,7 +1484,7 @@ contains ! ======================================================================= ! EVAPORATION SPECTRUM - ! read number of interpolation regions and incoming energies + ! read number of interpolation regions and incoming energies NR = int(edist % data(1)) NE = int(edist % data(2 + 2*NR)) @@ -1565,7 +1565,7 @@ contains call fatal_error() end if - ! read number of interpolation regions and incoming energies + ! read number of interpolation regions and incoming energies NR = int(edist % data(1)) NE = int(edist % data(2 + 2*NR)) if (NR > 0) then @@ -1587,7 +1587,7 @@ contains r = ONE else i = binary_search(edist % data(lc+1:lc+NE), NE, E_in) - r = (E_in - edist%data(lc+i)) / & + r = (E_in - edist%data(lc+i)) / & (edist%data(lc+i+1) - edist%data(lc+i)) end if @@ -1714,11 +1714,11 @@ contains if (.not. present(mu_out)) then ! call write_particle_restart() - message = "Law 44 called without giving mu_out as argument." + message = "Law 61 called without giving mu_out as argument." call fatal_error() end if - ! read number of interpolation regions and incoming energies + ! read number of interpolation regions and incoming energies NR = int(edist % data(1)) NE = int(edist % data(2 + 2*NR)) if (NR > 0) then @@ -1740,7 +1740,7 @@ contains r = ONE else i = binary_search(edist % data(lc+1:lc+NE), NE, E_in) - r = (E_in - edist%data(lc+i)) / & + r = (E_in - edist%data(lc+i)) / & (edist%data(lc+i+1) - edist%data(lc+i)) end if From 1dd13d80641407ca8217635f3a8ecbdb93908e16 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 15 Dec 2013 14:30:29 -0500 Subject: [PATCH 61/84] Added code to physics.F90%sab_scatter() to sample continuous secondary energy distributions; still have to test. --- src/physics.F90 | 190 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 146 insertions(+), 44 deletions(-) diff --git a/src/physics.F90 b/src/physics.F90 index 5771377c86..372d67bd3e 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -490,13 +490,24 @@ contains integer :: k ! outgoing cosine bin integer :: n_energy_out ! number of outgoing energy bins real(8) :: f ! interpolation factor - real(8) :: r ! used for skewed sampling + real(8) :: r ! used for skewed sampling & continuous real(8) :: E_ij ! outgoing energy j for E_in(i) real(8) :: E_i1j ! outgoing energy j for E_in(i+1) real(8) :: mu_ijk ! outgoing cosine k for E_in(i) and E_out(j) real(8) :: mu_i1jk ! outgoing cosine k for E_in(i+1) and E_out(j) real(8) :: prob ! probability for sampling Bragg edge type(SAlphaBeta), pointer, save :: sab => null() + ! Following are needed only for SAB_SECONDARY_CONT scattering + integer :: l ! sampled incoming E bin (is i or i + 1) + real(8) :: E_i_1, E_i_J ! endpoints on outgoing grid i + real(8) :: E_i1_1, E_i1_J ! endpoints on outgoing grid i+1 + real(8) :: E_1, E_J ! endpoints interpolated between i and i+1 + real(8) :: E_l_j, E_l_j1 ! adjacent E on outgoing grid l + real(8) :: p_l_j, p_l_j1 ! adjacent p on outgoing grid l + real(8) :: c_j, c_j1 ! cumulative probability + real(8) :: frac ! interpolation factor on outgoing energy + real(8) :: r1 ! RNG for outgoing energy + !$omp threadprivate(sab) ! Get pointer to S(a,b) table @@ -554,8 +565,7 @@ contains ! Outgoing energy is same as incoming energy -- no need to do anything else - ! Determine number of outgoing energy and angle bins - n_energy_out = sab % n_inelastic_e_out + ! Perform inelastic calculations ! Get index and interpolation factor for inelastic grid if (E < sab % inelastic_e_in(1)) then @@ -570,54 +580,146 @@ contains ! Now that we have an incoming energy bin, we need to determine the ! outgoing energy bin. This will depend on the "secondary energy ! mode". If the mode is 0, then the outgoing energy bin is chosen from a - ! set of equally-likely bins. However, if the mode is 1, then the first + ! set of equally-likely bins. If the mode is 1, then the first ! two and last two bins are skewed to have lower probabilities than the ! other bins (0.1 for the first and last bins and 0.4 for the second and - ! second to last bins, relative to a normal bin probability of 1) + ! second to last bins, relative to a normal bin probability of 1). + ! Finally, if the mode is 2, then a continuous distribution (with + ! accompanying PDF and CDF is utilized) - if (sab % secondary_mode == SAB_SECONDARY_EQUAL) then - ! All bins equally likely - j = 1 + int(prn() * n_energy_out) - elseif (sab % secondary_mode == SAB_SECONDARY_SKEWED) then - r = prn() * (n_energy_out - 3) - if (r > ONE) then - ! equally likely N-4 middle bins - j = int(r) + 2 - elseif (r > 0.6) then - ! second to last bin has relative probability of 0.4 - j = n_energy_out - 1 - elseif (r > 0.5) then - ! last bin has relative probability of 0.1 - j = n_energy_out - elseif (r > 0.1) then - ! second bin has relative probability of 0.4 - j = 2 - else - ! first bin has relative probability of 0.1 - j = 1 + if ((sab % secondary_mode == SAB_SECONDARY_EQUAL) .or. & + (sab % secondary_mode == SAB_SECONDARY_SKEWED)) then + if (sab % secondary_mode == SAB_SECONDARY_EQUAL) then + ! All bins equally likely + + j = 1 + int(prn() * sab % n_inelastic_e_out) + elseif (sab % secondary_mode == SAB_SECONDARY_SKEWED) then + ! Distribution skewed away from edge points + + ! Determine number of outgoing energy and angle bins + n_energy_out = sab % n_inelastic_e_out + + r = prn() * (n_energy_out - 3) + if (r > ONE) then + ! equally likely N-4 middle bins + j = int(r) + 2 + elseif (r > 0.6) then + ! second to last bin has relative probability of 0.4 + j = n_energy_out - 1 + elseif (r > 0.5) then + ! last bin has relative probability of 0.1 + j = n_energy_out + elseif (r > 0.1) then + ! second bin has relative probability of 0.4 + j = 2 + else + ! first bin has relative probability of 0.1 + j = 1 + end if end if + + ! Determine outgoing energy corresponding to E_in(i) and E_in(i+1) + E_ij = sab % inelastic_e_out(j,i) + E_i1j = sab % inelastic_e_out(j,i+1) + + ! Outgoing energy + E = (1 - f)*E_ij + f*E_i1j + + ! Sample outgoing cosine bin + k = 1 + int(prn() * sab % n_inelastic_mu) + + ! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1) + mu_ijk = sab % inelastic_mu(k,j,i) + mu_i1jk = sab % inelastic_mu(k,j,i+1) + + ! Cosine of angle between incoming and outgoing neutron + mu = (1 - f)*mu_ijk + f*mu_i1jk + + else if (sab % secondary_mode == SAB_SECONDARY_CONT) then + ! Continuous secondary energy - this is to be similar to + ! Law 61 interpolation on outgoing energy + + ! Sample between ith and (i+1)th bin + r = prn() + if (f > r) then + l = i + 1 + else + l = i + end if + + ! Determine endpoints on grid i + n_energy_out = sab % inelastic_data(i) % n_e_out + E_i_1 = sab % inelastic_data(i) % e_out(1) + E_i_J = sab % inelastic_data(i) % e_out(n_energy_out) + + ! Determine endpoints on grid i + 1 + n_energy_out = sab % inelastic_data(i + 1) % n_e_out + E_i1_1 = sab % inelastic_data(i + 1) % e_out(1) + E_i1_J = sab % inelastic_data(i + 1) % e_out(n_energy_out) + + E_1 = E_i_1 + f * (E_i1_1 - E_i_1) + E_J = E_i_J + f * (E_i1_J - E_i_J) + + ! Determine outgoing energy bin + ! (First reset n_energy_out to the right value) + n_energy_out = sab % inelastic_data(l) % n_e_out + r1 = prn() + c_j = sab % inelastic_data(l) % e_out_cdf(1) + do j = 1, n_energy_out - 1 + c_j1 = sab % inelastic_data(l) % e_out_cdf(j + 1) + if (r1 < c_j1) exit + c_j = c_j1 + end do + + ! check to make sure k is <= n_energy_out - 1 + j = min(j, n_energy_out - 1) + + ! Get the data to interpolate between + E_l_j = sab % inelastic_data(l) % e_out(j) + p_l_j = sab % inelastic_data(l) % e_out_pdf(j) + + ! Next part assumes linear-linear interpolation in standard + E_l_j1 = sab % inelastic_data(l) % e_out(j + 1) + p_l_j1 = sab % inelastic_data(l) % e_out_pdf(j + 1) + + ! Find secondary energy (variable E) + frac = (p_l_j1 - p_l_j) / (E_l_j1 - E_l_j) + if (frac == ZERO) then + E = E_l_j + (r1 - c_j) / p_l_j + else + E = E_l_j + (sqrt(max(ZERO, p_l_j * p_l_j + & + TWO * frac * (r1 - c_j))) - p_l_j) / frac + end if + + ! Now interpolate between incident energy bins i and i + 1 + if (l == i) then + E = E_1 + (E - E_i_1) * (E_J - E_1) / (E_i_J - E_i_1) + else + E = E_1 + (E - E_i1_1) * (E_J - E_1) / (E_i1_J - E_i1_1) + end if + + ! Find correlated angular distribution for closest outgoing energy bin + if (r1 - c_j < c_j1 - r1) then + j = j + else + j = j + 1 + end if + + ! Sample outgoing cosine bin + k = 1 + int(prn() * sab % n_inelastic_mu) + + ! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1) + mu_ijk = sab % inelastic_data(i) % mu(k, j) + mu_i1jk = sab % inelastic_data(i) % mu(k, j) + + ! Cosine of angle between incoming and outgoing neutron + mu = (ONE - f) * mu_ijk + f * mu_i1jk + else message = "Invalid secondary energy mode on S(a,b) table " // & trim(sab % name) - end if - - ! Determine outgoing energy corresponding to E_in(i) and E_in(i+1) - E_ij = sab % inelastic_e_out(j,i) - E_i1j = sab % inelastic_e_out(j,i+1) - - ! Outgoing energy - E = (1 - f)*E_ij + f*E_i1j - - ! Sample outgoing cosine bin - k = 1 + int(prn() * sab % n_inelastic_mu) - - ! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1) - mu_ijk = sab % inelastic_mu(k,j,i) - mu_i1jk = sab % inelastic_mu(k,j,i+1) - - ! Cosine of angle between incoming and outgoing neutron - mu = (1 - f)*mu_ijk + f*mu_i1jk - end if + end if ! (inelastic secondary energy treatment) + end if ! (elastic or inelastic) ! change direction of particle uvw = rotate_angle(uvw, mu) From 56d3d382826d9a3df29e60f51d6e5abcdf211f98 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 15 Dec 2013 15:58:55 -0500 Subject: [PATCH 62/84] Modified code after some testing to not perform interpolation on Ein and Eout when finding mu; instead the closest distribution is chosen, as is done for file 4 angular distributions in sample_angle. --- src/physics.F90 | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/physics.F90 b/src/physics.F90 index 372d67bd3e..2d3cfeb39f 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -698,7 +698,7 @@ contains E = E_1 + (E - E_i1_1) * (E_J - E_1) / (E_i1_J - E_i1_1) end if - ! Find correlated angular distribution for closest outgoing energy bin + ! Find angular distribution for closest outgoing energy bin if (r1 - c_j < c_j1 - r1) then j = j else @@ -708,12 +708,9 @@ contains ! Sample outgoing cosine bin k = 1 + int(prn() * sab % n_inelastic_mu) - ! Determine outgoing cosine corresponding to E_in(i) and E_in(i+1) - mu_ijk = sab % inelastic_data(i) % mu(k, j) - mu_i1jk = sab % inelastic_data(i) % mu(k, j) - - ! Cosine of angle between incoming and outgoing neutron - mu = (ONE - f) * mu_ijk + f * mu_i1jk + ! Will use mu from the randomly chosen incoming and closest outgoing + ! energy bins + mu = sab % inelastic_data(l) % mu(k, j) else message = "Invalid secondary energy mode on S(a,b) table " // & From b64644407e5c04aaefe865ceb3a43be288e2b72c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Dec 2013 21:19:42 -0500 Subject: [PATCH 63/84] Two small fixes in installation instructions. --- docs/source/usersguide/install.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index c05782e71d..164b0151be 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -78,7 +78,7 @@ Prerequisites ./configure --prefix=/opt/hdf5/1.8.11-gnu --enable-fortran \ --enable-fortran2003 --enable-parallel - You may omit '--enable-parallel' if you want to compile HDF5_ in serial. + You may omit ``--enable-parallel`` if you want to compile HDF5_ in serial. * PETSc_ for CMFD acceleration @@ -93,7 +93,7 @@ Prerequisites --with-fortran-datatypes The BLAS/LAPACK library is not required to be downloaded and can be linked - explicitly (e.g., Intel MLK library). + explicitly (e.g., Intel MKL library). * git_ version control software for obtaining source code From 96999e00c6eb2ab1172c590268bc79155fea9d84 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Dec 2013 11:18:58 -0500 Subject: [PATCH 64/84] Make sure uvw is initialized when sampling source position. --- src/source.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/source.F90 b/src/source.F90 index 4fcdd0074a..231a23cc5c 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -97,6 +97,7 @@ contains ! Fill p with needed data p % coord0 % xyz = site % xyz + p % coord0 % uvw = [ ONE, ZERO, ZERO ] ! Now search to see if location exists in geometry call find_cell(p, found) From d28750c34fdec59b3ce955865299eafe980cba16 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Dec 2013 11:48:47 -0500 Subject: [PATCH 65/84] Fix bug in convert_xsdir.py. --- src/utils/convert_xsdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/convert_xsdir.py b/src/utils/convert_xsdir.py index fa29f46eca..32315f1ed5 100755 --- a/src/utils/convert_xsdir.py +++ b/src/utils/convert_xsdir.py @@ -71,7 +71,7 @@ class Xsdir(object): # Handle continuation lines while words[-1] == '+': extraWords = self.f.readline().split() - words = words + extraWords + words = words[:-1] + extraWords assert len(words) >= 7 # Create XsdirTable object and add to line From d0028d388dc68b61efa520ac8ddc937efa8eb6a3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Dec 2013 17:10:31 -0500 Subject: [PATCH 66/84] Allow mixed ascii/binary cross_sections.xml. --- src/input_xml.F90 | 14 +++++++++++++- src/relaxng/cross_sections.rnc | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 8cd93f0e10..63fb2163e3 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2938,7 +2938,19 @@ contains end if ! set filetype, record length, and number of entries - listing % filetype = filetype + if (check_for_node(node_ace, "filetype")) then + temp_str = '' + call get_node_value(node_ace, "filetype", temp_str) + if (temp_str == 'ascii') then + listing % filetype = ASCII + else if (temp_str == 'binary') then + listing % filetype = BINARY + end if + else + listing % filetype = filetype + end if + + ! Set record length and entries for binary files if (filetype == BINARY) then listing % recl = recl listing % entries = entries diff --git a/src/relaxng/cross_sections.rnc b/src/relaxng/cross_sections.rnc index e82bb986fb..75c0ea29c2 100644 --- a/src/relaxng/cross_sections.rnc +++ b/src/relaxng/cross_sections.rnc @@ -10,7 +10,9 @@ element cross_sections { (element temperature { xsd:double } | attribute temperature { xsd:double }) & (element path { xsd:string { maxLength = "255" } } | attribute path { xsd:string { maxLength = "255" } }) & - (element location { xsd:int } | attribute location { xsd:int })? + (element location { xsd:int } | attribute location { xsd:int })? & + (element filetype { ( "ascii" | "binary" ) } | + attribute filetype { ( "ascii" | "binary" ) })? }* & element directory { xsd:string { maxLength = "255" } }? & From 4a8157f28081eb9b470e408bda3db1cb65b71dff Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Fri, 20 Dec 2013 07:10:38 -0500 Subject: [PATCH 67/84] Included explicit integer casting in the reading of thermal data. --- src/ace.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ace.F90 b/src/ace.F90 index ff8582470d..aa6de5ab64 100644 --- a/src/ace.F90 +++ b/src/ace.F90 @@ -1195,7 +1195,7 @@ contains integer :: NE_out ! number of outgoing energies integer :: NMU ! number of outgoing angles integer :: JXS4 ! location of elastic energy table - real(8), allocatable :: LOCC(:) ! Location of inelastic data + integer(8), allocatable :: LOCC(:) ! Location of inelastic data ! read secondary energy mode for inelastic scattering table % secondary_mode = NXS(7) @@ -1252,10 +1252,10 @@ contains end do else if (table % secondary_mode == SAB_SECONDARY_CONT) then ! Get the location pointers to each Ein's DistEnergySAB data - LOCC = get_real(NE_in) + LOCC = get_int(NE_in) ! Get the number of outgoing energies and allocate space accordingly do i = 1, NE_in - NE_out = XSS(XSS_index + i - 1) + NE_out = int(XSS(XSS_index + i - 1)) table % inelastic_data(i) % n_e_out = NE_out allocate(table % inelastic_data(i) % e_out (NE_out)) allocate(table % inelastic_data(i) % e_out_pdf (NE_out)) From 4bda7f2054fe8174a6eacf72d367499307227a89 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Fri, 20 Dec 2013 09:44:09 -0500 Subject: [PATCH 68/84] Updated theory manual to incorporate the new thermal scattering sampling technique --- docs/source/methods/physics.rst | 64 ++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/docs/source/methods/physics.rst b/docs/source/methods/physics.rst index c79144dfe3..fc82dc1a98 100644 --- a/docs/source/methods/physics.rst +++ b/docs/source/methods/physics.rst @@ -790,6 +790,7 @@ outgoing angle is \mu = \frac{1}{A} \ln \left ( \xi_4 e^A + (1 - \xi_4) e^{-A} \right ). +.. _ace-law-61: ACE Law 61 - Correlated Energy and Angle Distribution +++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -952,7 +953,7 @@ as v_n \bar{\sigma} (v_n, T) = \int d\mathbf{v}_T v_r \sigma(v_r) M (\mathbf{v}_T) - + where :math:`v_n` is the magnitude of the velocity of the neutron, :math:`\bar{\sigma}` is an effective cross section, :math:`T` is the temperature of the target material, :math:`\mathbf{v}_T` is the velocity of the target @@ -1321,7 +1322,7 @@ given analytically by \mu = 1 - \frac{E_i}{E} -where :math:`E_i` is the energy of the Bragg edge that scattered the neutron. +where :math:`E_i` is the energy of the Bragg edge that scattered the neutron. Outgoing Angle for Incoherent Elastic Scattering ------------------------------------------------ @@ -1348,18 +1349,24 @@ where the interpolation factor is defined as Outgoing Energy and Angle for Inelastic Scattering -------------------------------------------------- -On each |sab| table, there is a correlated angle-energy secondary distribution -for neutron thermal inelastic scattering. While the documentation for the ACE -format implies that there are a series of equiprobable outgoing energies, the -outgoing energies may have non-uniform probability distribution. In particular, -if the thermal data were processed with :math:`iwt = 0` in NJOY, then the first -and last outgoing energies have a relative probability of 1, the second and -second to last energies have a relative probability of 4, and all other energies -have a relative probability of 10. The procedure to determine the outgoing +Each |sab| table provides a correlated angle-energy secondary distribution for +neutron thermal inelastic scattering. There are three representations used +in the ACE thermal scattering data: equiprobable discrete outgoing +energies, non-uniform yet still discrete outgoing energies, and continuous +outgoing energies with corresponding probability and cumulative distribution +functions provided in tabular format. These three representations all +represent the angular distribution in a common format, using a series of +discrete equiprobable outgoing cosines. + +Equi-Probable Outgoing Energies ++++++++++++++++++++++++++++++++ + +If the thermal data was processed with :math:`iwt = 1` in NJOY, then the +outgoing energy spectra is represented in the ACE data as a set of discrete and +equiprobable outgoing energies. The procedure to determine the outgoing energy and angle is as such. First, the interpolation factor is determined from -equation :eq:`sab-interpolation-factor`. Then, an outgoing energy bin is sampled -either from a uniform distribution or from the aforementioned skewed -distribution. The outgoing energy is then interpolated between values +equation :eq:`sab-interpolation-factor`. Then, an outgoing energy bin is +sampled from ths uniform distribution and then interpolated between values corresponding to neighboring incoming energies: .. math:: @@ -1380,6 +1387,37 @@ uniformly and then the final cosine is interpolated on the incoming energy grid: where :math:`\mu_{i,j,k}` is the k-th outgoing cosine corresponding to the j-th outgoing energy and the i-th incoming energy. +Skewed Equi-Probable Outgoing Energies +++++++++++++++++++++++++++++++++++++++ + +If the thermal data was processed with :math:`iwt=0` in NJOY, then the +outgoing energy spectra is represented in the ACE data according to the +following: the first and last outgoing energies have a relative probability of +1, the second and second-to-last energies have a relative probability of 4, and +all other energies have a relative probability of 10. The procedure to +determine the outgoing energy and angle is similar to the method discussed +above, except that the sampled probability distribution is now skewed +accordingly. + +Continuous Outgoing Energies +++++++++++++++++++++++++++++ + +If the thermal data was processed with :math:`iwt=2` in NJOY, then the +outgoing energy spectra is represented by a continuous outgoing energy spectra +in tabular form with linear-linear interpolation. The sampling of the outgoing +energy portion of this format is very similar to :ref:`ACE Law 61`, +but the sampling of the correlated angle is performed as it was in the other +two representations discussed in this sub-section. In the Law 61 algorithm, +we found an interpolation factor :math:`f`, statistically sampled an incoming +energy bin :math:`\ell`, and sampled an outgoing energy bin :math:`j` based on +the tabulated cumulative distribution function. Once the outgoing energy has +been determined with equation :eq:`ace-law-4-energy`, we then need to decide +which angular distribution data to use. Like the linear-linear interpolation +case in Law 61, the angular distribution closest to the sampled value of the +cumulative distribution function for the outgoing energy is utilized. The +actual algorithm utilized to sample the outgoing angle is shown in equation +:eq:`inelastic-angle`. + .. _probability_tables: ---------------------------------------------- From 402df187428ebf1f985b6ac73cd2252ff0ca434d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 22 Dec 2013 20:41:49 -0500 Subject: [PATCH 69/84] Fixed a typo in theory documentation. --- docs/source/methods/physics.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/methods/physics.rst b/docs/source/methods/physics.rst index fc82dc1a98..54dc913455 100644 --- a/docs/source/methods/physics.rst +++ b/docs/source/methods/physics.rst @@ -1363,10 +1363,10 @@ Equi-Probable Outgoing Energies If the thermal data was processed with :math:`iwt = 1` in NJOY, then the outgoing energy spectra is represented in the ACE data as a set of discrete and -equiprobable outgoing energies. The procedure to determine the outgoing -energy and angle is as such. First, the interpolation factor is determined from +equiprobable outgoing energies. The procedure to determine the outgoing energy +and angle is as such. First, the interpolation factor is determined from equation :eq:`sab-interpolation-factor`. Then, an outgoing energy bin is -sampled from ths uniform distribution and then interpolated between values +sampled from a uniform distribution and then interpolated between values corresponding to neighboring incoming energies: .. math:: From 5db982406428e40e09154b530b0de7ee7b7ed00b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 22 Dec 2013 20:57:36 -0500 Subject: [PATCH 70/84] Fix particle track bug when using Intel compiler. --- src/input_xml.F90 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 63fb2163e3..3ce3c9d0d4 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -474,7 +474,8 @@ contains allocate(temp_int_array(n_tracks)) call get_node_array(doc, "track", temp_int_array) - ! Reshape into track_identifiers -- note automatic array allocation + ! Reshape into track_identifiers + allocate(track_identifiers(3, n_tracks/3)) track_identifiers = reshape(temp_int_array, [3, n_tracks/3]) end if From 73c542e2016e7dc7290973671bf2151e9518d88c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 26 Dec 2013 22:44:03 -0500 Subject: [PATCH 71/84] Display number of OpenMP threads under title banner. --- src/output.F90 | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 1507aa4cd5..677c35396c 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -31,6 +31,10 @@ contains subroutine title() +#ifdef OPENMP + use omp_lib +#endif + write(UNIT=OUTPUT_UNIT, FMT='(/11(A/))') & ' .d88888b. 888b d888 .d8888b.', & ' d88P" "Y88b 8888b d8888 d88P Y88b', & @@ -46,25 +50,31 @@ contains ! Write version information write(UNIT=OUTPUT_UNIT, FMT=*) & - ' Copyright: 2011-2013 Massachusetts Institute of Technology' + ' Copyright: 2011-2013 Massachusetts Institute of Technology' write(UNIT=OUTPUT_UNIT, FMT=*) & - ' License: http://mit-crpg.github.io/openmc/license.html' - write(UNIT=OUTPUT_UNIT, FMT='(6X,"Version:",7X,I1,".",I1,".",I1)') & + ' License: http://mit-crpg.github.io/openmc/license.html' + write(UNIT=OUTPUT_UNIT, FMT='(6X,"Version:",8X,I1,".",I1,".",I1)') & VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE #ifdef GIT_SHA1 - write(UNIT=OUTPUT_UNIT, FMT='(6X,"Git SHA1:",6X,A)') GIT_SHA1 + write(UNIT=OUTPUT_UNIT, FMT='(6X,"Git SHA1:",7X,A)') GIT_SHA1 #endif ! Write the date and time - write(UNIT=OUTPUT_UNIT, FMT='(6X,"Date/Time:",5X,A)') & + write(UNIT=OUTPUT_UNIT, FMT='(6X,"Date/Time:",6X,A)') & time_stamp() #ifdef MPI ! Write number of processors - write(UNIT=OUTPUT_UNIT, FMT='(6X,"MPI Processes:",1X,A)') & + write(UNIT=OUTPUT_UNIT, FMT='(6X,"MPI Processes:",2X,A)') & trim(to_str(n_procs)) #endif +#ifdef OPENMP + ! Write number of OpenMP threads + write(UNIT=OUTPUT_UNIT, FMT='(6X,"OpenMP Threads:",1X,A)') & + trim(to_str(omp_get_max_threads())) +#endif + end subroutine title !=============================================================================== From f3f024bbc60f499b5ca38f4e4f156f1f2df1a95f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 26 Dec 2013 23:17:03 -0500 Subject: [PATCH 72/84] Avoid passing temporary arrays to write_data. --- src/hdf5_summary.F90 | 5 +++-- src/state_point.F90 | 6 +++--- src/track_output.F90 | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/hdf5_summary.F90 b/src/hdf5_summary.F90 index 915e6af070..c9e367200c 100644 --- a/src/hdf5_summary.F90 +++ b/src/hdf5_summary.F90 @@ -100,6 +100,7 @@ contains integer :: i, j, k, m integer :: n_x, n_y, n_z + integer :: length(3) integer, allocatable :: lattice_universes(:,:,:) type(Cell), pointer :: c => null() type(Surface), pointer :: s => null() @@ -312,8 +313,8 @@ contains end do end do end do - call su % write_data(lattice_universes, "universes", & - length=(/n_x, n_y, n_z/), & + length = [n_x, n_y, n_z] + call su % write_data(lattice_universes, "universes", length=length, & group="geometry/lattices/lattice " // trim(to_str(lat % id))) deallocate(lattice_universes) diff --git a/src/state_point.F90 b/src/state_point.F90 index 3793923bb2..e13cd8f486 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -464,6 +464,7 @@ contains character(19) :: current_time integer :: i integer :: j + integer :: length(4) integer :: int_array(3) integer, allocatable :: temp_array(:) real(8) :: real_array(3) @@ -542,10 +543,9 @@ contains call sp % read_data(cmfd % indices, "indicies", length=4, group="cmfd") call sp % read_data(cmfd % k_cmfd, "k_cmfd", length=restart_batch, & group="cmfd") + length = cmfd % indices([4,1,2,3]) call sp % read_data(cmfd % cmfd_src, "cmfd_src", & - length=(/cmfd % indices(4), cmfd % indices(1), & - cmfd % indices(2), cmfd % indices(3)/), & - group="cmfd") + length=length, group="cmfd") call sp % read_data(cmfd % entropy, "cmfd_entropy", & length=restart_batch, group="cmfd") call sp % read_data(cmfd % balance, "cmfd_balance", & diff --git a/src/track_output.F90 b/src/track_output.F90 index 021cbb8ca6..79a2809066 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -56,6 +56,7 @@ contains subroutine finalize_particle_track(p) type(Particle), intent(in) :: p + integer :: length(2) character(MAX_FILE_LEN) :: fname type(BinaryOutput) :: binout @@ -69,7 +70,8 @@ contains // '.binary' #endif call binout % file_create(fname) - call binout % write_data(coords, 'coordinates', length=(/3, n_tracks/)) + length = [3, n_tracks] + call binout % write_data(coords, 'coordinates', length=length) call binout % file_close() deallocate(coords) end subroutine finalize_particle_track From 9a0e50574cfb2c8b7220c4ede790e65d61bcf776 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 27 Dec 2013 16:33:25 -0500 Subject: [PATCH 73/84] Fix bug causing CMFD to report incorrect results when using threads. --- src/cmfd_input.F90 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index 120db5f649..6f259f9497 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -577,7 +577,9 @@ contains end do ! Put cmfd tallies into active tally array and turn tallies on +!$omp parallel call setup_active_cmfdtallies() +!$omp end parallel tallies_on = .true. end subroutine create_cmfd_tally From 415e5da764cdc6d05dc43402b958c9e74d359048 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 2 Jan 2014 15:44:48 -0500 Subject: [PATCH 74/84] Added description of UFS method in documentation. --- docs/source/methods/eigenvalue.rst | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/source/methods/eigenvalue.rst b/docs/source/methods/eigenvalue.rst index a5ba0bf459..fe99ba22ec 100644 --- a/docs/source/methods/eigenvalue.rst +++ b/docs/source/methods/eigenvalue.rst @@ -108,6 +108,40 @@ at plots of :math:`k_{eff}` and the Shannon entropy. A number of methods have been proposed (see e.g. [Romano]_, [Ueki]_), but each of these is not without problems. +--------------------------- +Uniform Fission Site Method +--------------------------- + +Generally speaking, the variance of a Monte Carlo tally will be inversely +proportional to the number of events that score to the tally. In a reactor +problem, this implies that regions with low relative power density will have +higher variance that regions with high relative power density. One method to +circumvent the uneven distribution of relative errors is the uniform fission +site (UFS) method introduced by [Sutton]_. In this method, the portion of the +problem containing fissionable material is subdivided into a number of cells +(typically using a structured mesh). Rather than producing + +.. math:: + + m = \frac{w}{k} \frac{\nu\Sigma_f}{\Sigma_t} + +fission sites at each collision where :math:`w` is the weight of the neutron, +:math:`k` is the previous-generation estimate of the neutron multiplication +factor, :math:`\nu\Sigma_f` is the neutron production cross section, and +:math:`\Sigma_t` is the total cross section, in the UFS method we produce + +.. math:: + + m_{UFS} = \frac{w}{k} \frac{\nu\Sigma_f}{\Sigma_t} \frac{v_i}{s_i} + +fission sites at each collision where :math:`v_i` is the fraction of the total +volume occupied by cell :math:`i` and :math:`s_i` is the fraction of the fission +source contained in cell :math:`i`. To ensure that no bias is introduced, the +weight of each fission site stored in the fission bank is :math:`s_i/v_i` rather +than unity. By ensuring that the expected number of fission sites in each mesh +cell is constant, the collision density across all cells, and hence the variance +of tallies, is more uniform than it would be otherwise. + .. _Shannon entropy: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-06-3737_entropy.pdf .. [Lieberoth] J. Lieberoth, "A Monte Carlo Technique to Solve the Static @@ -119,5 +153,9 @@ problems. *Proc. International Conference on Mathematics, Computational Methods, and Reactor Physics*, Saratoga Springs, New York (2009). +.. [Sutton] Daniel J. Kelly, Thomas M. Sutton, and Stephen C. Wilson, "MC21 + Analysis of the Nuclear Energy Agency Monte Carlo Performance Benchmark + Problem," *Proc. PHYSOR 2012*, Knoxville, Tennessee, Apr. 15--20 (2012). + .. [Ueki] Taro Ueki, "On-the-Fly Judgments of Monte Carlo Fission Source Convergence," *Trans. Am. Nucl. Soc.*, **98**, 512 (2008). From 0f628689132f503060c06f1419d7c3c487d0f9a3 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Fri, 3 Jan 2014 14:20:54 -0500 Subject: [PATCH 75/84] Made some revisions to plot_mesh_tally and statepoint for usability purposes. --- src/utils/plot_mesh_tally.py | 340 ++++++++++++++++++++--------------- src/utils/statepoint.py | 33 ++-- 2 files changed, 208 insertions(+), 165 deletions(-) diff --git a/src/utils/plot_mesh_tally.py b/src/utils/plot_mesh_tally.py index 04b6b4592a..c70b291653 100755 --- a/src/utils/plot_mesh_tally.py +++ b/src/utils/plot_mesh_tally.py @@ -16,123 +16,161 @@ from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as Naviga import numpy as np class AppForm(QMainWindow): - def __init__(self, parent=None): + def __init__(self, argv, parent=None): QMainWindow.__init__(self, parent) # Read data from source or leakage fraction file - self.get_file_data() - self.main_frame = QWidget() - self.setCentralWidget(self.main_frame) - - # Create the Figure, Canvas, and Axes - self.dpi = 100 - self.fig = Figure((5.0, 15.0), dpi=self.dpi) - self.canvas = FigureCanvas(self.fig) - self.canvas.setParent(self.main_frame) - self.axes = self.fig.add_subplot(111) - - # Create the navigation toolbar, tied to the canvas - self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame) + self.all_good = False + while not self.all_good: + if len(argv) > 1: + cl_file = str(argv[1]) + else: + cl_file = None + self.get_file_data(cl_file) + # Check that there are any mesh tallies at all + if len(self.tally_ids) != 0: + self.all_good = True + else: + # if there are not, the user will be given the choice to choose + # another file (but only if using interactive chooser) + if cl_file is None: + choice = QMessageBox.critical(None, "Invalid StatePoint File", + "File Does Not Contain Mesh " + + "Tallies!" + + "\nSelect Another File Or Quit", + QMessageBox.Retry, + QMessageBox.Abort) + if choice == QMessageBox.Abort: + self.all_good = False + break + else: + print("Invalid StatePoint File; File Does Not Contain " + + "Mesh Tallies!") + self.all_good = False + break - # Grid layout at bottom - self.grid = QGridLayout() - # Overall layout - self.vbox = QVBoxLayout() - self.vbox.addWidget(self.canvas) - self.vbox.addWidget(self.mpl_toolbar) - self.vbox.addLayout(self.grid) - self.main_frame.setLayout(self.vbox) + if self.all_good: + # Set maximum colorbar value by maximum tally data value + self.maxvalue = self.datafile.tallies[0].results.max() - # Tally selections - label_tally = QLabel("Tally:") - self.tally = QComboBox() - self.tally.addItems([(str(i + 1)) for i in range(self.n_tallies)]) - self.connect(self.tally, SIGNAL('activated(int)'), - self._update) - self.connect(self.tally, SIGNAL('activated(int)'), - self.populate_boxes) - self.connect(self.tally, SIGNAL('activated(int)'), - self.on_draw) + self.main_frame = QWidget() + self.setCentralWidget(self.main_frame) - # Planar basis - label_basis = QLabel("Basis:") - self.basis = QComboBox() - self.basis.addItems(['xy', 'yz', 'xz']) + # Create the Figure, Canvas, and Axes + self.dpi = 100 + self.fig = Figure((5.0, 15.0), dpi=self.dpi) + self.canvas = FigureCanvas(self.fig) + self.canvas.setParent(self.main_frame) + self.axes = self.fig.add_subplot(111) - # Update window when 'Basis' selection is changed - self.connect(self.basis, SIGNAL('activated(int)'), - self._update) - self.connect(self.basis, SIGNAL('activated(int)'), - self.populate_boxes) - self.connect(self.basis, SIGNAL('activated(int)'), - self.on_draw) + # Create the navigation toolbar, tied to the canvas + self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame) - # Axial level within selected basis - label_axial_level = QLabel("Axial Level:") - self.axial_level = QComboBox() - self.connect(self.axial_level, SIGNAL('activated(int)'), - self.on_draw) - - # Add Option to plot mean or uncertainty - label_mean = QLabel("Mean or Uncertainty:") - self.mean = QComboBox() - self.mean.addItems(['Mean','Absolute Uncertainty', - 'Relative Uncertainty']) - - # Update window when mean selection is changed - self.connect(self.mean, SIGNAL('activated(int)'), - self.on_draw) - + # Grid layout at bottom + self.grid = QGridLayout() - self.label_filters = QLabel("Filter options:") + # Overall layout + self.vbox = QVBoxLayout() + self.vbox.addWidget(self.canvas) + self.vbox.addWidget(self.mpl_toolbar) + self.vbox.addLayout(self.grid) + self.main_frame.setLayout(self.vbox) - # Labels for all possible filters - self.labels = {'cell': 'Cell: ', 'cellborn': 'Cell born: ', - 'surface': 'Surface: ', 'material': 'Material', - 'universe': 'Universe: ', 'energyin': 'Energy in: ', - 'energyout': 'Energy out: '} - - # Empty reusable labels - self.qlabels = {} - for j in range(8): - self.nextLabel = QLabel - self.qlabels[j] = self.nextLabel - - # Reusable comboboxes labelled with filter names - self.boxes = {} - for key in self.labels.keys(): - self.nextBox = QComboBox() - self.connect(self.nextBox, SIGNAL('activated(int)'), + # Tally selections + label_tally = QLabel("Tally:") + self.tally = QComboBox() + # Only show options for the tallies with meshes + self.tally.addItems([str(i + 1) for i in self.tally_ids]) + self.connect(self.tally, SIGNAL('activated(int)'), + self._update) + self.connect(self.tally, SIGNAL('activated(int)'), + self.populate_boxes) + self.connect(self.tally, SIGNAL('activated(int)'), self.on_draw) - self.boxes[key] = self.nextBox - # Combobox to select among scores - self.score_label = QLabel("Score:") - self.scoreBox = QComboBox() - for item in self.tally_scores[0]: - self.scoreBox.addItems(str(item)) - self.connect(self.scoreBox, SIGNAL('activated(int)'), - self.on_draw) + # Planar basis + label_basis = QLabel("Basis:") + self.basis = QComboBox() + self.basis.addItems(['xy', 'yz', 'xz']) - # Fill layout - self.grid.addWidget(label_tally, 0, 0) - self.grid.addWidget(self.tally, 0, 1) - self.grid.addWidget(label_basis, 1, 0) - self.grid.addWidget(self.basis, 1, 1) - self.grid.addWidget(label_axial_level, 2, 0) - self.grid.addWidget(self.axial_level, 2, 1) - self.grid.addWidget(label_mean, 3, 0) - self.grid.addWidget(self.mean, 3, 1) - self.grid.addWidget(self.label_filters, 4, 0) + # Update window when 'Basis' selection is changed + self.connect(self.basis, SIGNAL('activated(int)'), + self._update) + self.connect(self.basis, SIGNAL('activated(int)'), + self.populate_boxes) + self.connect(self.basis, SIGNAL('activated(int)'), + self.on_draw) - self._update() - self.populate_boxes() - self.on_draw() + # Axial level within selected basis + label_axial_level = QLabel("Axial Level:") + self.axial_level = QComboBox() + self.connect(self.axial_level, SIGNAL('activated(int)'), + self.on_draw) - def get_file_data(self): + # Add Option to plot mean or uncertainty + label_mean = QLabel("Mean or Uncertainty:") + self.mean = QComboBox() + self.mean.addItems(['Mean','Absolute Uncertainty', + 'Relative Uncertainty']) + + # Update window when mean selection is changed + self.connect(self.mean, SIGNAL('activated(int)'), + self.on_draw) + + + self.label_filters = QLabel("Filter options:") + + # Labels for all possible filters + self.labels = {'cell': 'Cell: ', 'cellborn': 'Cell born: ', + 'surface': 'Surface: ', 'material': 'Material', + 'universe': 'Universe: ', 'energyin': 'Energy in: ', + 'energyout': 'Energy out: '} + + # Empty reusable labels + self.qlabels = {} + for j in range(8): + self.nextLabel = QLabel + self.qlabels[j] = self.nextLabel + + # Reusable comboboxes labelled with filter names + self.boxes = {} + for key in self.labels.keys(): + self.nextBox = QComboBox() + self.connect(self.nextBox, SIGNAL('activated(int)'), + self.on_draw) + self.boxes[key] = self.nextBox + + # Combobox to select among scores + self.score_label = QLabel("Score:") + self.scoreBox = QComboBox() + for item in self.tally_scores[0]: + self.scoreBox.addItems(str(item)) + self.connect(self.scoreBox, SIGNAL('activated(int)'), + self.on_draw) + + # Fill layout + self.grid.addWidget(label_tally, 0, 0) + self.grid.addWidget(self.tally, 0, 1) + self.grid.addWidget(label_basis, 1, 0) + self.grid.addWidget(self.basis, 1, 1) + self.grid.addWidget(label_axial_level, 2, 0) + self.grid.addWidget(self.axial_level, 2, 1) + self.grid.addWidget(label_mean, 3, 0) + self.grid.addWidget(self.mean, 3, 1) + self.grid.addWidget(self.label_filters, 4, 0) + + self._update() + self.populate_boxes() + self.on_draw() + + def get_file_data(self, cl_file=None): # Get data file name from "open file" browser - filename = QFileDialog.getOpenFileName(self, 'Select statepoint file', '.') + if cl_file is None: + filename = QFileDialog.getOpenFileName(self, + 'Select statepoint file', '.') + else: + filename = cl_file # Create StatePoint object and read in data self.datafile = StatePoint(str(filename)) @@ -141,32 +179,31 @@ class AppForm(QMainWindow): self.setWindowTitle('Core Map Tool : ' + str(self.datafile.path)) - # Set maximum colorbar value by maximum tally data value - self.maxvalue = self.datafile.tallies[0].results.max() - self.labelList = [] # Read mesh dimensions -# for mesh in self.datafile.meshes: -# self.nx, self.ny, self.nz = mesh.dimension + # for mesh in self.datafile.meshes: + # self.nx, self.ny, self.nz = mesh.dimension - # Read filter types from statepoint file + # Find which tallies have meshes so the rest can be ignored, + # and for these tallies read the filter and score types + self.tally_ids = [] self.n_tallies = len(self.datafile.tallies) self.tally_list = [] - for tally in self.datafile.tallies: - self.filter_types = [] - for f in tally.filters: - self.filter_types.append(f) - self.tally_list.append(self.filter_types) - - # Read score types from statepoint file self.tally_scores = [] - for tally in self.datafile.tallies: - self.score_types = [] - for s in tally.scores: - self.score_types.append(s) - self.tally_scores.append(self.score_types) -# print 'self.tally_scores = ', self.tally_scores + for itally, tally in enumerate(self.datafile.tallies): + if 'mesh' in tally.filters: + # Then we have a good tally, store the ID, filters and + # scores + self.tally_ids.append(itally) + self.filter_types = [] + for f in tally.filters: + self.filter_types.append(f) + self.tally_list.append(self.filter_types) + self.score_types = [] + for s in tally.scores: + self.score_types.append(s) + self.tally_scores.append(self.score_types) def on_draw(self): """ Redraws the figure @@ -178,70 +215,73 @@ class AppForm(QMainWindow): axial_level = self.axial_level.currentIndex() + 1 is_mean = self.mean.currentIndex() + # get current tally index + tally_id = self.tally_ids[self.tally.currentIndex()] + # Create spec_list spec_list = [] - for tally in self.datafile.tallies[self.tally.currentIndex()].filters.values(): + for tally in self.datafile.tallies[tally_id].filters.values(): if tally.type == 'mesh': continue index = self.boxes[tally.type].currentIndex() spec_list.append((tally.type, index)) - + # Take is_mean and convert it to an index of the score score_loc = is_mean if score_loc > 1: score_loc = 1 - + if self.basis.currentText() == 'xy': matrix = np.zeros((self.nx, self.ny)) for i in range(self.nx): for j in range(self.ny): - matrix[i,j] = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (i, j, axial_level))], + matrix[i,j] = self.datafile.get_value(tally_id, + spec_list + [('mesh', (i + 1, j + 1, axial_level))], self.scoreBox.currentIndex())[score_loc] # Calculate relative uncertainty from absolute, if # requested if is_mean == 2: # Take care to handle zero means when normalizing - mean_val = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (i, j, axial_level))], + mean_val = self.datafile.get_value(tally_id, + spec_list + [('mesh', (i + 1, j + 1, axial_level))], self.scoreBox.currentIndex())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val else: matrix[i,j] = 0.0 - + elif self.basis.currentText() == 'yz': matrix = np.zeros((self.ny, self.nz)) for i in range(self.ny): for j in range(self.nz): - matrix[i,j] = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (axial_level, i, j))], + matrix[i,j] = self.datafile.get_value(tally_id, + spec_list + [('mesh', (axial_level, i + 1, j + 1))], self.scoreBox.currentIndex())[score_loc] # Calculate relative uncertainty from absolute, if # requested if is_mean == 2: # Take care to handle zero means when normalizing - mean_val = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (axial_level, i, j))], + mean_val = self.datafile.get_value(tally_id, + spec_list + [('mesh', (axial_level, i + 1, j + 1))], self.scoreBox.currentIndex())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val else: matrix[i,j] = 0.0 - + else: matrix = np.zeros((self.nx, self.nz)) for i in range(self.nx): for j in range(self.nz): - matrix[i,j] = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (i, axial_level, j))], + matrix[i,j] = self.datafile.get_value(tally_id, + spec_list + [('mesh', (i + 1, axial_level, j + 1))], self.scoreBox.currentIndex())[score_loc] # Calculate relative uncertainty from absolute, if # requested if is_mean == 2: # Take care to handle zero means when normalizing - mean_val = self.datafile.get_value(self.tally.currentIndex(), - spec_list + [('mesh', (i, axial_level, j))], + mean_val = self.datafile.get_value(tally_id, + spec_list + [('mesh', (i + 1, axial_level, j + 1))], self.scoreBox.currentIndex())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val @@ -255,8 +295,8 @@ class AppForm(QMainWindow): # Make figure, set up color bar self.axes = self.fig.add_subplot(111) - cax = self.axes.imshow(matrix, vmin=0.0, vmax=matrix.max(), - interpolation="nearest") + cax = self.axes.imshow(matrix.transpose(), vmin=0.0, vmax=matrix.max(), + interpolation="nearest", origin='lower') self.fig.colorbar(cax) self.axes.set_xticks([]) @@ -271,9 +311,11 @@ class AppForm(QMainWindow): ''' # print 'Calling _update...' + # get current tally index + tally_id = self.tally_ids[self.tally.currentIndex()] + self.mesh = self.datafile.meshes[ - self.datafile.tallies[ - self.tally.currentIndex()].filters['mesh'].bins[0] - 1] + self.datafile.tallies[tally_id].filters['mesh'].bins[0] - 1] self.nx, self.ny, self.nz = self.mesh.dimension @@ -289,8 +331,7 @@ class AppForm(QMainWindow): self.axial_level.addItems([str(i+1) for i in range(self.ny)]) # Determine maximum value from current tally data set - self.maxvalue = self.datafile.tallies[ - self.tally.currentIndex()].results.max() + self.maxvalue = self.datafile.tallies[tally_id].results.max() # print self.maxvalue # Clear and hide old filter labels @@ -307,6 +348,9 @@ class AppForm(QMainWindow): def populate_boxes(self): # print 'Calling populate_boxes...' + # get current tally index + tally_id = self.tally_ids[self.tally.currentIndex()] + n = 5 labels = {'cell': 'Cell : ', 'cellborn': 'Cell born: ', @@ -317,8 +361,7 @@ class AppForm(QMainWindow): # For each filter in newly-selected tally, name a label and fill the # relevant combobox with options for element in self.tally_list[self.tally.currentIndex()]: - nextFilter = self.datafile.tallies[ - self.tally.currentIndex()].filters[element] + nextFilter = self.datafile.tallies[tally_id].filters[element] if element == 'mesh': continue @@ -337,7 +380,7 @@ class AppForm(QMainWindow): elif element == 'energyin' or element == 'energyout': for i in range(nextFilter.length): - text = (str(nextFilter.bins[i]) + ' to ' + + text = (str(nextFilter.bins[i]) + ' to ' + str(nextFilter.bins[i+1])) combobox.addItem(text) @@ -351,9 +394,10 @@ class AppForm(QMainWindow): def main(): app = QApplication(sys.argv) - form = AppForm() - form.show() - app.exec_() + form = AppForm(app.arguments()) + if form.all_good: + form.show() + app.exec_() if __name__ == "__main__": diff --git a/src/utils/statepoint.py b/src/utils/statepoint.py index ec9d9edf78..430abee8b8 100644 --- a/src/utils/statepoint.py +++ b/src/utils/statepoint.py @@ -1,7 +1,6 @@ #!/usr/bin/env python2 import struct -from math import sqrt from collections import OrderedDict import numpy as np @@ -10,10 +9,10 @@ import scipy.stats filter_types = {1: 'universe', 2: 'material', 3: 'cell', 4: 'cellborn', 5: 'surface', 6: 'mesh', 7: 'energyin', 8: 'energyout'} -score_types = {-1: 'flux', +score_types = {-1: 'flux', -2: 'total', -3: 'scatter', - -4: 'nu-scatter', + -4: 'nu-scatter', -5: 'scatter-n', -6: 'scatter-pn', -7: 'transport', @@ -276,7 +275,7 @@ class StatePoint(object): f.bins = self._get_int(path=base+'bins') else: f.bins = self._get_int(f.length, path=base+'bins') - + base = 'tallies/tally' + str(i+1) + '/' # Read nuclide bins @@ -379,7 +378,7 @@ class StatePoint(object): Calculates the sample mean and standard deviation of the mean for each tally bin. """ - + # Determine number of realizations n = self.n_realizations @@ -387,14 +386,14 @@ class StatePoint(object): for i in range(len(self.global_tallies)): # Get sum and sum of squares s, s2 = self.global_tallies[i] - + # Calculate sample mean and replace value s /= n self.global_tallies[i,0] = s # Calculate standard deviation if s != 0.0: - self.global_tallies[i,1] = t_value*sqrt((s2/n - s*s)/(n-1)) + self.global_tallies[i,1] = t_value*np.sqrt((s2/n - s*s)/(n-1)) # Regular tallies for t in self.tallies: @@ -402,14 +401,14 @@ class StatePoint(object): for j in range(t.results.shape[1]): # Get sum and sum of squares s, s2 = t.results[i,j] - + # Calculate sample mean and replace value s /= n t.results[i,j,0] = s # Calculate standard deviation if s != 0.0: - t.results[i,j,1] = t_value*sqrt((s2/n - s*s)/(n-1)) + t.results[i,j,1] = t_value*np.sqrt((s2/n - s*s)/(n-1)) def get_value(self, tally_index, spec_list, score_index): """Returns a tally score given a list of filters to satisfy. @@ -458,7 +457,7 @@ class StatePoint(object): filter_index += value*t.filters[f_type].stride else: filter_index += f_index*t.filters[f_type].stride - + # Return the desired result from Tally.results. This could be the sum and # sum of squares, or it could be mean and stdev if self.generate_stdev() # has been called already. @@ -531,7 +530,7 @@ class StatePoint(object): for i in range(n_filters): # compute indices for filter combination - filters[:,n_filters - i - 1] = np.floor((np.arange(n_bins) % + filters[:,n_filters - i - 1] = np.floor((np.arange(n_bins) % np.prod(filtmax[0:i+2]))/(np.prod(filtmax[0:i+1]))) + 1 # append in dictionary bin with filter @@ -544,14 +543,14 @@ class StatePoint(object): dims.reverse() dims = np.asarray(dims) if score_str == 'current': - dims += 1 - meshmax[1:4] = dims + dims += 1 + meshmax[1:4] = dims mesh_bins = np.zeros((n_bins,3)) - mesh_bins[:,2] = np.floor(((filters[:,n_filters - i - 1] - 1) % + mesh_bins[:,2] = np.floor(((filters[:,n_filters - i - 1] - 1) % np.prod(meshmax[0:2]))/(np.prod(meshmax[0:1]))) + 1 - mesh_bins[:,1] = np.floor(((filters[:,n_filters - i - 1] - 1) % + mesh_bins[:,1] = np.floor(((filters[:,n_filters - i - 1] - 1) % np.prod(meshmax[0:3]))/(np.prod(meshmax[0:2]))) + 1 - mesh_bins[:,0] = np.floor(((filters[:,n_filters - i - 1] - 1) % + mesh_bins[:,0] = np.floor(((filters[:,n_filters - i - 1] - 1) % np.prod(meshmax[0:4]))/(np.prod(meshmax[0:3]))) + 1 data.update({'mesh':zip(mesh_bins[:,0],mesh_bins[:,1], mesh_bins[:,2])}) @@ -576,7 +575,7 @@ class StatePoint(object): def _get_data(self, n, typeCode, size): return list(struct.unpack('={0}{1}'.format(n,typeCode), self._f.read(n*size))) - + def _get_int(self, n=1, path=None): if self._hdf5: return [int(v) for v in self._f[path].value] From a3726724ecf9d287707d87d7863c84dbf5f3ef13 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Jan 2014 23:15:59 -0500 Subject: [PATCH 76/84] Ability to generate PDF documentation. --- .gitignore | 1 + docs/Makefile | 20 +++++++++++++++--- docs/{img => source/_images}/3dcore.png | Bin docs/{img => source/_images}/3dgeomplot.png | Bin docs/{img => source/_images}/Tracks.png | Bin docs/{img => source/_images}/atr.png | Bin docs/{img => source/_images}/fluxplot.png | Bin docs/{img => source/_images}/fork.png | Bin docs/{img => source/_images}/halfspace.svg | 0 docs/{img => source/_images}/master-slave.png | Bin .../_images}/nearest-neighbor-example.png | Bin .../_images}/nearest-neighbor.png | Bin docs/{img => source/_images}/openmc.png | Bin .../{img => source/_images}/plotmeshtally.png | Bin docs/{img => source/_images}/pullrequest.png | Bin docs/{img => source/_images}/union.svg | 0 docs/{img => source/_images}/uniongrid.svg | 0 docs/source/conf.py | 4 +++- docs/source/devguide/workflow.rst | 4 ++-- docs/source/methods/cross_sections.rst | 2 +- docs/source/methods/geometry.rst | 4 ++-- docs/source/methods/parallelization.rst | 6 +++--- docs/source/usersguide/processing.rst | 12 +++++------ 23 files changed, 35 insertions(+), 18 deletions(-) rename docs/{img => source/_images}/3dcore.png (100%) rename docs/{img => source/_images}/3dgeomplot.png (100%) rename docs/{img => source/_images}/Tracks.png (100%) rename docs/{img => source/_images}/atr.png (100%) rename docs/{img => source/_images}/fluxplot.png (100%) rename docs/{img => source/_images}/fork.png (100%) rename docs/{img => source/_images}/halfspace.svg (100%) rename docs/{img => source/_images}/master-slave.png (100%) rename docs/{img => source/_images}/nearest-neighbor-example.png (100%) rename docs/{img => source/_images}/nearest-neighbor.png (100%) rename docs/{img => source/_images}/openmc.png (100%) rename docs/{img => source/_images}/plotmeshtally.png (100%) rename docs/{img => source/_images}/pullrequest.png (100%) rename docs/{img => source/_images}/union.svg (100%) rename docs/{img => source/_images}/uniongrid.svg (100%) diff --git a/.gitignore b/.gitignore index 023c4f2c1a..9f720f9894 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ src/openmc # Documentation builds docs/build +docs/source/_images/*.pdf # xml-fortran reader src/xml-fortran/xmlreader diff --git a/docs/Makefile b/docs/Makefile index 8909256f3d..89c71dc074 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,13 +6,19 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build +IMAGEDIR = source/_images # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +# SVG to PDF conversion +SVG2PDF = inkscape +PDFS = $(patsubst %.svg,%.pdf,$(wildcard $(IMAGEDIR)/*.svg)) + + +.PHONY: help images clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @@ -33,8 +39,16 @@ help: @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" +# Pattern rule for converting SVG to PDF +%.pdf: %.svg + $(SVG2PDF) -f $< -A $@ + +# Rule to build PDFs +images: $(PDFS) + clean: -rm -rf $(BUILDDIR)/* + -rm $(PDFS) html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @@ -91,14 +105,14 @@ epub: @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." -latex: +latex: images $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." -latexpdf: +latexpdf: images $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf diff --git a/docs/img/3dcore.png b/docs/source/_images/3dcore.png similarity index 100% rename from docs/img/3dcore.png rename to docs/source/_images/3dcore.png diff --git a/docs/img/3dgeomplot.png b/docs/source/_images/3dgeomplot.png similarity index 100% rename from docs/img/3dgeomplot.png rename to docs/source/_images/3dgeomplot.png diff --git a/docs/img/Tracks.png b/docs/source/_images/Tracks.png similarity index 100% rename from docs/img/Tracks.png rename to docs/source/_images/Tracks.png diff --git a/docs/img/atr.png b/docs/source/_images/atr.png similarity index 100% rename from docs/img/atr.png rename to docs/source/_images/atr.png diff --git a/docs/img/fluxplot.png b/docs/source/_images/fluxplot.png similarity index 100% rename from docs/img/fluxplot.png rename to docs/source/_images/fluxplot.png diff --git a/docs/img/fork.png b/docs/source/_images/fork.png similarity index 100% rename from docs/img/fork.png rename to docs/source/_images/fork.png diff --git a/docs/img/halfspace.svg b/docs/source/_images/halfspace.svg similarity index 100% rename from docs/img/halfspace.svg rename to docs/source/_images/halfspace.svg diff --git a/docs/img/master-slave.png b/docs/source/_images/master-slave.png similarity index 100% rename from docs/img/master-slave.png rename to docs/source/_images/master-slave.png diff --git a/docs/img/nearest-neighbor-example.png b/docs/source/_images/nearest-neighbor-example.png similarity index 100% rename from docs/img/nearest-neighbor-example.png rename to docs/source/_images/nearest-neighbor-example.png diff --git a/docs/img/nearest-neighbor.png b/docs/source/_images/nearest-neighbor.png similarity index 100% rename from docs/img/nearest-neighbor.png rename to docs/source/_images/nearest-neighbor.png diff --git a/docs/img/openmc.png b/docs/source/_images/openmc.png similarity index 100% rename from docs/img/openmc.png rename to docs/source/_images/openmc.png diff --git a/docs/img/plotmeshtally.png b/docs/source/_images/plotmeshtally.png similarity index 100% rename from docs/img/plotmeshtally.png rename to docs/source/_images/plotmeshtally.png diff --git a/docs/img/pullrequest.png b/docs/source/_images/pullrequest.png similarity index 100% rename from docs/img/pullrequest.png rename to docs/source/_images/pullrequest.png diff --git a/docs/img/union.svg b/docs/source/_images/union.svg similarity index 100% rename from docs/img/union.svg rename to docs/source/_images/union.svg diff --git a/docs/img/uniongrid.svg b/docs/source/_images/uniongrid.svg similarity index 100% rename from docs/img/uniongrid.svg rename to docs/source/_images/uniongrid.svg diff --git a/docs/source/conf.py b/docs/source/conf.py index 8752b48986..24c26bcd87 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -121,7 +121,7 @@ html_title = "OpenMC Documentation" # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '../img/openmc.png' +html_logo = '_images/openmc.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -188,6 +188,8 @@ latex_documents = [ u'Massachusetts Institute of Technology', 'manual'), ] +latex_elements = {'preamble': '\\usepackage{enumitem}\\setlistdepth{9}'} + # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index 9562c9584e..f3793628af 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -60,7 +60,7 @@ features and bug fixes. The general steps for contributing are as follows: repository with the same name under your personal account. As such, you can commit to it as you please without disrupting other developers. - .. image:: ../../img/fork.png + .. image:: ../_images/fork.png 2. Clone your fork of OpenMC and create a branch that branches off of *develop*: @@ -77,7 +77,7 @@ features and bug fixes. The general steps for contributing are as follows: 4. Issue a pull request from GitHub and select the *develop* branch of mit-crpg/openmc as the target. - .. image:: ../../img/pullrequest.png + .. image:: ../_images/pullrequest.png At a minimum, you should describe what the changes you've made are and why you are making them. If the changes are related to an oustanding issue, make diff --git a/docs/source/methods/cross_sections.rst b/docs/source/methods/cross_sections.rst index c564896c79..a193dde96d 100644 --- a/docs/source/methods/cross_sections.rst +++ b/docs/source/methods/cross_sections.rst @@ -80,7 +80,7 @@ dashed box would need to be stored on a per-nuclide basis, and the union grid would need to be stored once. This method is also referred to as *double indexing* and is available as an option in Serpent (see paper by Leppanen_). -.. figure:: ../../img/uniongrid.svg +.. figure:: ../_images/uniongrid.* :width: 600px :align: center :figclass: align-center diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index d4824f5d1c..1515ffa891 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -45,7 +45,7 @@ surface by a combination of the unique ID of the surface and a positive/negative sign. The following illustration shows an example of an ellipse with unique ID 1 dividing space into two half-spaces. -.. figure:: ../../img/halfspace.svg +.. figure:: ../_images/halfspace.* :align: center :figclass: align-center @@ -60,7 +60,7 @@ half-space references whose intersection defines the region. The region is then assigned a material defined elsewhere. The following illustration shows an example of a cell defined as the intersection of an ellipse and two planes. -.. figure:: ../../img/union.svg +.. figure:: ../_images/union.* :align: center :figclass: align-center diff --git a/docs/source/methods/parallelization.rst b/docs/source/methods/parallelization.rst index 7f0b9bd6eb..66ead52a2a 100644 --- a/docs/source/methods/parallelization.rst +++ b/docs/source/methods/parallelization.rst @@ -65,7 +65,7 @@ in the case of an eigenvalue calculation). This idea is illustrated in .. _figure-master-slave: -.. figure:: ../../img/master-slave.png +.. figure:: ../_images/master-slave.png :align: center :figclass: align-center @@ -122,7 +122,7 @@ needed. This concept is illustrated in :ref:`Figure 2 .. _figure-nearest-neighbor: -.. figure:: ../../img/nearest-neighbor.png +.. figure:: ../_images/nearest-neighbor.png :align: center :figclass: align-center @@ -203,7 +203,7 @@ communicated between adjacent nodes. .. _figure-neighbor-example: -.. figure:: ../../img/nearest-neighbor-example.png +.. figure:: ../_images/nearest-neighbor-example.png :align: center :figclass: align-center diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 043041ef42..fc426ba2f4 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -38,7 +38,7 @@ running OpenMC with the -plot or -p command-line option (See Plotting in 2D -------------- -.. image:: ../../img/atr.png +.. image:: ../_images/atr.png :height: 200px After running OpenMC to obtain PPM files, images should be saved to another @@ -58,7 +58,7 @@ Ubuntu: ``sudo apt-get install imagemagick``). Images are then converted like: Plotting in 3D -------------- -.. image:: ../../img/3dgeomplot.png +.. image:: ../_images/3dgeomplot.png :height: 200px The binary VOXEL files output by OpenMC can not be viewed directly by any @@ -162,7 +162,7 @@ tasks will be described here in the following sections. Plotting in 2D -------------- -.. image:: ../../img/plotmeshtally.png +.. image:: ../_images/plotmeshtally.png :height: 200px For simple viewing of 2D slices of a mesh plot, the utility plot_mesh_tally.py @@ -170,7 +170,7 @@ is provided. This utility provides an interactive GUI to explore and plot mesh tallies for any scores and filter bins. It requires statepoint.py, as well as `PyQt `_. -.. image:: ../../img/fluxplot.png +.. image:: ../_images/fluxplot.png :height: 200px Alternatively, the user can write their own Python script to manipulate the data @@ -249,7 +249,7 @@ two heatmaps in the previous figure. Plotting in 3D -------------- -.. image:: ../../img/3dcore.png +.. image:: ../_images/3dcore.png :height: 200px As with 3D plots of the geometry, meshtally data needs to be put into a standard @@ -357,7 +357,7 @@ and dumped to MATLAB in one step. Particle Track Visualization ---------------------------- -.. image:: ../../img/Tracks.png +.. image:: ../_images/Tracks.png :height: 200px OpenMC can dump particle tracks—the position of particles as they are From c439c3dc5d533c74ca7df25759009eb89d81af9a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 14 Jan 2014 20:34:29 -0500 Subject: [PATCH 77/84] Change OPENMP macro to _OPENMP which is automatically defined when compiling with OpenMP. --- src/Makefile | 6 +++--- src/eigenvalue.F90 | 4 ++-- src/global.F90 | 6 +++--- src/initialize.F90 | 6 +++--- src/input_xml.F90 | 2 +- src/output.F90 | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Makefile b/src/Makefile index 03c370e07a..00abca489f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -181,17 +181,17 @@ endif ifeq ($(OPENMP),yes) ifeq ($(COMPILER),intel) - F90FLAGS += -openmp -DOPENMP + F90FLAGS += -openmp LDFLAGS += -openmp endif ifeq ($(COMPILER),gnu) - F90FLAGS += -fopenmp -DOPENMP + F90FLAGS += -fopenmp LDFLAGS += -fopenmp endif ifeq ($(COMPILER),ibm) - F90FLAGS += -qsmp=omp -WF,-DOPENMP + F90FLAGS += -qsmp=omp LDFLAGS += -qsmp=omp endif endif diff --git a/src/eigenvalue.F90 b/src/eigenvalue.F90 index f5375bf3a9..4047f815f5 100644 --- a/src/eigenvalue.F90 +++ b/src/eigenvalue.F90 @@ -166,7 +166,7 @@ contains subroutine finalize_generation() -#ifdef OPENMP +#ifdef _OPENMP ! Join the fission bank from each thread into one global fission bank call join_bank_from_threads() #endif @@ -821,7 +821,7 @@ contains end subroutine replay_batch_history -#ifdef OPENMP +#ifdef _OPENMP !=============================================================================== ! JOIN_BANK_FROM_THREADS !=============================================================================== diff --git a/src/global.F90 b/src/global.F90 index 637ea315e6..c3dbf0aa24 100644 --- a/src/global.F90 +++ b/src/global.F90 @@ -158,7 +158,7 @@ module global ! Source and fission bank type(Bank), allocatable, target :: source_bank(:) type(Bank), allocatable, target :: fission_bank(:) -#ifdef OPENMP +#ifdef _OPENMP type(Bank), allocatable, target :: master_fission_bank(:) #endif integer(8) :: n_bank ! # of sites in fission bank @@ -205,7 +205,7 @@ module global integer :: MPI_BANK ! MPI datatype for fission bank integer :: MPI_TALLYRESULT ! MPI datatype for TallyResult -#ifdef OPENMP +#ifdef _OPENMP integer :: n_threads = NONE ! number of OpenMP threads integer :: thread_id ! ID of a given thread #endif @@ -438,7 +438,7 @@ contains !$omp parallel if (allocated(fission_bank)) deallocate(fission_bank) !$omp end parallel -#ifdef OPENMP +#ifdef _OPENMP if (allocated(master_fission_bank)) deallocate(master_fission_bank) #endif if (allocated(source_bank)) deallocate(source_bank) diff --git a/src/initialize.F90 b/src/initialize.F90 index b75fa20ef8..9f495e4ac9 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -26,7 +26,7 @@ module initialize use mpi #endif -#ifdef OPENMP +#ifdef _OPENMP use omp_lib #endif @@ -372,7 +372,7 @@ contains ! Read number of threads i = i + 1 -#ifdef OPENMP +#ifdef _OPENMP ! Read and set number of OpenMP threads n_threads = str_to_int(argv(i)) if (n_threads < 1) then @@ -830,7 +830,7 @@ contains call fatal_error() end if -#ifdef OPENMP +#ifdef _OPENMP ! If OpenMP is being used, each thread needs its own private fission ! bank. Since the private fission banks need to be combined at the end of a ! generation, there is also a 'master_fission_bank' that is used to collect diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 3ce3c9d0d4..ae4e4b1b2e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -228,7 +228,7 @@ contains ! Number of OpenMP threads if (check_for_node(doc, "threads")) then -#ifdef OPENMP +#ifdef _OPENMP if (n_threads == NONE) then call get_node_value(doc, "threads", n_threads) if (n_threads < 1) then diff --git a/src/output.F90 b/src/output.F90 index 677c35396c..3e31ad7183 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -31,7 +31,7 @@ contains subroutine title() -#ifdef OPENMP +#ifdef _OPENMP use omp_lib #endif @@ -69,7 +69,7 @@ contains trim(to_str(n_procs)) #endif -#ifdef OPENMP +#ifdef _OPENMP ! Write number of OpenMP threads write(UNIT=OUTPUT_UNIT, FMT='(6X,"OpenMP Threads:",1X,A)') & trim(to_str(omp_get_max_threads())) From b142ff6458d4515a2e4f9d72ae0b7ee3bfb67da6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 14 Jan 2014 21:26:40 -0500 Subject: [PATCH 78/84] Move calculation of number of threads outside of .omp parallel. --- src/initialize.F90 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/initialize.F90 b/src/initialize.F90 index 9f495e4ac9..c1fbf40cda 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -836,8 +836,9 @@ contains ! generation, there is also a 'master_fission_bank' that is used to collect ! the sites from each thread. + n_threads = omp_get_max_threads() + !$omp parallel - n_threads = omp_get_num_threads() thread_id = omp_get_thread_num() if (thread_id == 0) then From cbf37e0916c6f878b3af07f8a89f3dd13570ca0e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 14 Jan 2014 21:44:40 -0500 Subject: [PATCH 79/84] Happy new year 2014! --- LICENSE | 2 +- docs/source/conf.py | 2 +- docs/source/license.rst | 2 +- man/man1/openmc.1 | 2 +- src/output.F90 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index f8d9cd7c04..0b9c351371 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2013 Massachusetts Institute of Technology +Copyright (c) 2011-2014 Massachusetts Institute of Technology Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/source/conf.py b/docs/source/conf.py index 24c26bcd87..7fb6226318 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,7 +39,7 @@ master_doc = 'index' # General information about the project. project = u'OpenMC' -copyright = u'2011-2013, Massachusetts Institute of Technology' +copyright = u'2011-2014, Massachusetts Institute of Technology' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/source/license.rst b/docs/source/license.rst index a7d7f29760..e7f4b3a69d 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -4,7 +4,7 @@ License Agreement ================= -Copyright © 2011-2013 Massachusetts Institute of Technology +Copyright © 2011-2014 Massachusetts Institute of Technology Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index cac8d59087..db91d7a8f0 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -46,7 +46,7 @@ to locate ACE format cross section libraries if the user has not specified the tag in .I settings.xml\fP. .SH LICENSE -Copyright \(co 2011-2013 Massachusetts Institute of Technology. +Copyright \(co 2011-2014 Massachusetts Institute of Technology. .PP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/output.F90 b/src/output.F90 index 3e31ad7183..ee0a8391c2 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -50,7 +50,7 @@ contains ! Write version information write(UNIT=OUTPUT_UNIT, FMT=*) & - ' Copyright: 2011-2013 Massachusetts Institute of Technology' + ' Copyright: 2011-2014 Massachusetts Institute of Technology' write(UNIT=OUTPUT_UNIT, FMT=*) & ' License: http://mit-crpg.github.io/openmc/license.html' write(UNIT=OUTPUT_UNIT, FMT='(6X,"Version:",8X,I1,".",I1,".",I1)') & From 55d257351c7b731aec57b415b3270d0e205f30f3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 20 Jan 2014 19:00:12 -0500 Subject: [PATCH 80/84] Converted plot_mesh_tally.py to use Tkinter rather than PyQt. --- src/utils/plot_mesh_tally.py | 512 +++++++++++++++-------------------- 1 file changed, 213 insertions(+), 299 deletions(-) diff --git a/src/utils/plot_mesh_tally.py b/src/utils/plot_mesh_tally.py index c70b291653..d22e0e0b4f 100755 --- a/src/utils/plot_mesh_tally.py +++ b/src/utils/plot_mesh_tally.py @@ -1,269 +1,241 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python -'''Python script to plot tally data generated by OpenMC.''' +"""Python script to plot tally data generated by OpenMC.""" +import os import sys -from statepoint import * -# Color intensity dependent on individual score? - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigureCanvas +from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as NavigationToolbar from matplotlib.figure import Figure -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar +import matplotlib.pyplot as plt import numpy as np -class AppForm(QMainWindow): - def __init__(self, argv, parent=None): - QMainWindow.__init__(self, parent) +from statepoint import * + +if sys.version_info[0] < 3: + import Tkinter as tk +else: + import tkinter as tk +import tkFileDialog +import tkFont +import tkMessageBox +import ttk + +class MeshPlotter(tk.Frame): + def __init__(self, parent, filename): + tk.Frame.__init__(self, parent) + + self.labels = {'cell': 'Cell:', 'cellborn': 'Cell born:', + 'surface': 'Surface:', 'material': 'Material:', + 'universe': 'Universe:', 'energyin': 'Energy in:', + 'energyout': 'Energy out:'} + + self.filterBoxes = {} # Read data from source or leakage fraction file - self.all_good = False - while not self.all_good: - if len(argv) > 1: - cl_file = str(argv[1]) - else: - cl_file = None - self.get_file_data(cl_file) - # Check that there are any mesh tallies at all - if len(self.tally_ids) != 0: - self.all_good = True - else: - # if there are not, the user will be given the choice to choose - # another file (but only if using interactive chooser) - if cl_file is None: - choice = QMessageBox.critical(None, "Invalid StatePoint File", - "File Does Not Contain Mesh " + - "Tallies!" + - "\nSelect Another File Or Quit", - QMessageBox.Retry, - QMessageBox.Abort) - if choice == QMessageBox.Abort: - self.all_good = False - break - else: - print("Invalid StatePoint File; File Does Not Contain " + - "Mesh Tallies!") - self.all_good = False - break + self.get_file_data(filename) + # Set up top-level window + top = self.winfo_toplevel() + top.title('Mesh Tally Plotter: ' + filename) + top.rowconfigure(0, weight=1) + top.columnconfigure(0, weight=1) + self.grid(sticky=tk.W+tk.N) - if self.all_good: - # Set maximum colorbar value by maximum tally data value - self.maxvalue = self.datafile.tallies[0].results.max() + # Create widgets and draw to screen + self.create_widgets() + self.update() - self.main_frame = QWidget() - self.setCentralWidget(self.main_frame) + def create_widgets(self): + figureFrame = tk.Frame(self) + figureFrame.grid(row=0, column=0) - # Create the Figure, Canvas, and Axes - self.dpi = 100 - self.fig = Figure((5.0, 15.0), dpi=self.dpi) - self.canvas = FigureCanvas(self.fig) - self.canvas.setParent(self.main_frame) - self.axes = self.fig.add_subplot(111) + # Create the Figure and Canvas + self.dpi = 100 + self.fig = Figure((5.0, 5.0), dpi=self.dpi) + self.canvas = FigureCanvas(self.fig, master=figureFrame) + self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) - # Create the navigation toolbar, tied to the canvas - self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame) + # Create the navigation toolbar, tied to the canvas + self.mpl_toolbar = NavigationToolbar(self.canvas, figureFrame) + self.mpl_toolbar.update() + self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) - # Grid layout at bottom - self.grid = QGridLayout() + # Create frame for comboboxes + self.selectFrame = tk.Frame(self) + self.selectFrame.grid(row=1, column=0, sticky=tk.W+tk.E) - # Overall layout - self.vbox = QVBoxLayout() - self.vbox.addWidget(self.canvas) - self.vbox.addWidget(self.mpl_toolbar) - self.vbox.addLayout(self.grid) - self.main_frame.setLayout(self.vbox) + # Tally selection + labelTally = tk.Label(self.selectFrame, text='Tally:') + labelTally.grid(row=0, column=0, sticky=tk.W) + self.tallyBox = ttk.Combobox(self.selectFrame, state='readonly') + self.tallyBox['values'] = [self.datafile.tallies[i].id + for i in self.meshTallies] + self.tallyBox.current(0) + self.tallyBox.grid(row=0, column=1, sticky=tk.W+tk.E) + self.tallyBox.bind('<>', self.update) - # Tally selections - label_tally = QLabel("Tally:") - self.tally = QComboBox() - # Only show options for the tallies with meshes - self.tally.addItems([str(i + 1) for i in self.tally_ids]) - self.connect(self.tally, SIGNAL('activated(int)'), - self._update) - self.connect(self.tally, SIGNAL('activated(int)'), - self.populate_boxes) - self.connect(self.tally, SIGNAL('activated(int)'), - self.on_draw) + # Planar basis selection + labelBasis = tk.Label(self.selectFrame, text='Basis:') + labelBasis.grid(row=1, column=0, sticky=tk.W) + self.basisBox = ttk.Combobox(self.selectFrame, state='readonly') + self.basisBox['values'] = ('xy', 'yz', 'xz') + self.basisBox.current(0) + self.basisBox.grid(row=1, column=1, sticky=tk.W+tk.E) + self.basisBox.bind('<>', self.update) - # Planar basis - label_basis = QLabel("Basis:") - self.basis = QComboBox() - self.basis.addItems(['xy', 'yz', 'xz']) + # Axial level + labelAxial = tk.Label(self.selectFrame, text='Axial level:') + labelAxial.grid(row=2, column=0, sticky=tk.W) + self.axialBox = ttk.Combobox(self.selectFrame, state='readonly') + self.axialBox.grid(row=2, column=1, sticky=tk.W+tk.E) + self.axialBox.bind('<>', self.redraw) - # Update window when 'Basis' selection is changed - self.connect(self.basis, SIGNAL('activated(int)'), - self._update) - self.connect(self.basis, SIGNAL('activated(int)'), - self.populate_boxes) - self.connect(self.basis, SIGNAL('activated(int)'), - self.on_draw) + # Option for mean/uncertainty + labelMean = tk.Label(self.selectFrame, text='Mean/Uncertainty:') + labelMean.grid(row=3, column=0, sticky=tk.W) + self.meanBox = ttk.Combobox(self.selectFrame, state='readonly') + self.meanBox['values'] = ('Mean', 'Absolute uncertainty', + 'Relative uncertainty') + self.meanBox.current(0) + self.meanBox.grid(row=3, column=1, sticky=tk.W+tk.E) + self.meanBox.bind('<>', self.update) - # Axial level within selected basis - label_axial_level = QLabel("Axial Level:") - self.axial_level = QComboBox() - self.connect(self.axial_level, SIGNAL('activated(int)'), - self.on_draw) + # Scores + labelScore = tk.Label(self.selectFrame, text='Score:') + labelScore.grid(row=4, column=0, sticky=tk.W) + self.scoreBox = ttk.Combobox(self.selectFrame, state='readonly') + self.scoreBox.grid(row=4, column=1, sticky=tk.W+tk.E) + self.scoreBox.bind('<>', self.redraw) - # Add Option to plot mean or uncertainty - label_mean = QLabel("Mean or Uncertainty:") - self.mean = QComboBox() - self.mean.addItems(['Mean','Absolute Uncertainty', - 'Relative Uncertainty']) + # Filter label + font = tkFont.Font(weight='bold') + labelFilters = tk.Label(self.selectFrame, text='Filters:', font=font) + labelFilters.grid(row=5, column=0, sticky=tk.W) - # Update window when mean selection is changed - self.connect(self.mean, SIGNAL('activated(int)'), - self.on_draw) - - - self.label_filters = QLabel("Filter options:") - - # Labels for all possible filters - self.labels = {'cell': 'Cell: ', 'cellborn': 'Cell born: ', - 'surface': 'Surface: ', 'material': 'Material', - 'universe': 'Universe: ', 'energyin': 'Energy in: ', - 'energyout': 'Energy out: '} - - # Empty reusable labels - self.qlabels = {} - for j in range(8): - self.nextLabel = QLabel - self.qlabels[j] = self.nextLabel - - # Reusable comboboxes labelled with filter names - self.boxes = {} - for key in self.labels.keys(): - self.nextBox = QComboBox() - self.connect(self.nextBox, SIGNAL('activated(int)'), - self.on_draw) - self.boxes[key] = self.nextBox - - # Combobox to select among scores - self.score_label = QLabel("Score:") - self.scoreBox = QComboBox() - for item in self.tally_scores[0]: - self.scoreBox.addItems(str(item)) - self.connect(self.scoreBox, SIGNAL('activated(int)'), - self.on_draw) - - # Fill layout - self.grid.addWidget(label_tally, 0, 0) - self.grid.addWidget(self.tally, 0, 1) - self.grid.addWidget(label_basis, 1, 0) - self.grid.addWidget(self.basis, 1, 1) - self.grid.addWidget(label_axial_level, 2, 0) - self.grid.addWidget(self.axial_level, 2, 1) - self.grid.addWidget(label_mean, 3, 0) - self.grid.addWidget(self.mean, 3, 1) - self.grid.addWidget(self.label_filters, 4, 0) - - self._update() - self.populate_boxes() - self.on_draw() - - def get_file_data(self, cl_file=None): - # Get data file name from "open file" browser - if cl_file is None: - filename = QFileDialog.getOpenFileName(self, - 'Select statepoint file', '.') + def update(self, event=None): + if not event: + widget = None else: - filename = cl_file + widget = event.widget - # Create StatePoint object and read in data - self.datafile = StatePoint(str(filename)) - self.datafile.read_results() - self.datafile.generate_stdev() + tally_id = self.meshTallies[self.tallyBox.current()] + selectedTally = self.datafile.tallies[tally_id] - self.setWindowTitle('Core Map Tool : ' + str(self.datafile.path)) + # Get mesh for selected tally + self.mesh = self.datafile.meshes[selectedTally.filters['mesh'].bins[0] - 1] - self.labelList = [] + # Get mesh dimensions + self.nx, self.ny, self.nz = self.mesh.dimension - # Read mesh dimensions - # for mesh in self.datafile.meshes: - # self.nx, self.ny, self.nz = mesh.dimension + # Repopulate comboboxes baesd on current basis selection + text = self.basisBox['values'][self.basisBox.current()] + if text == 'xy': + self.axialBox['values'] = [str(i+1) for i in range(self.nz)] + elif text == 'yz': + self.axialBox['values'] = [str(i+1) for i in range(self.nx)] + else: + self.axialBox['values'] = [str(i+1) for i in range(self.ny)] + self.axialBox.current(0) - # Find which tallies have meshes so the rest can be ignored, - # and for these tallies read the filter and score types - self.tally_ids = [] - self.n_tallies = len(self.datafile.tallies) - self.tally_list = [] - self.tally_scores = [] - for itally, tally in enumerate(self.datafile.tallies): - if 'mesh' in tally.filters: - # Then we have a good tally, store the ID, filters and - # scores - self.tally_ids.append(itally) - self.filter_types = [] - for f in tally.filters: - self.filter_types.append(f) - self.tally_list.append(self.filter_types) - self.score_types = [] - for s in tally.scores: - self.score_types.append(s) - self.tally_scores.append(self.score_types) + # If update() was called by a change in the basis combobox, we don't + # need to repopulate the filters + if widget == self.basisBox: + self.redraw() + return - def on_draw(self): - """ Redraws the figure - """ + # Update scores + self.scoreBox['values'] = selectedTally.scores # self.tally_scores[self.tallyBox.current()] + self.scoreBox.current(0) -# print 'Calling on_draw...' - # Get selected basis, axial_level and stage - basis = self.basis.currentIndex() + 1 - axial_level = self.axial_level.currentIndex() + 1 - is_mean = self.mean.currentIndex() + # Remove any filter labels/comboboxes that exist + for row in range(6, self.selectFrame.grid_size()[1]): + for w in self.selectFrame.grid_slaves(row=row): + w.grid_forget() + w.destroy() - # get current tally index - tally_id = self.tally_ids[self.tally.currentIndex()] + # create a label/combobox for each filter in selected tally + count = 0 + for filterType in selectedTally.filters: + if filterType == 'mesh': + continue + count += 1 + + # Create label and combobox for this filter + label = tk.Label(self.selectFrame, text=self.labels[filterType]) + label.grid(row=count+6, column=0, sticky=tk.W) + combobox = ttk.Combobox(self.selectFrame, state='readonly') + self.filterBoxes[filterType] = combobox + + # Set combobox items + f = selectedTally.filters[filterType] + if filterType in ['energyin', 'energyout']: + combobox['values'] = ['{0} to {1}'.format(*f.bins[i:i+2]) + for i in range(f.length)] + else: + combobox['values'] = [str(i) for i in f.bins] + + combobox.current(0) + combobox.grid(row=count+6, column=1, sticky=tk.W+tk.E) + combobox.bind('<>', self.redraw) + + self.redraw() + + def redraw(self, event=None): + basis = self.basisBox.current() + 1 + axial_level = self.axialBox.current() + 1 + is_mean = self.meanBox.current() + + # Get selected tally + tally_id = self.meshTallies[self.tallyBox.current()] + selectedTally = self.datafile.tallies[tally_id] # Create spec_list spec_list = [] - for tally in self.datafile.tallies[tally_id].filters.values(): - if tally.type == 'mesh': + for f in selectedTally.filters.values(): + if f.type == 'mesh': continue - index = self.boxes[tally.type].currentIndex() - spec_list.append((tally.type, index)) + index = self.filterBoxes[f.type].current() + spec_list.append((f.type, index)) # Take is_mean and convert it to an index of the score score_loc = is_mean if score_loc > 1: score_loc = 1 - if self.basis.currentText() == 'xy': + text = self.basisBox['values'][self.basisBox.current()] + if text == 'xy': matrix = np.zeros((self.nx, self.ny)) for i in range(self.nx): for j in range(self.ny): matrix[i,j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, j + 1, axial_level))], - self.scoreBox.currentIndex())[score_loc] - # Calculate relative uncertainty from absolute, if - # requested + self.scoreBox.current())[score_loc] + # Calculate relative uncertainty from absolute, if requested if is_mean == 2: # Take care to handle zero means when normalizing mean_val = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, j + 1, axial_level))], - self.scoreBox.currentIndex())[0] + self.scoreBox.current())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val else: matrix[i,j] = 0.0 - elif self.basis.currentText() == 'yz': + elif text == 'yz': matrix = np.zeros((self.ny, self.nz)) for i in range(self.ny): for j in range(self.nz): matrix[i,j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (axial_level, i + 1, j + 1))], - self.scoreBox.currentIndex())[score_loc] - # Calculate relative uncertainty from absolute, if - # requested + self.scoreBox.current())[score_loc] + # Calculate relative uncertainty from absolute, if requested if is_mean == 2: # Take care to handle zero means when normalizing mean_val = self.datafile.get_value(tally_id, spec_list + [('mesh', (axial_level, i + 1, j + 1))], - self.scoreBox.currentIndex())[0] + self.scoreBox.current())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val else: @@ -275,20 +247,18 @@ class AppForm(QMainWindow): for j in range(self.nz): matrix[i,j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, axial_level, j + 1))], - self.scoreBox.currentIndex())[score_loc] - # Calculate relative uncertainty from absolute, if - # requested + self.scoreBox.current())[score_loc] + # Calculate relative uncertainty from absolute, if requested if is_mean == 2: # Take care to handle zero means when normalizing mean_val = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, axial_level, j + 1))], - self.scoreBox.currentIndex())[0] + self.scoreBox.current())[0] if mean_val > 0.0: matrix[i,j] = matrix[i,j] / mean_val else: matrix[i,j] = 0.0 -# print spec_list # Clear the figure self.fig.clear() @@ -296,7 +266,7 @@ class AppForm(QMainWindow): # Make figure, set up color bar self.axes = self.fig.add_subplot(111) cax = self.axes.imshow(matrix.transpose(), vmin=0.0, vmax=matrix.max(), - interpolation="nearest", origin='lower') + interpolation='none', origin='lower') self.fig.colorbar(cax) self.axes.set_xticks([]) @@ -305,100 +275,44 @@ class AppForm(QMainWindow): # Draw canvas self.canvas.draw() + + def get_file_data(self, filename): + # Create StatePoint object and read in data + self.datafile = StatePoint(filename) + self.datafile.read_results() + self.datafile.generate_stdev() - def _update(self): - '''Updates widget to display new relevant comboboxes and figure data - ''' -# print 'Calling _update...' + # Find which tallies are mesh tallies + self.meshTallies = [] + for itally, tally in enumerate(self.datafile.tallies): + if 'mesh' in tally.filters: + self.meshTallies.append(itally) - # get current tally index - tally_id = self.tally_ids[self.tally.currentIndex()] + if not self.meshTallies: + tkMessageBox.showerror("Invalid StatePoint File", + "File does not contain mesh tallies!") + sys.exit(1) + - self.mesh = self.datafile.meshes[ - self.datafile.tallies[tally_id].filters['mesh'].bins[0] - 1] +if __name__ == '__main__': + # Hide root window + root = tk.Tk() + root.withdraw() - self.nx, self.ny, self.nz = self.mesh.dimension + # If no filename given as command-line argument, open file dialog + if len(sys.argv) < 2: + filename = tkFileDialog.askopenfilename(title='Select statepoint file', + initialdir='.') + else: + filename = sys.argv[1] - # Clear axial level combobox - self.axial_level.clear() + if filename: + # Check to make sure file exists + if not os.path.isfile(filename): + tkMessageBox.showerror("File not found", + "Could not find regular file: " + filename) + sys.exit(1) - # Repopulate axial level combobox based on current basis selection - if (self.basis.currentText() == 'xy'): - self.axial_level.addItems([str(i+1) for i in range(self.nz)]) - elif (self.basis.currentText() == 'yz'): - self.axial_level.addItems([str(i+1) for i in range(self.nx)]) - else: - self.axial_level.addItems([str(i+1) for i in range(self.ny)]) - - # Determine maximum value from current tally data set - self.maxvalue = self.datafile.tallies[tally_id].results.max() -# print self.maxvalue - - # Clear and hide old filter labels - for item in self.labelList: - item.clear() - - # Clear and hide old filter boxes - for j in self.labels: - self.boxes[j].clear() - self.boxes[j].setParent(None) - - self.update() - - def populate_boxes(self): -# print 'Calling populate_boxes...' - - # get current tally index - tally_id = self.tally_ids[self.tally.currentIndex()] - - n = 5 - labels = {'cell': 'Cell : ', - 'cellborn': 'Cell born: ', - 'surface': 'Surface: ', - 'material': 'Material: ', - 'universe': 'Universe: '} - - # For each filter in newly-selected tally, name a label and fill the - # relevant combobox with options - for element in self.tally_list[self.tally.currentIndex()]: - nextFilter = self.datafile.tallies[tally_id].filters[element] - if element == 'mesh': - continue - - label = QLabel(self.labels[element]) - self.labelList.append(label) - combobox = self.boxes[element] - self.grid.addWidget(label, n, 0) - self.grid.addWidget(combobox, n, 1) - n += 1 - -# print element - if element in ['cell', 'cellborn', 'surface', 'material', 'universe']: - combobox.addItems([str(i) for i in nextFilter.bins]) -# for i in nextFilter.bins: -# print i - - elif element == 'energyin' or element == 'energyout': - for i in range(nextFilter.length): - text = (str(nextFilter.bins[i]) + ' to ' + - str(nextFilter.bins[i+1])) - combobox.addItem(text) - - self.scoreBox.clear() - for item in self.tally_scores[self.tally.currentIndex()]: - self.scoreBox.addItem(str(item)) - self.grid.addWidget(self.score_label, n, 0) - self.grid.addWidget(self.scoreBox, n, 1) - - - -def main(): - app = QApplication(sys.argv) - form = AppForm(app.arguments()) - if form.all_good: - form.show() - app.exec_() - - -if __name__ == "__main__": - main() + app = MeshPlotter(root, filename) + root.deiconify() + root.mainloop() From 39a97d6be5dd34fc4a87123f89c891412e1e5e54 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 20 Jan 2014 19:49:44 -0500 Subject: [PATCH 81/84] Some PEP8 fixes. --- src/utils/plot_mesh_tally.py | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/utils/plot_mesh_tally.py b/src/utils/plot_mesh_tally.py index d22e0e0b4f..fcd6613758 100755 --- a/src/utils/plot_mesh_tally.py +++ b/src/utils/plot_mesh_tally.py @@ -5,8 +5,8 @@ import os import sys -from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigureCanvas -from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as NavigationToolbar +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg from matplotlib.figure import Figure import matplotlib.pyplot as plt import numpy as np @@ -22,6 +22,7 @@ import tkFont import tkMessageBox import ttk + class MeshPlotter(tk.Frame): def __init__(self, parent, filename): tk.Frame.__init__(self, parent) @@ -54,11 +55,11 @@ class MeshPlotter(tk.Frame): # Create the Figure and Canvas self.dpi = 100 self.fig = Figure((5.0, 5.0), dpi=self.dpi) - self.canvas = FigureCanvas(self.fig, master=figureFrame) + self.canvas = FigureCanvasTkAgg(self.fig, master=figureFrame) self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) # Create the navigation toolbar, tied to the canvas - self.mpl_toolbar = NavigationToolbar(self.canvas, figureFrame) + self.mpl_toolbar = NavigationToolbar2TkAgg(self.canvas, figureFrame) self.mpl_toolbar.update() self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) @@ -96,7 +97,7 @@ class MeshPlotter(tk.Frame): labelMean = tk.Label(self.selectFrame, text='Mean/Uncertainty:') labelMean.grid(row=3, column=0, sticky=tk.W) self.meanBox = ttk.Combobox(self.selectFrame, state='readonly') - self.meanBox['values'] = ('Mean', 'Absolute uncertainty', + self.meanBox['values'] = ('Mean', 'Absolute uncertainty', 'Relative uncertainty') self.meanBox.current(0) self.meanBox.grid(row=3, column=1, sticky=tk.W+tk.E) @@ -124,7 +125,8 @@ class MeshPlotter(tk.Frame): selectedTally = self.datafile.tallies[tally_id] # Get mesh for selected tally - self.mesh = self.datafile.meshes[selectedTally.filters['mesh'].bins[0] - 1] + self.mesh = self.datafile.meshes[ + selectedTally.filters['mesh'].bins[0] - 1] # Get mesh dimensions self.nx, self.ny, self.nz = self.mesh.dimension @@ -146,7 +148,7 @@ class MeshPlotter(tk.Frame): return # Update scores - self.scoreBox['values'] = selectedTally.scores # self.tally_scores[self.tallyBox.current()] + self.scoreBox['values'] = selectedTally.scores self.scoreBox.current(0) # Remove any filter labels/comboboxes that exist @@ -209,7 +211,7 @@ class MeshPlotter(tk.Frame): matrix = np.zeros((self.nx, self.ny)) for i in range(self.nx): for j in range(self.ny): - matrix[i,j] = self.datafile.get_value(tally_id, + matrix[i, j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, j + 1, axial_level))], self.scoreBox.current())[score_loc] # Calculate relative uncertainty from absolute, if requested @@ -219,15 +221,15 @@ class MeshPlotter(tk.Frame): spec_list + [('mesh', (i + 1, j + 1, axial_level))], self.scoreBox.current())[0] if mean_val > 0.0: - matrix[i,j] = matrix[i,j] / mean_val + matrix[i, j] = matrix[i, j] / mean_val else: - matrix[i,j] = 0.0 + matrix[i, j] = 0.0 elif text == 'yz': matrix = np.zeros((self.ny, self.nz)) for i in range(self.ny): for j in range(self.nz): - matrix[i,j] = self.datafile.get_value(tally_id, + matrix[i, j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (axial_level, i + 1, j + 1))], self.scoreBox.current())[score_loc] # Calculate relative uncertainty from absolute, if requested @@ -237,15 +239,15 @@ class MeshPlotter(tk.Frame): spec_list + [('mesh', (axial_level, i + 1, j + 1))], self.scoreBox.current())[0] if mean_val > 0.0: - matrix[i,j] = matrix[i,j] / mean_val + matrix[i, j] = matrix[i, j] / mean_val else: - matrix[i,j] = 0.0 + matrix[i, j] = 0.0 else: matrix = np.zeros((self.nx, self.nz)) for i in range(self.nx): for j in range(self.nz): - matrix[i,j] = self.datafile.get_value(tally_id, + matrix[i, j] = self.datafile.get_value(tally_id, spec_list + [('mesh', (i + 1, axial_level, j + 1))], self.scoreBox.current())[score_loc] # Calculate relative uncertainty from absolute, if requested @@ -255,10 +257,9 @@ class MeshPlotter(tk.Frame): spec_list + [('mesh', (i + 1, axial_level, j + 1))], self.scoreBox.current())[0] if mean_val > 0.0: - matrix[i,j] = matrix[i,j] / mean_val + matrix[i, j] = matrix[i, j] / mean_val else: - matrix[i,j] = 0.0 - + matrix[i, j] = 0.0 # Clear the figure self.fig.clear() @@ -275,7 +276,7 @@ class MeshPlotter(tk.Frame): # Draw canvas self.canvas.draw() - + def get_file_data(self, filename): # Create StatePoint object and read in data self.datafile = StatePoint(filename) @@ -292,7 +293,7 @@ class MeshPlotter(tk.Frame): tkMessageBox.showerror("Invalid StatePoint File", "File does not contain mesh tallies!") sys.exit(1) - + if __name__ == '__main__': # Hide root window From 65a25edf63b762a95608f859fbcbd886a19a5453 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Wed, 22 Jan 2014 14:54:10 -0500 Subject: [PATCH 82/84] Reverted to python2 and added None Available text when no Filters are available. --- src/utils/plot_mesh_tally.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/plot_mesh_tally.py b/src/utils/plot_mesh_tally.py index fcd6613758..fb5b8cb839 100755 --- a/src/utils/plot_mesh_tally.py +++ b/src/utils/plot_mesh_tally.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 """Python script to plot tally data generated by OpenMC.""" @@ -182,6 +182,12 @@ class MeshPlotter(tk.Frame): combobox.grid(row=count+6, column=1, sticky=tk.W+tk.E) combobox.bind('<>', self.redraw) + # If There are no filters, leave a 'None available' message + if count == 0: + count += 1 + label = tk.Label(self.selectFrame, text="None Available") + label.grid(row=count+6, column=0, sticky=tk.W) + self.redraw() def redraw(self, event=None): From 7cf22c6c323258f03b758f1c05968160ca6a227c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 27 Jan 2014 20:36:31 -0500 Subject: [PATCH 83/84] Check for empty datapath in xsdir for conversion. --- src/utils/convert_xsdir.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/convert_xsdir.py b/src/utils/convert_xsdir.py index 32315f1ed5..edee1a66c7 100755 --- a/src/utils/convert_xsdir.py +++ b/src/utils/convert_xsdir.py @@ -35,8 +35,12 @@ class Xsdir(object): words = line.split() if words: if words[0].lower().startswith('datapath'): - index = line.index('=') - self.datapath = line[index+1:].strip() + if '=' in words[0]: + index = line.index('=') + self.datapath = line[index+1:].strip() + else: + if len(line.strip()) > 8: + self.datapath = line[8:].strip() else: self.f.seek(0) From ad1235db60cf90bd39c38473908d126131659dce Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 27 Jan 2014 20:37:48 -0500 Subject: [PATCH 84/84] Update one publication in documentation. --- docs/source/publications.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index c17e652c7f..df7ca639bc 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -48,8 +48,8 @@ Publications - Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, "Multi-core performance studies of a Monte Carlo neutron transport code," - *Int. J. High Perform. Comput. Appl.* - (2013). ``_ + *Int. J. High Perform. Comput. Appl.*, **28** (1), 87--96 + (2014). ``_ - Paul K. Romano, Andrew R. Siegel, Benoit Forget, and Kord Smith, "Data decomposition of Monte Carlo particle transport simulations via tally