Commit 2103086f authored by Salko Jr, Robert's avatar Salko Jr, Robert
Browse files

Modify the process subpackage to work with new HDF5 format

The new HDF5 format is not as nicely organized, but it leads to
much smaller file sizes and much faster I/O.  These changes get
all tools working with the new format and add more support for
multi-section models.  A new test was also added to skb, which
has rods starting in different axial sections and spanning multiple
sections.  This was used to generate a very small HDF5 file that
has a lot of features useful for unit testing.  It also uncovered
some issues in skbuild that were fixed and additional error checks
were added.
parent 8f85aa81
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
*.pyc
deck.inp
CtfDeckBuilder.egg-info
SubKit.egg-info
MCFR
.DS_Store
*.png
+1 −2
Original line number Diff line number Diff line
#!/usr/bin/env python
import os
myPath = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import Model
import Section
import InpParse
sys.path.insert(0, myPath)
import numpy as np
import argparse

+2 −1
Original line number Diff line number Diff line
@@ -4,7 +4,8 @@ import BoundaryCondition as BC
import Solid
import os
import sys
import SubKit.utils.utils as utils
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import utils.utils as utils
from subprocess import Popen, PIPE, check_call
mypath = os.path.abspath(os.path.dirname(__file__))
localWaspPath = os.path.join(mypath, "../wasp/build/install")
+160 −18
Original line number Diff line number Diff line
@@ -230,7 +230,33 @@ class Model:
         me.chb[c] = []

   def setConnection(me, chID, cha=None, chb=None):
      """ Use to connect channels in the axial direction.
      """ Use to connect channels in the axial direction

      Note that if the "channels above" (cha) for one channel are logged, it is not a requirement
      to log the "channels below" for that above channel.  SubKit will automatically make these
      connections.  For example, in the following model:

      3 4
      3 4
      1 2
      1 2

      Where 1, 2, 3, and 4 are subchannel IDs with each row being a level in the model, any one
      of the three specifications will achieve the same result:

      Case 1:
      model.setConnection(1, cha=3)
      model.setConnection(2, cha=4)

      Case 2:
      model.setConnection(3, chb=1)
      model.setConnection(4, chb=2)

      Case 3:
      model.setConnection(1, cha=3)
      model.setConnection(2, cha=4)
      model.setConnection(3, chb=1)
      model.setConnection(4, chb=2)

      Args:
         chID (int): The ID of the channel that is having axial connections specified
@@ -344,7 +370,7 @@ class Model:

      Args:
         solidID (choice): The ID that was assigned to the solid in :meth:`addSolid`
         chID (choice): Can be one channel ID or a list of channel IDs.  The ID must correspond to the
         chID (int or list): Can be one channel ID or a list of channel IDs.  The ID must correspond to the
            ID(s) that were used to define the channels in the :meth:`addChannel` method
         percent (float): Specifies the percent of the rod azimuth connected to each channel specified in the
            chID list.  Must add up to 1.0.  Must be the same size as the chID list (or scalar if chID
@@ -373,6 +399,25 @@ class Model:
            me.rodsConnectedToChannel[chans[i]] = []
         me.rodsConnectedToChannel[chans[i]].append(solidID)

   def getSolidSections(me, solidID):
       """ Returns a list of the sections the solid exists in

       Args:
          solidID (int) : solidID

       """
       sections = []
       outer = me.channelsOuter[solidID]
       inner = me.channelsInner[solidID]
       assert(not (outer and inner)) # cannot have both
       allch = outer+inner
       for ch in allch:
           chSec = me.getSectionOfChannel(ch)
           if chSec not in sections:
               sections.append(chSec)
       return sections


   def addSolidInnerChan(me, solidID, chID, percent):
      """ Use for adding a connection between the inerior of the solid and a channel.

@@ -399,21 +444,32 @@ class Model:
         me.rodsConnectedToChannel[chID] = []
      me.rodsConnectedToChannel[chID].append(solidID)

   def getOuterConnections(me, solidID):
   def getOuterConnections(me, solidID, section=None):
      """ Use to get connections to a solid object.

      Args:
         solidID (int) : The unique ID for the solid object
         section (int) : The section for which connections will be returned.  None means all connections
            will be returned.

      Returns:
         (list of channel IDs connected to the rod, list of percent of each connection)

      """
      #CTF deck needs 8 connections, with zeros representing no connection,
      # so fill out the connections list to size 8.'''
      fullList  = [0]*8
      fractList = [0.0]*8
      mySections = me.getSolidSections(solidID)
      if section is None:
          sections = mySections
      else:
          if section not in mySections:
              raise RuntimeError("Specified section: "+str(section)+" for solidID "+str(solidID)+" is not a section in which this solid exists")
          sections = [section]
      chans = []
      percents = []
      for i, ch in enumerate(me.channelsOuter[solidID]):
          fullList[i]  = ch
          fractList[i] = me.percentsOuter[solidID][i]
      return ( fullList, fractList )
          if me.getSectionOfChannel(ch) in sections:
             chans.append(ch)
             percents.append(me.percentsOuter[solidID][i])
      return chans, percents

   def getInnerConnections(me, solidID):
      """ Get inner conenctions to a solid.
@@ -874,6 +930,18 @@ class Model:
         me.channelsInDomain[domain].append(IDsInDomain)
      me.solver = 5

   def getSectionOfChannel(me, chID):
      """ Returns the secID of the section that owns the passed chID

      Args:
         chID (int) : Channel ID

      """
      for secID in me.sections.keys():
         if chID in me.sections[secID].channels:
            return secID
      assert(False) # Fail if not found in any sections

   def _preGenDeckChecks(me):
      """
      This method is run prior to generating the deck to check for model consistency.
@@ -884,8 +952,12 @@ class Model:
         me.axialPowerShapeAxial[0.0] = {}
         me.axialPowerShapePower[0.0] = {}

      me._sectionConnections()

      me._mapBCsToChannels()

      me._validateBoundaryConditions()

      # Add a uniform power profile that covers the entire model so it can be assigned to rods
      # that had now power shape assigned
      if me._anyRodsWithoutPowerID():
@@ -1067,12 +1139,6 @@ class Model:
         me.sectionAxial[secID] = [z, z+me.sections[secID].height]
         z = z+me.sections[secID].height

      def getSectionOfChannel(chID):
         """ Returns the secID of the section that owns the passed chID"""
         for secID in me.sections.keys():
            if chID in me.sections[secID].channels:
               return secID
         assert(False) # Fail if not found in any sections


      # Get the start/end of each rod in the model
@@ -1086,8 +1152,8 @@ class Model:
         endSection = 0
         rodChans = me.channelsOuter[pinID]+me.channelsInner[pinID]
         for chID in rodChans:
            startSection = min(startSection, getSectionOfChannel(chID))
            endSection = max(endSection, getSectionOfChannel(chID))
            startSection = min(startSection, me.getSectionOfChannel(chID))
            endSection = max(endSection, me.getSectionOfChannel(chID))
         rodStarts[pinID] = me.sectionAxial[startSection][0]
         rodEnds[pinID] = me.sectionAxial[endSection][1]

@@ -1135,6 +1201,22 @@ class Model:

      me._chfChecks()

      me._rodsInValidSections()

      me._rodConnectionsValid()




   def _rodsInValidSections(me):
       """ Ensure rods are in continuous sections"""

       for solidID in me.solidObjects:
          sections = me.getSolidSections(solidID)
          for i in range(1, len(sections)):
              if sections[i]!=sections[i-1]+1:
                  raise RuntimeError("Solid: "+str(solidID)+" exists in Sections "+str(sections[i-1])+" and "+str(sections[i])+" which are not sequential")

   def _inferInitialConditionsFromBC(me):
      """ If the IC can be inferred from the channel boundary conditions that will be set, returns
      initial mass flow rate, temperature, and pressure.  If not, returns None."""
@@ -1217,6 +1299,66 @@ class Model:
           # The post-CHF check is not valid for transients, so disable it
           me.conductorOptions['postChfCheck']='none'

   def _sectionConnections(me):
       """Ensure that all connections between sections are consistent"""
       for chID in me.cha:
           for above in me.cha[chID]:
               # Check the above channel's below connections and if they are not logged, log them
               if chID not in me.chb[above]:
                   me.chb[above].append(chID)
       for chID in me.chb:
           for below in me.chb[chID]:
               # Check the below channel's above connection and if this one is not in it, log it
               if chID not in me.cha[below]:
                   me.cha[below].append(chID)

   def _validateBoundaryConditions(me):
       """ Ensure boundary conditions were setup correctly"""
       # Ensure all were defined where expected
       for sec in me.sections.values():
           for chanID in sec.channels.keys():
               chan = sec.channels[chanID]
               if 1 not in chan.bcLevels and len(me.chb[chanID])==0:
                   # No lower BC defined and no channels below
                   warnings.warn("No boundary condition defined for bottom of Channel "+str(chanID))
               elif (sec.nLev+1 not in chan.bcLevels and sec.nLev+2 not in chan.bcLevels) and len(me.cha[chanID])==0:
                   warnings.warn("No boundary condition defined for top of Channel "+str(chanID))
       # Cannot specify mass/temp anywhere but channel bounds
       for sec in me.sections.values():
           topMom = sec.nLev+1
           for chanID in sec.channels.keys():
               chan = sec.channels[chanID]
               for i, bc in enumerate(chan.bcs):
                   if isinstance(bc, BoundaryCondition.MassEnthalpy) and (chan.bcLevels[i]!=1 and chan.bcLevels[i]!=topMom):
                       raise RuntimeError("Set MassEnthalpy BC in Chan: "+str(chanID)+" Level: "+str(chan.bcLevels[i])+" but level must be 1 or "+str(topMom))
                   elif isinstance(bc, BoundaryCondition.MassTemperature) and (chan.bcLevels[i]!=1 and chan.bcLevels[i]!=topMom):
                       raise RuntimeError("Set MassEnthalpy BC in Chan: "+str(chanID)+" Level: "+str(chan.bcLevels[i])+" but level must be 1 or "+str(topMom))

   def _rodConnectionsValid(me):
       """ Ensure rods are 100% connected to surrounding channels in each axial section"""
       for solidID in me.solidObjects:
           # Channels connected to the rod
           outerChan = me.channelsOuter[solidID]
           innerChan = me.channelsInner[solidID]
           if outerChan and innerChan:
               raise RuntimeError("This version of CTF does not support connecting solids to both inner/outer channels.  See solidID: "+str(solidID))
           allChan = outerChan+innerChan
           allPercent = me.percentsOuter[solidID]+me.percentsInner[solidID]
           # Get section IDs of all channels
           sections = []
           for ch in allChan:
              sections.append(me.getSectionOfChannel(ch))
           unique = list(set(sections))
           # Loop through each section connected to this rod
           for u in unique:
               # In each section, ensure that all percentages of connections add up to 1.0
               totPercent = 0.0
               for i, sec in enumerate(sections):
                   if sec==u:
                       totPercent = totPercent+allPercent[i]
               if not np.isclose(totPercent, 1.0):
                   raise RuntimeError("For solidID: "+str(solidID)+" in Section: "+str(u)+" percentages of connected channels add up to "+str(totPercent)+" instead of 1.0")

class Gap(object):
   """ Defines a gap (a lateral connection between two channels in the same axial section)

+14 −8
Original line number Diff line number Diff line
@@ -606,9 +606,14 @@ class SquareLatticeLWR_Nodal(CoreBuilder.Core):
         to zero.  The zeros are blank spots and must match the blank spots in coreMap.  The positive integers
         specify the domain the rod exists in.  This is only needed if modeling a parallel model.  The domains
         should be sequential (1 to N, where N is the number of domains, with no skipped numbers).
      modelGuideTubes (bool): It is likely the guide tubes do not need to be explicitly modeled as conductor
         objects because they are not heated.  Setting this to True will override the default of not modeling
         them.  Note that whether they are modeled or not, their effect on gap size and wall friction are
         always captured in the model.  Not modeling them will save some computational time.
   """
   def __init__(me, model, coreMap, assemMaps, pitch, dz, solidGeoms, startChanIndex=1, assemPitch=None,
            baffleGap=0.0, sectionID=1, symOpt=1, assemLosses=None, assemLossLevels=None, rodDomains=None):
            baffleGap=0.0, sectionID=1, symOpt=1, assemLosses=None, assemLossLevels=None, rodDomains=None,
            modelGuideTubes=False):

      assert(isinstance(model, Model.Model))
      assert(len(coreMap)>0)
@@ -707,9 +712,10 @@ class SquareLatticeLWR_Nodal(CoreBuilder.Core):
            if isinstance(solidGeoDict[solidTypeID], Solid.FuelRod):
               powID=1
               power = 1.0
            else:
            elif modelGuideTubes:
               powID=0
               power = 0.0
            if isinstance(solidGeoDict[solidTypeID], Solid.FuelRod) or modelGuideTubes:
               me.rodsInAssem[assemID].append(solidID)
               [i, j]=chMapObj.getLoc(chIdx)
               model.addSolid(solidID, solidTypeID, Solid.HeatedSolid(mult=mult/4.0, power=power, powID=powID,
Loading