diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index c834bbc92e..20fd66e2f7 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -833,11 +833,13 @@ sub-elements: *Default*: None - :outside: - The unique integer identifier of a material that is to be used to fill all - space outside of the lattice. This element is optional. + :outer: + The unique integer identifier of a universe that will be used to fill all + space outside of the lattice. The universe will be tiled repeatedly as if + it were placed in a lattice of infinite size. This element is optional. - *Default*: The region outside the defined lattice is treated as void. + *Default*: An error will be raised if a particle leaves a lattice with no + outer universe. :universes: A list of the universe numbers that fill each cell of the lattice. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06c044e42c..dd920bfcc0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -269,6 +269,9 @@ install(PROGRAMS utils/statepoint_histogram.py install(PROGRAMS utils/statepoint_meshplot.py DESTINATION bin RENAME statepoint_meshplot) +install(PROGRAMS utils/update_inputs.py + DESTINATION bin + RENAME update_inputs) install(FILES ../man/man1/openmc.1 DESTINATION share/man/man1) install(FILES ../LICENSE DESTINATION "share/doc/${program}/copyright") diff --git a/src/constants.F90 b/src/constants.F90 index 84b289f839..bac301e36a 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -127,6 +127,9 @@ module constants SURF_CONE_Y = 10, & ! Cone parallel to y-axis SURF_CONE_Z = 11 ! Cone parallel to z-axis + ! Flag to say that the outside of a lattice is not defined + integer, parameter :: NO_OUTER_UNIVERSE = -22 + ! Maximum number of lost particles integer, parameter :: MAX_LOST_PARTICLES = 10 diff --git a/src/geometry.F90 b/src/geometry.F90 index c79f0e6119..b7116ebc57 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -280,17 +280,7 @@ contains p % coord % xyz = xyz else - - ! We're outside the lattice, so treat this as a normal cell with - ! the material specified for the outside - outside_lattice = .true. - p % last_material = p % material - p % material = c % material - - ! We'll still make a new coordinate for the particle, as - ! distance_to_boundary will still need to track through lattice - ! widths even though there's nothing in them but this material end if @@ -322,13 +312,13 @@ contains if (.not. outside_lattice) then p % coord % next % universe = lat % universes(i_x,i_y,i_z) else - - ! Set universe as the same for subsequent calls to find_cell - p % coord % next % universe = p % coord % universe - - ! Set coord cell for calls to distance_to_boundary - p % coord % next % cell = index_cell - + if (lat % outer == NO_OUTER_UNIVERSE) then + call fatal_error("A particle is outside latttice " & + &// trim(to_str(lat % id)) // " but the lattice has no & + &defined outer universe.") + else + p % coord % next % universe = lat % outer + end if end if ! Move particle to next level @@ -336,10 +326,9 @@ contains end if - if (.not. outside_lattice) then - call find_cell(p, found) - if (.not. found) exit - end if + ! Find in the next lowest coordinate level. + call find_cell(p, found) + if (.not. found) exit end if diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 93863865c4..f085c64728 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -30,7 +30,7 @@ module geometry_header real(8), allocatable :: lower_left(:) ! lower-left corner of lattice real(8), allocatable :: width(:) ! width of each lattice cell integer, allocatable :: universes(:,:,:) ! specified universes - integer :: outside ! material to fill area outside + integer :: outer ! universe to tile outside the lat end type Lattice !=============================================================================== diff --git a/src/initialize.F90 b/src/initialize.F90 index 1ab34ca0bf..ec64a1f19b 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -549,7 +549,7 @@ contains integer :: j ! index for various purposes integer :: k ! loop index for lattices integer :: m ! loop index for lattices - integer :: mid, lid ! material and lattice IDs + integer :: lid ! lattice IDs integer :: n_x, n_y, n_z ! size of lattice integer :: i_array ! index in surfaces/materials array integer :: id ! user-specified id @@ -607,18 +607,8 @@ contains c % fill = universe_dict % get_key(id) elseif (lattice_dict % has_key(id)) then lid = lattice_dict % get_key(id) - mid = lattices(lid) % outside c % type = CELL_LATTICE c % fill = lid - if (mid == MATERIAL_VOID) then - c % material = mid - else if (material_dict % has_key(mid)) then - c % material = material_dict % get_key(mid) - else - call fatal_error("Could not find material " // trim(to_str(mid)) & - &// " specified on lattice " // trim(to_str(lid))) - end if - else call fatal_error("Specified fill " // trim(to_str(id)) // " on cell "& &// trim(to_str(c % id)) // " is neither a universe nor a & @@ -654,6 +644,16 @@ contains end do end do + if (lat % outer /= NO_OUTER_UNIVERSE) then + if (universe_dict % has_key(lat % outer)) then + lat % outer = universe_dict % get_key(lat % outer) + else + call fatal_error("Invalid universe number " & + &// trim(to_str(lat % outer)) & + &// " specified on lattice " // trim(to_str(lat % id))) + end if + end if + end do TALLY_LOOP: do i = 1, n_tallies diff --git a/src/input_xml.F90 b/src/input_xml.F90 index e5c21bdc1c..292b32e066 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -904,7 +904,6 @@ contains integer :: universe_num integer :: n_cells_in_univ integer :: coeffs_reqd - integer :: mid integer :: temp_int_array3(3) integer, allocatable :: temp_int_array(:) real(8) :: phi, theta, psi @@ -1340,15 +1339,18 @@ contains end do deallocate(temp_int_array) - ! Read material for area outside lattice - lat % outside = MATERIAL_VOID + ! Read outer universe for area outside lattice. + lat % outer = NO_OUTER_UNIVERSE + if (check_for_node(node_lat, "outer")) then + call get_node_value(node_lat, "outer", lat % outer) + end if + + ! Check for 'outside' nodes which are no longer supported. if (check_for_node(node_lat, "outside")) then - call get_node_value(node_lat, "outside", mid) - if (mid == 0 .or. mid == MATERIAL_VOID) then - lat % outside = MATERIAL_VOID - else - lat % outside = mid - end if + call fatal_error("The use of 'outside' in lattices is no longer & + &supported. Instead, use 'outer' which defines a universe rather & + &than a material. The utility openmc/src/utils/update_inputs.py & + &can be used automatically replace 'outside' with 'outer'.") end if ! Add lattice to dictionary diff --git a/src/relaxng/geometry.rnc b/src/relaxng/geometry.rnc index 62827f04c1..e93c8a9a83 100644 --- a/src/relaxng/geometry.rnc +++ b/src/relaxng/geometry.rnc @@ -30,6 +30,6 @@ element geometry { (element lower_left { list { xsd:double+ } } | attribute lower_left { list { xsd:double+ } }) & (element width { list { xsd:double+ } } | attribute width { list { xsd:double+ } }) & (element universes { list { xsd:int+ } } | attribute universes { list { xsd:int+ } }) & - (element outside { xsd:int } | attribute outside { xsd:int })? + (element outer { xsd:int } | attribute outer { xsd:int })? }* } diff --git a/src/utils/update_inputs.py b/src/utils/update_inputs.py new file mode 100755 index 0000000000..cd6f98b63c --- /dev/null +++ b/src/utils/update_inputs.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +"""Update OpenMC's input XML files to the latest format. + +Usage information can be obtained by running 'update_inputs.py --help': + +usage: update_lattices.py [-h] IN [IN ...] + +Update lattices in geometry.xml files to the latest format. This will remove +'outside' attributes/elements and replace them with 'outer' attributes. Note +that this script will not delete the given files; it will append '.original' +to the given files and write new ones. + +positional arguments: + IN Input geometry.xml file(s). + +optional arguments: + -h, --help show this help message and exit + +""" + +from __future__ import print_function + +import argparse +from random import randint +from shutil import move +import xml.etree.ElementTree as ET + + +description = "Update OpenMC's input XML files to the latest format." +epilog = """\ +If any of the given files do not match the most up-to-date formatting, then they +will be automatically rewritten. The old out-of-date files will not be deleted; +they will be moved to a new file with '.original' appended to their name. + +Formatting changes that will be made: + +geometry.xml: Lattices containing 'outside' attributes/tags will be replaced + with lattices containing 'outer' attributes, and the appropriate + cells/universes will be added. +""" + + +def parse_args(): + """Read the input files from the commandline.""" + # Create argument parser. + parser = argparse.ArgumentParser( + description=description, + epilog=epilog, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('input', metavar='IN', type=str, nargs='+', + help='Input geometry.xml file(s).') + + # Parse and return commandline arguments. + return parser.parse_args() + + +def get_universe_ids(geometry_root): + """Return a set of universe id numbers.""" + root = geometry_root + out = {0} + + # Get the ids of universes defined by cells. + for cell in root.iter('cell'): + # Get universe attributes. + if 'universe' in cell.attrib: + uid = cell.attrib['universe'] + out.add(int(uid)) + + # Get universe elements. + elif cell.find('universe') is not None: + elem = cell.find('universe') + uid = elem.text + out.add(int(uid)) + + # Get the ids of universes defined by lattices. + for lat in root.iter('lattice'): + # Get id attributes. + if 'id' in lat.attrib: + uid = lat.attrib['id'] + out.add(int(uid)) + + # Get id elements. + elif lat.find('id') is not None: + elem = lat.find('id') + uid = elem.text + out.add(int(uid)) + + return out + + +def get_cell_ids(geometry_root): + """Return a set of cell id numbers.""" + root = geometry_root + out = set() + + # Get the ids of universes defined by cells. + for cell in root.iter('cell'): + # Get id attributes. + if 'id' in cell.attrib: + cid = cell.attrib['id'] + out.add(int(cid)) + + # Get id elements. + elif cell.find('id') is not None: + elem = cell.find('id') + cid = elem.text + out.add(int(cid)) + + return out + + +def find_new_id(current_ids, preferred=None): + """Return a new id that is not already present in current_ids.""" + distance_from_preferred = 21 + max_random_attempts = 10000 + + # First, try to find an id near the preferred number. + if preferred is not None: + assert isinstance(preferred, int) + for i in range(1, distance_from_preferred): + if (preferred - i not in current_ids) and (preferred - i > 0): + return preferred - i + if (preferred + i not in current_ids) and (preferred + i > 0): + return preferred + i + + # If that was unsuccessful, attempt to randomly guess a new id number. + for i in range(max_random_attempts): + num = randint(1, 2147483647) + if num not in current_inds: + return num + + # Raise an error if an id was not found. + raise RuntimeError('Could not find a unique id number for a new universe.') + + +def get_lat_id(lattice_element): + """Return the id integer of the lattice_element.""" + assert isinstance(lattice_element, ET.Element) + if 'id' in lattice_element.attrib: + return int(lattice_element.attrib['id'].strip()) + elif any([child.tag == 'id' for child in lattice_element]): + elem = lattice_element.find('id') + return int(elem.text.strip()) + else: + raise RuntimeError('Could not find the id for a lattice.') + + +def pop_lat_outside(lattice_element): + """Return lattice's outside material and remove from attributes/elements.""" + assert isinstance(lattice_element, ET.Element) + + # Check attributes. + if 'outside' in lattice_element.attrib: + material = lattice_element.attrib['outside'].strip() + del lattice_element.attrib['outside'] + + # Check subelements. + elif any([child.tag == 'outside' for child in lattice_element]): + elem = lattice_element.find('outside') + material = elem.text.strip() + lattice_element.remove(elem) + + # No 'outside' specified. This means the outside is a void. + else: + material = 'void' + + return material + + +def update_geometry(geometry_root): + """Update the given XML geometry tree. Return True if changes were made.""" + root = geometry_root + was_updated = False + + # Ignore files that do not contain lattices. + if all([child.tag != 'lattice' for child in root]): return False + + # Get a set of already-used universe and cell ids. + uids = get_universe_ids(root) + cids = get_cell_ids(root) + taken_ids = uids.union(cids) + + # Update the definitions of each lattice + for lat in root.iter('lattice'): + # Get the lattice's id. + lat_id = get_lat_id(lat) + + # Ignore lattices that have 'outer' specified. + if any([child.tag == 'outer' for child in lat]): continue + + # Pop the 'outside' material. + material = pop_lat_outside(lat) + + # Get an id number for a new outer universe. Ideally, the id should + # be close to the lattice's id. + new_uid = find_new_id(taken_ids, preferred=lat_id) + assert new_uid not in taken_ids + + # Add the new universe filled with the old 'outside' material to the + # geometry. + new_cell = ET.Element('cell') + new_cell.attrib['id'] = str(new_uid) + new_cell.attrib['universe'] = str(new_uid) + new_cell.attrib['material'] = material + root.append(new_cell) + taken_ids.add(new_uid) + + # Add the new universe to the lattice's 'outer' attribute. + lat.attrib['outer'] = str(new_uid) + + was_updated = True + + return was_updated + + +if __name__ == '__main__': + args = parse_args() + for fname in args.input: + # Parse the XML data. + tree = ET.parse(fname) + root = tree.getroot() + was_updated = False + + if root.tag == 'geometry': + was_updated = update_geometry(root) + + if was_updated: + # Move the original geometry file to preserve it. + move(fname, fname + '.original') + + # Write a new geometry file. + tree.write(fname) diff --git a/tests/test_infinite_cell/geometry.xml b/tests/test_infinite_cell/geometry.xml index 77ef6110a9..b16d7f497c 100644 --- a/tests/test_infinite_cell/geometry.xml +++ b/tests/test_infinite_cell/geometry.xml @@ -4,12 +4,13 @@ + width="2.0 2.0" outer="22"> 11 12 12 11 +