Source code for fudge.gnds.product

# <<BEGIN-copyright>>
# <<END-copyright>>

"""
This module contains the product class.

GNDS classes store reaction data in a hierarchical format. At the top is the product class. Next is the
channel, representing the reaction output channels. An output channel contains the Q-value and a list
of outgoing products (some of which may decay into other particles).
The channel is stored inside the reaction class, which consists of an input and output channel together,
(e.g. n_1 + O_16  --> H_1 + N_16). Finally, the reactionSuite class is used to store a list of reaction
for a given input channel (that is, for the incoming particles that come together in the reaction).
The following outline summarizes the classes.

:product:
    Outgoing particle, including multiplicity and distribution information.

:channel:
    A Q-value plus a list of products.

:reaction:
    Contains a cross section (defined in the gnds.reactionData.crossSection module), along with
    an outgoing channel which must be one of the classes defined in gnds.channels (e.g., 2n + Pu239).
    
:reactionSuite:
    Contains all reactions sharing the same input channel (e.g. n + Pu239)
    
        (e.g., n + Pu239 --> n + Pu239, n + Pu239 --> n + Pu239_e1,
        n + Pu239 --> n + Pu239_e2, n + Pu239 --> 2n + Pu239 ... )
"""

from pqu import PQU as PQUModule

from PoPs import IDs as IDsPoPsModule
from PoPs import misc as miscPoPsModule
from PoPs.families import nuclide as nuclidePoPsModule

from fudge.core.utilities import brb

from xData import ancestry as ancestryModule
from xData import physicalQuantity as physicalQuantityModule

from fudge.processing import group as groupModule

from fudge.gnds import suites as suitesModule

from .productData.distributions import distribution as distributionModule
from .productData.distributions import unspecified as unspecifiedModule
from .productData import multiplicity as multiplicityModule
from .productData import energyDeposition as energyDepositionModule
from .productData import momentumDeposition as momentumDepositionModule
from . import channels as channelsModule

__metaclass__ = type

[docs]class product( ancestryModule.ancestry ) : """ This is the class for a gnds product. If the product can decay (e.g. for breakup reactions), the resulting decay information is defined in the product outputChannel """ moniker = 'product' ancestryMembers = ( 'multiplicity', 'distribution', 'outputChannel', 'energyDeposition', 'momentumDeposition' ) def __init__( self, id, label = None, attributes = None, outputChannel = None ) : """Creates a new product object.""" ancestryModule.ancestry.__init__( self ) self.__id = id self.__label = label self.__particle = None # lazy evaluation self.attributes = {} if attributes is not None: for q in attributes : self.addAttribute( q, attributes[q] ) self.outputChannel = None if( outputChannel is not None ) : self.addOutputChannel( outputChannel ) self.__multiplicity = multiplicityModule.component( ) self.__multiplicity.setAncestor( self ) self.__energyDeposition = energyDepositionModule.component( ) self.__energyDeposition.setAncestor( self ) self.__momentumDeposition = momentumDepositionModule.component( ) self.__momentumDeposition.setAncestor( self ) self.__distribution = distributionModule.component( ) self.__distribution.setAncestor( self )
[docs] def cullStyles( self, styleList ) : self.__multiplicity.cullStyles( styleList ) self.__energyDeposition.cullStyles( styleList ) self.__momentumDeposition.cullStyles( styleList ) self.__distribution.cullStyles( styleList )
def __cmp__( self, other ) : """Compares self to other.""" if( self.id < other.id ) : return( -1 ) if( self.id > other.id ) : return( 1 ) if( self.outputChannel < other.outputChannel ) : return( -1 ) if( self.outputChannel > other.outputChannel ) : return( 1 ) return( 0 ) def __str__( self ) : """Converts product object to a string representation.""" return( self.toString( simpleString = False ) ) @property def id( self ) : return( self.__id ) @property def pid( self ) : return( self.id ) @property def particle( self ) : if( self.__particle is None ) : pops = self.findAttributeInAncestry( 'PoPs' ) try : self.__particle = pops[self.id] except : baseName, anti, qualifier = miscPoPsModule.baseAntiQualifierFromID( self.id, qualifierAllowed = True ) name = baseName + anti self.__particle = pops.chemicalElements.getSymbol( name ) return( self.__particle ) @property def label( self ) : """Returns self's label.""" return( self.__label ) @label.setter def label( self, value ) : if( not( isinstance( value, str ) ) ) : raise TypeError( 'label must be a string' ) self.__label = value @property def multiplicity( self ) : return( self.__multiplicity ) @property def energyDeposition( self ) : return( self.__energyDeposition ) @property def momentumDeposition( self ) : return( self.__momentumDeposition ) @property def distribution( self ) : return( self.__distribution )
[docs] def addOutputChannel( self, outputChannel ) : """Adds outputChannel to particle.""" if( isinstance( outputChannel, channelsModule.channel ) ) : self.outputChannel = outputChannel self.outputChannel.setAncestor( self ) else : raise TypeError( 'Invalid decay channel = %s' % brb.getType( outputChannel ) )
[docs] def addAttribute( self, name, value ) : """Add name and value to attribute list.""" self.attributes[name] = value
[docs] def convertUnits( self, unitMap ) : "See documentation for reactionSuite.convertUnits." self.multiplicity.convertUnits( unitMap ) self.energyDeposition.convertUnits( unitMap ) self.momentumDeposition.convertUnits( unitMap ) self.distribution.convertUnits( unitMap ) if( self.outputChannel is not None ) : self.outputChannel.convertUnits( unitMap )
[docs] def checkProductFrame( self ) : """ Calls checkProductFrame for self's distributions and if present for its outputChannel. """ self.distribution.checkProductFrame( ) if( self.outputChannel is not None ) : self.outputChannel.checkProductFrame( )
@property def domainMin( self ) : return( self.multiplicity.domainMin ) @property def domainMax( self ) : return( self.multiplicity.domainMax ) @property def domainUnit( self ) : return( self.multiplicity.domainUnit )
[docs] def thresholdQAs( self, unit, final = True ) : if( self.outputChannel is not None ) : return( self.outputChannel.thresholdQAs( unit, final = final ) ) return( 0. )
[docs] def getLevelAsFloat( self, unit, default = 0. ) : if( hasattr( self.particle, 'getLevelAsFloat' ) ) : return( self.particle.getLevelAsFloat( unit, default = default ) ) return( default )
[docs] def getMass( self, unit ) : """Returns the mass of the particle if possible, otherwise None is returned.""" particle = self.particle PoPs = self.findAttributeInAncestry('PoPs') if particle.id in PoPs.aliases: particle = PoPs[particle.pid] return( particle.getMass( unit ) )
[docs] def getAttribute( self, name ) : """Returns value for attribute name if it exists; otherwise, returns None.""" if( name in self.attributes ) : return( self.attributes[name] ) return( None )
[docs] def calculateAverageProductData( self, style, indent, **kwargs ) : verbosity = kwargs['verbosity'] indent2 = indent + kwargs['incrementalIndent'] indent3 = indent2 + kwargs['incrementalIndent'] reactionSuite = kwargs['reactionSuite'] energyUnit = kwargs['incidentEnergyUnit'] momentumDepositionUnit = kwargs['momentumDepositionUnit'] massUnit = kwargs['massUnit'] energyAccuracy = kwargs['energyAccuracy'] momentumAccuracy = kwargs['momentumAccuracy'] kwargs['product'] = self try : kwargs['productMass'] = reactionSuite.PoPs[self.id].getMass( massUnit ) except : # Can happend when mass is not needed for evaluation and hence not stored. kwargs['productMass'] = None if( verbosity > 1 ) : print '%s%s: label = %s: calculating average product data' % ( indent, self.id, self.label ) if( len( self.distribution ) > 0 ) : multiplicity = style.findFormMatchingDerivedStyle( self.multiplicity ) if( not( isinstance( multiplicity, multiplicityModule.branching1d ) ) ) : kwargs['multiplicity'] = multiplicity.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = 1e-8 ) energyData, momentumData = self.distribution.calculateAverageProductData( style, indent = indent2, **kwargs ) if( energyData is not None ) : axes = energyDepositionModule.defaultAxes( energyUnit = energyUnit ) if( len( energyData ) == 1 ) : averageEnergy = energyDepositionModule.XYs1d( data = energyData[0], axes = axes, label = style.label ) else : averageEnergy = energyDepositionModule.regions1d( axes = axes, label = style.label ) for energyDataRegion in energyData : averageEnergyRegion = energyDepositionModule.XYs1d( data = energyDataRegion, axes = axes ) averageEnergy.append( averageEnergyRegion ) self.energyDeposition.add( averageEnergy ) if( momentumData is not None ) : axes = momentumDepositionModule.defaultAxes( energyUnit = energyUnit, momentumDepositionUnit = momentumDepositionUnit ) if( len( momentumData ) == 1 ) : averageMomentum = momentumDepositionModule.XYs1d( data = momentumData[0], axes = axes, label = style.label ) else : averageMomentum = momentumDepositionModule.regions1d( axes = axes, label = style.label ) for momentumDataRegion in momentumData : averageMomentumRegion = momentumDepositionModule.XYs1d( data = momentumDataRegion, axes = axes ) averageMomentum.append( averageMomentumRegion ) self.momentumDeposition.add( averageMomentum ) if( self.outputChannel is not None ) : self.outputChannel.calculateAverageProductData( style, indent = indent3, **kwargs )
[docs] def processMC_cdf( self, style, tempInfo, indent = '', incrementalIndent = ' ' ) : indent2 = indent + tempInfo['incrementalIndent'] verbosity = tempInfo['verbosity'] if( verbosity > 1 ) : print '%s%s: label = %s: MonteCarlo_cdf processing' % ( indent, self.id, self.label ) self.distribution.processMC_cdf( style, tempInfo, indent ) if( self.outputChannel is not None ) : self.outputChannel.processMC_cdf( style, tempInfo, indent2 )
[docs] def processMultiGroup( self, style, tempInfo, indent ) : indent2 = indent + tempInfo['incrementalIndent'] verbosity = tempInfo['verbosity'] tempInfo['workFile'].append( self.label ) doIt = not( isinstance( self.distribution[0], unspecifiedModule.form ) ) if( doIt and ( self.id in style.transportables ) ) : if( verbosity > 1 ) : print '%s%s: label = %s: multiGroup processing' % ( indent, self.id, self.label ) productMass = tempInfo['masses']['Product'] # Save to restore later tempInfo['masses']['Product'] = self.getMass( tempInfo['massUnit'] ) tempInfo['product'] = self tempInfo['multiplicity'] = self.multiplicity self.multiplicity.processMultiGroup( style, tempInfo, indent ) self.energyDeposition.processMultiGroup( style, tempInfo, indent ) self.momentumDeposition.processMultiGroup( style, tempInfo, indent ) try : self.distribution.processMultiGroup( style, tempInfo, indent ) except : if( tempInfo['logFile'] is None ) : raise else : import traceback tempInfo['logFile'].write( '\n' + self.toXLink() + ':\n' + traceback.format_exc( ) + '\n' ) tempInfo['failures'] += 1 tempInfo['masses']['Product'] = productMass if( self.outputChannel is not None ) : self.outputChannel.processMultiGroup( style, tempInfo, indent2 ) del tempInfo['workFile'][-1]
[docs] def check( self, info ): """ check product multiplicity, distribution and breakup products (if applicable) """ from fudge.gnds import warning warnings = [] multWarnings = self.multiplicity.check( info ) if multWarnings: warnings.append( warning.context("Multiplicity:", multWarnings) ) if( ( self.label in info['transportables'] ) and ( not self.distribution.hasData( ) ) ) : warnings.append( warning.missingDistribution( self.label, self ) ) distributionWarnings = self.distribution.check( info ) if distributionWarnings: warnings.append( warning.context("Distribution:", distributionWarnings) ) if self.outputChannel is not None: parentIsTwoBody = info['isTwoBody'] info['isTwoBody'] = isinstance( self.outputChannel, channelsModule.twoBodyOutputChannel ) for decayProduct in self.outputChannel: decayWarnings = decayProduct.check( info ) if decayWarnings: warnings.append( warning.context("Decay product: %s" % decayProduct.label, decayWarnings) ) info['isTwoBody'] = parentIsTwoBody # reset to parent channel return warnings
[docs] def toXMLList( self, indent = '', **kwargs ) : indent2 = indent + kwargs.get( 'incrementalIndent', ' ' ) attributeString = '' for q in sorted(self.attributes) : if( q in [ 'discrete', 'decayRate', 'primary' ] ) : attributeString += ' %s="%s"' % ( q, self.attributes[q].toString( keepPeriod = False ) ) else : attributeString += ' %s="%s"' % ( q, self.attributes[q] ) xmlString = [ '%s<%s pid="%s" label="%s"%s>' % ( indent, self.moniker, self.id, self.label, attributeString ) ] xmlString += self.multiplicity.toXMLList( indent2, **kwargs ) xmlString += self.distribution.toXMLList( indent2, **kwargs ) xmlString += self.energyDeposition.toXMLList( indent2, **kwargs ) xmlString += self.momentumDeposition.toXMLList( indent2, **kwargs ) if( self.outputChannel is not None ) : xmlString += self.outputChannel.toXMLList( indent2, **kwargs ) xmlString[-1] += '</%s>' % self.moniker return( xmlString )
[docs] def toString( self, simpleString = False, exposeGammaMultiplicity = False ) : """ Returns a string representation of self. If simpleString is True, the string contains only the final particles, and not any intermediate particles. """ def multiplicityString( ) : multiplicity = '' if( ( self.id != IDsPoPsModule.photon ) or exposeGammaMultiplicity ) : _multiplicity = self.multiplicity.evaluated if( isinstance( _multiplicity, multiplicityModule.constant1d ) ) : iValue = int( _multiplicity.constant ) if( iValue == _multiplicity.constant ) : if( iValue != 1 ) : multiplicity = '%s' % iValue else : multiplicity = '%s ' % multiplicity.constant else : multiplicity = 'm(E)*' return( multiplicity ) multiplicity = multiplicityString( ) if( simpleString == True ) : productName = '%s%s' % ( multiplicity, self.id ) else : productName = self.id qs = '' c = '[' for q in self.attributes : v = self.attributes[q] qs += "%s%s:'%s'" % ( c, q, v ) c = ', ' if( len( qs ) > 0 ) : qs += ']' productName = '%s%s%s' % ( multiplicity, productName, qs ) if( self.outputChannel is not None ) : productName = '(%s -> %s)' % ( productName, self.outputChannel ) return( productName )
[docs] @staticmethod def parseXMLNode( productElement, xPath, linkData ): """Translate a <product> element from xml.""" def parseChildNode( moniker, parser ) : child = productElement.find( moniker ) if( child is not None ) : parser( child, xPath, linkData ) xPath.append( '%s[@label="%s"]' % ( productElement.tag, productElement.get( 'label' ) ) ) attrs = dict( productElement.items( ) ) prod = product( id = attrs.pop( 'pid' ), label = attrs.pop( 'label' ) ) parseChildNode( multiplicityModule.component.moniker, prod.multiplicity.parseXMLNode ) parseChildNode( distributionModule.component.moniker, prod.distribution.parseXMLNode ) outputChannel = productElement.find( channelsModule.outputChannelToken ) if( outputChannel is not None ) : prod.addOutputChannel( channelsModule.parseXMLNode( outputChannel, xPath, linkData ) ) for attr in ( 'decayRate', 'primary', 'discrete' ) : if( attr in attrs ) : attrs[attr] = PQUModule.PQU( attrs[attr] ) prod.attributes = attrs parseChildNode( energyDepositionModule.component.moniker, prod.energyDeposition.parseXMLNode ) parseChildNode( momentumDepositionModule.component.moniker, prod.momentumDeposition.parseXMLNode ) xPath.pop( ) return( prod )
[docs]class products( suitesModule.suite ) : moniker = 'products' def __init__( self ) : suitesModule.suite.__init__( self, ( product, ) )
[docs] def uniqueLabel( self, product ) : """ If product's label is the same as another product's label in self, construct a new unique label based on product's name appended with '__' and one or more lower case letters (i.e., 'a' to 'z'). """ if( product.label is None ) : product.label = product.id return( suitesModule.suite.uniqueLabel( self, product ) )