Source code for fudge.gnds.reactionData.crossSection

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

import abc
import math

from pqu import PQU as PQUModule

from numericalFunctions import pointwiseXY as pointwiseXYClass

from xData import ancestry as ancestryModule
from xData import standards as standardsModule
from xData import axes as axesModule
from xData import link as linkModule
from xData import values as valuesModule
from xData import Ys1d as Ys1dModule
from xData import XYs as XYsModule
from xData import uncertainties as uncertaintiesModule
from xData import regions as regionsModule
from xData import gridded as griddedModule

from fudge.processing import group as groupModule

from fudge.gnds import abstractClasses as abstractClassesModule
from fudge.gnds import tokens as tokensModule
from fudge.gnds import styles as stylesModule

__metaclass__ = type

lowerEps = 1e-8
upperEps = 1e-8

[docs]def defaultAxes( energyUnit ) : axes = axesModule.axes( rank = 2 ) axes[0] = axesModule.axis( component.moniker, 0, 'b' ) axes[1] = axesModule.axis( 'energy_in', 1, energyUnit ) return( axes )
# # crossSection forms. #
[docs]class baseCrossSectionForm( abstractClassesModule.form ) : pass
[docs]class Ys1d( baseCrossSectionForm, Ys1dModule.Ys1d ) :
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : if( 'cls' not in kwargs ) : kwargs['cls'] = XYs1d return( Ys1dModule.Ys1d.toPointwise_withLinearXYs( self, **kwargs ) )
[docs]class XYs1d( baseCrossSectionForm, XYsModule.XYs1d ) : mutableYUnit = False def __init__( self, **kwargs ) : XYsModule.XYs1d.__init__( self, **kwargs )
[docs] def changeInterpolation( self, interpolation, accuracy, lowerEps = 0, upperEps = 0, cls = None ) : def func( x, parameters ) : self, T, subParameters = parameters x1, y1, x2, y2, f1, f2 = subParameters if( ( x is None ) or ( x < x1 ) or ( x > x2 ) ) : index = self.lowerIndexBoundingX( x ) x1, y1 = self[index] if( ( index + 1 ) == len( self ) ) : return( y1 ) x2, y2 = self[index+1] f1 = 1 / math.sqrt( x1 - T ) f2 = 1 / math.sqrt( x2 - T ) parameters[2] = [ x1, y1, x2, y2, f1, f2 ] if( x == x1 ) : return( y1 ) if( x == x2 ) : return( y2 ) f = 1 / math.sqrt( x - T ) alpha = ( f - f1 ) / ( f2 - f1 ) return( math.pow( x1 * y1, 1 - alpha ) * math.pow( x2 * y2, alpha ) / x ) if( cls is None ) : cls = XYs1d if( self.interpolation == standardsModule.interpolation.chargedParticleToken ) : if( interpolation != standardsModule.interpolation.linlinToken ) : raise TypeError( 'Only "%s" interpolation for conversion from %s' % ( standardsModule.interpolation.linlinToken, self.interpolation ) ) temp = self.cloneToInterpolation( interpolation ) parameters = [ temp, 0, [ None, None, None, None, None, None ] ] return( temp.applyFunction( func, parameters, accuracy = accuracy, biSectionMax = -1, checkForRoots = False ) ) return( XYsModule.XYs1d.changeInterpolation( self, interpolation, accuracy, lowerEps = lowerEps, upperEps = upperEps, cls = cls ) )
[docs] def evaluate( self, xValue ) : if( self.interpolation == standardsModule.interpolation.chargedParticleToken ) : if( xValue > self.domainMax ) : raise Exception( 'Evaluation of "%s" intepolation not support above domain' % self.interpolation ) elif( xValue < self.domainMin ) : index = 0 else : index = self.lowerIndexBoundingX( xValue ) T = 0.0 x1, y1 = self[index] x2, y2 = self[index+1] f1 = 1.0 / math.sqrt( x1 - T ) f2 = 1.0 / math.sqrt( x2 - T ) fValue = 1.0 / math.sqrt( xValue - T ) alpha = ( fValue - f1 ) / ( f2 - f1 ) return( math.pow( x1 * y1, 1.0 - alpha ) * math.pow( x2 * y2, alpha ) / xValue ) return( XYsModule.XYs1d.evaluate( self, xValue ) )
[docs] def heat( self, currentTemperature, newTemperature, massRatio, EMin, lowerlimit = None, upperlimit = None, interpolationAccuracy = 0.001, heatAllPoints = False, doNotThin = True, heatBelowThreshold = True, heatAllEDomain = True, setThresholdToZero = False ) : """ Returns a linear version of the cross section heated to 'newTemperature'. If the current temperature of the cross section, given by 'currentTemperature', is greater than 'newTemperature' a raise is executed. If lowerlimit is None, it is set to 'oneOverV' except when the reaction is determined to be a threshold reaction, then it is set to 'threshold'. Any cross section with domainMin greater than 2.5e-4 eV is determined to be a threshold reaction. If upperlimit is None it is set to 'constant'. If heatBelowThreshold is False, then EMin is set to the larger of EMin and self's domainMin. The unit of 'currentTemperature' and 'newTemperature' must be the same as the self's domain unit. For more information on EMin, lowerlimit, upperlimit, interpolationAccuracy, heatAllPoints, doNotThin and heatAllEDomain see the module crossSectionAdjustForHeatedTarget. """ from crossSectionAdjustForHeatedTarget import heat dT = newTemperature - currentTemperature if( abs( dT ) <= 1e-2 * newTemperature ) : dT = 0. if( dT < 0 ) : raise Exception( 'Current temperature "%s" (in energy units) higher than desired temperature "%s"' % ( currentTemperature, newTemperature ) ) heated = unheated = self if( not( unheated.isInterpolationLinear( ) ) ) : unheated = self.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = upperEps ) if( ( len( unheated ) > 0 ) and ( dT > 0. ) ) : if( massRatio == 0.0 ) : raise Exception( "Heating with gamma as projectile not supported." ) if( not( heatBelowThreshold ) ) : EMin = max( unheated.domainMin, EMin ) if( lowerlimit is None ) : lowerlimit = "oneOverV" # BRB6 Hardwired. domainMin = PQUModule.PQU( 1e-5, 'eV' ).getValueAs( self.domainUnit ) if( domainMin < 0.04 * unheated.domainMin ) : lowerlimit = "threshold" if( upperlimit is None ) : upperlimit = 'constant' heated = heat.crossSectionAdjustForHeatedTarget( massRatio, dT, EMin, unheated, lowerlimit = lowerlimit, upperlimit = upperlimit, interpolationAccuracy = interpolationAccuracy, heatAllPoints = heatAllPoints, doNotThin = doNotThin, heatAllEDomain = heatAllEDomain ) if( ( unheated[0][1] == 0.0 ) and setThresholdToZero ) : heated[0][1] = 0.0 heated = XYs1d( data = heated, axes = unheated.axes ) heated = heated.thin( interpolationAccuracy / 2 ) # crossSectionAdjustForHeatedTarget is not doing a good job of thinning. return( heated )
[docs] def processMultiGroup( self, style, tempInfo, indent ) : from fudge.processing import miscellaneous as miscellaneousModule def styleFilter( style ) : if( isinstance( style, stylesModule.griddedCrossSection ) ) : return( False ) return( True ) if( tempInfo['verbosity'] > 2 ) : print( '%sMulti-grouping XYs1d cross section' % indent ) crossSectionGrouped = miscellaneousModule.groupOneFunctionAndFlux( style, tempInfo, self, styleFilter = styleFilter ) return( groupModule.toMultiGroup1d( gridded1d, style, tempInfo, self.axes, crossSectionGrouped ) )
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : ptw = XYsModule.XYs1d.toPointwise_withLinearXYs( self, cls = XYs1d, hh = True, **kwargs ) ptw.label = self.label return( ptw )
[docs] def toXMLList( self, indent = "", **kwargs ) : def dataToString( self, dummy, indent = '', **kwargs ) : maxXFormat = "%.17e" valuesPerLine = int( kwargs.get( 'valuesPerLine', 100 ) ) if( ( valuesPerLine % 2 ) == 1 ) : valuesPerLine += 1 xFormat = kwargs.get( "xFormat", "%14.8e" ) XMLList = [] Line = [] i1 = 1 for value in self : if( ( i1 % 2 ) != 0 ) : xString = xFormat % value if( i1 > 1 ) : if( xString == priorXString ) : if( xFormat == maxXFormat ) : raise ValueError( 'x-value strings identical: "%s" and "%s"' % ( priorXString, xString ) ) kwargs['xFormat'] = maxXFormat return( dataToString( self, dummy, indent = indent, **kwargs ) ) Line.append( xString ) priorXString = xString else : Line.append( xFormat % value ) if( ( i1 % valuesPerLine ) == 0 ) : XMLList.append( indent + ' '.join( Line ) ) Line = [] i1 += 1 if( len( Line ) > 0 ) : XMLList.append( indent + ' '.join( Line ) ) return( XMLList ) kwargs['dataToString'] = dataToString kwargs['dataToStringParent'] = None return( XYsModule.XYs1d.toXMLList( self, indent, **kwargs ) )
[docs]class regions1d( baseCrossSectionForm, regionsModule.regions1d ) : """ This class stores a cross section in two or more regions, which may have different interpolations. Each region must contain at least two points. Each pair of adjacent regions must overlap at exactly one point. """ def __init__( self, **kwargs ) : regionsModule.regions1d.__init__( self, **kwargs )
[docs] def processMultiGroup( self, style, tempInfo, indent ) : linear = self.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = 1e-8 ) return( linear.processMultiGroup( style, tempInfo, indent ) )
[docs] def toLinearXYsClass( self ) : return( XYs1d )
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : xys = regionsModule.regions1d.toPointwise_withLinearXYs( self, **kwargs ) return( XYs1d( data = xys, axes = xys.axes ) )
[docs] def ysMappedToXs( self, cls, grid, label = None ) : start = None for region in self : offset, Ys = pointwiseXYClass.ysMappedToXs( region, grid.values ) if( start is None ) : allYs = Ys start = offset else : allYs += Ys[1:] Ys1d = cls( valuesModule.values( allYs, start = start ), axes = self.axes.copy( ), label = label ) Ys1d.axes[1] = linkModule.link2( grid.moniker, instance = grid, keyName = 'index', keyValue = 1 ) return( Ys1d )
[docs] @staticmethod def allowedSubElements( ) : return( ( XYs1d, ) )
[docs]class gridded1d( baseCrossSectionForm, griddedModule.gridded1d ) : def __init__( self, **kwargs ) : griddedModule.gridded1d.__init__( self, **kwargs )
[docs]class backgroundTerm( ancestryModule.ancestry ): __metaclass__ = abc.ABCMeta def __init__(self, data): ancestryModule.ancestry.__init__(self) self.__data = data self.__data.setAncestor( self ) @abc.abstractproperty def moniker(self): pass @property def data(self): return self.__data @property def XYs1d(self): if isinstance(self.__data, XYs1d): return self.__data raise KeyError("No XYs1d data found in the %s background section" % self.moniker) @property def regions1d(self): if isinstance(self.__data, regions1d): return self.__data raise KeyError("No regions1d data found in the %s background section" % self.moniker)
[docs] def convertUnits(self, unitMap): self.data.convertUnits(unitMap)
[docs] def toXMLList( self, indent="", **kwargs ): indent2 = indent + kwargs.get('incrementalIndent', ' ') xml = ['%s<%s>' % (indent, self.moniker)] xml += self.data.toXMLList(indent2, **kwargs) xml[-1] += '</%s>' % self.moniker return xml
[docs] @classmethod def parseXMLNode(cls, element, xPath, linkData): xPath.append( element.tag ) child = element[0] if child.tag == XYs1d.moniker: data = XYs1d.parseXMLNode(child, xPath, linkData) elif child.tag == regions1d.moniker: data = regions1d.parseXMLNode(child, xPath, linkData) else: raise ValueError("Encountered unexpected element '%s' in %s" % (child.tag, element.tag)) background_ = cls(data) xPath.pop() return background_
[docs]class resolvedRegion( backgroundTerm ): moniker = 'resolvedRegion'
[docs]class unresolvedRegion( backgroundTerm ): moniker = 'unresolvedRegion'
[docs]class fastRegion( backgroundTerm ): moniker = 'fastRegion'
[docs]class background( ancestryModule.ancestry ): moniker = 'background' ancestryMembers = ('resolvedRegion', 'unresolvedRegion', 'fastRegion') def __init__(self, resolvedRegion=None, unresolvedRegion=None, fastRegion=None): ancestryModule.ancestry.__init__(self) self.resolvedRegion = resolvedRegion self.unresolvedRegion = unresolvedRegion self.fastRegion = fastRegion def __iter__(self): for term in self.ancestryMembers[:-1]: if getattr(self, term) is not None: yield getattr(self, term) @property def resolvedRegion(self): return self.__resolvedRegion @resolvedRegion.setter def resolvedRegion(self, data): if isinstance(data, resolvedRegion): data.setAncestor(self) elif data is not None: raise TypeError("Expected resolvedRegion instance, got %s instead" % type(data)) self.__resolvedRegion = data @property def unresolvedRegion( self ): return self.__unresolvedRegion @unresolvedRegion.setter def unresolvedRegion( self, data ): if isinstance(data, unresolvedRegion): data.setAncestor(self) elif data is not None: raise TypeError("Expected unresolvedRegion instance, got %s instead" % type(data)) self.__unresolvedRegion = data @property def fastRegion( self ): return self.__fastRegion @fastRegion.setter def fastRegion( self, data ): if isinstance(data, fastRegion): data.setAncestor(self) elif data is not None: raise TypeError("Expected fastRegion instance, got %s instead" % type(data)) self.__fastRegion = data @property def domainMin(self): for term in (self.resolvedRegion, self.unresolvedRegion, self.fastRegion): if term is not None: return term.data.domainMin @property def domainMax(self): for term in (self.fastRegion, self.unresolvedRegion, self.resolvedRegion): if term is not None: domainMax = term.data.domainMax if term.data.domainUnit != self.domainUnit: domainMax = PQUModule.PQU(domainMax, term.data.domainUnit).getValueAs(self.domainUnit) return domainMax @property def domainUnit(self): for term in (self.resolvedRegion, self.unresolvedRegion, self.fastRegion): if term is not None: return term.data.domainUnit
[docs] def convertUnits(self, unitMap): for term in self.ancestryMembers[:-1]: term_ = getattr(self, term) if term_ is not None: term_.convertUnits(unitMap)
[docs] def toRegions( self ): """ Merge all background terms into a single regions1d :return: """ regions = regions1d() axes = None for term in (self.resolvedRegion, self.unresolvedRegion, self.fastRegion): if term is None: continue if axes is None: axes = term.data.axes elif term.data.axes != axes: raise NotImplementedError("Inconsistent axes/units inside background cross section") if isinstance(term.data, XYs1d): regions.append(term.data.copy()) elif isinstance(term.data, regions1d): for region in term.data: regions.append(region.copy()) regions.axes = axes return regions
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : regions = self.toRegions() xys = regionsModule.regions1d.toPointwise_withLinearXYs( regions, **kwargs ) return( XYs1d( data = xys, axes = regions.axes ) )
[docs] def toXMLList( self, indent="", **kwargs ): indent2 = indent + kwargs.get('incrementalIndent', ' ') xml = ['%s<%s>' % (indent, self.moniker)] for child in (self.resolvedRegion, self.unresolvedRegion, self.fastRegion): if child is not None: xml += child.toXMLList(indent2, **kwargs) xml[-1] += '</%s>' % self.moniker return xml
[docs] @classmethod def parseXMLNode( cls, element, xPath, linkData ): xPath.append(element.tag) resolved = unresolved = fast = None for child in element: if child.tag == resolvedRegion.moniker: resolved = resolvedRegion.parseXMLNode(child, xPath, linkData) elif child.tag == unresolvedRegion.moniker: unresolved = unresolvedRegion.parseXMLNode(child, xPath, linkData) elif child.tag == fastRegion.moniker: fast = fastRegion.parseXMLNode(child, xPath, linkData) else: raise ValueError("Encountered unexpected element '%s' in %s" % (child.tag, element.tag)) background_ = cls(resolved, unresolved, fast) xPath.pop() return background_
[docs]class resonancesWithBackground( baseCrossSectionForm ) : """ This class stores cross sections that include resonances along with a background contribution. Contains a link to the resonances, and the 'background' which consists of up to three terms: resolved, unresolved and fast regions. The full XYs1d cross section can be obtained by first reconstructing resonances and then adding the background contribution (users should use the reactionSuite.reconstructResonances method). """ moniker = tokensModule.resonancesWithBackgroundFormToken ancestryMembers = ( 'resonances','background','uncertainty' ) def __init__( self, label, resonances, background, uncertainty=None ) : baseCrossSectionForm.__init__( self ) if not isinstance( label, str ): raise TypeError( 'label must be a string' ) self.__label = label self.resonances = resonances self.background = background self.uncertainty = uncertainty @property def resonances(self): return self.__resonances @resonances.setter def resonances(self, value): if not isinstance(value, resonanceLink): raise TypeError("Expected resonancLink instance, got %s instead" % type(value)) self.__resonances = value self.__resonances.setAncestor(self) @property def background(self): return self.__background @background.setter def background(self, value): if not isinstance(value, background): raise TypeError("Expected background instance, got %s instead" % type(value)) self.__background = value self.__background.setAncestor(self) @property def uncertainty(self): return self.__uncertainty @uncertainty.setter def uncertainty(self, data): if isinstance(data, uncertaintiesModule.uncertainty): data.setAncestor(self) elif data is not None: raise TypeError("Expected uncertainty instance, got %s insteand" % type(data)) self.__uncertainty = data @property def label(self): return self.__label @property def domainMin( self ) : return( self.background.domainMin ) @property def domainMax( self ) : return( self.background.domainMax ) @property def domainUnit( self ) : return( self.background.domainUnit )
[docs] def convertUnits( self, unitMap ) : """See documentation for reactionSuite.convertUnits.""" self.background.convertUnits( unitMap )
# FIXME skipping uncertainties for now # BRB FIXME This is a kludge until we fix production/crossSection/reference # For example, see n + U236 in ENDF/B-7.1. # <productions> # <production label="0" outputChannel="U235" ENDF_MT="16"> # <crossSection> # <reference label="eval" ...
[docs] def processMultiGroup( self, style, tempInfo, indent ) : return( self.ancestor.processMultiGroup( style, tempInfo, indent ) )
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : _component = self.findClassInAncestry( component ) reconstructed = _component.getStyleOfClass( stylesModule.crossSectionReconstructed ) if( reconstructed is not None ) : return( reconstructed.toPointwise_withLinearXYs( **kwargs ) ) # Return a copy. raise Exception( 'resonancesWithBackground cross section has not been reconstructed via reactionSuite.reconstructResonances' )
[docs] def toXMLList( self, indent = "", **kwargs ) : indent2 = indent + kwargs.get( 'incrementalIndent', ' ' ) xmlList = [ '%s<%s label="%s">' % ( indent, self.moniker, self.label ) ] xmlList.append( self.resonances.toXML( indent2, **kwargs ) ) xmlList += self.background.toXMLList( indent2, **kwargs ) if self.uncertainty is not None: xmlList += self.uncertainty.toXMLList( indent2, **kwargs ) xmlList[-1] += '</%s>' % self.moniker return( xmlList )
[docs] @classmethod def parseXMLNode( cls, element, xPath, linkData ) : xPath.append( element.tag ) link = resonanceLink.parseXMLNode( element.find(resonanceLink.moniker), xPath, linkData ) background_ = background.parseXMLNode( element.find(background.moniker), xPath, linkData ) uncertainty = element.find(uncertaintiesModule.uncertainty.moniker) if uncertainty is not None: uncertainty = uncertaintiesModule.uncertainty.parseXMLNode(uncertainty, xPath, linkData) resWithBack = cls( element.get( 'label' ), link, background_, uncertainty ) xPath.pop() return resWithBack
[docs]class reference( linkModule.link, baseCrossSectionForm ) : """This cross section form consists of a reference to another cross section.""" moniker = tokensModule.referenceFormToken def __init__( self, link = None, root = None, path = None, label = None, relative = False ) : linkModule.link.__init__(self, link = link, root = root, path = path, label = label, relative = relative ) baseCrossSectionForm.__init__( self ) @property def crossSection( self ) : if self.link is None : raise Exception( "Unresolved link!" ) return self.link @property def domainMin( self ) : return( self.crossSection.domainMin ) @property def domainMax( self ) : return( self.crossSection.domainMax ) @property def domainUnit( self ) : return( self.crossSection.domainUnit )
[docs] def convertUnits( self, unitMap ) : "See documentation for reactionSuite.convertUnits." pass
[docs] def getReference( self ) : return( self.crossSection )
[docs] def setReference( self, crossSection ) : # FIXME delete? Not used + broken (self.crossSection has no setter) if( not( isinstance( crossSection, (component, None.__class__) ) ) ) : raise TypeError( 'crossSection argument must be a cross section component not type %s' % type( crossSection ) ) self.crossSection = crossSection
[docs] def processMultiGroup( self, style, tempInfo, indent ) : addToComponent = tempInfo.get( 'addToComponent', None ) tempInfo['addToComponent'] = False multiGroup = self.crossSection.processMultiGroup( style, tempInfo, indent ) tempInfo['addToComponent'] = addToComponent if( addToComponent is None ) : del tempInfo['addToComponent'] return( multiGroup )
[docs] def toPointwise_withLinearXYs( self, **kwargs ) : return( self.crossSection.toPointwise_withLinearXYs( **kwargs ) )
[docs] def check( self ) : from fudge.gnds import warning warnings = [] if self.getRootAncestor() != self.getReference().getRootAncestor(): warnings.append( warning.badCrossSectionReference() ) return warnings
[docs]class CoulombPlusNuclearElastic( reference ) : """Special type of link: points to doubleDifferentialCrossSection/chargedParticleElastic""" moniker = "CoulombPlusNuclearElastic"
[docs]def chargeParticle_changeInterpolationSubFunction( threshold, x, x1, y1, x2, y2 ) : B = math.log( x2 * y2 / ( x1 * y1 ) ) / ( 1. / math.sqrt( x1 - threshold ) - 1. / math.sqrt( x2 - threshold ) ) A = x1 * y1 * math.exp( B / math.sqrt( x1 - threshold ) ) y = A * math.exp( - B / math.sqrt( x - threshold ) ) / x return( y )
# # crossSection component #
[docs]class component( abstractClassesModule.component ) : moniker = 'crossSection' def __init__( self ) : abstractClassesModule.component.__init__( self, ( Ys1d, XYs1d, regions1d, gridded1d, resonancesWithBackground, reference ) )
[docs] def domainUnitConversionFactor( self, unitTo ) : if( unitTo is None ) : return( 1. ) return( PQUModule.PQU( '1 ' + self.domainUnit ).getValueAs( unitTo ) )
@property def domainMin( self ) : return( self.evaluated.domainMin ) @property def domainMax( self ) : return( self.evaluated.domainMax ) @property def domainUnit( self ) : return( self.evaluated.domainUnit )
[docs] def hasLinearForm( self ) : for form in self : if( isinstance( form, XYs1d ) ) : if( form.isInterpolationLinear( ) ) : return( form ) return( None )
[docs] def heat( self, style, EMin, lowerlimit = None, upperlimit = None, interpolationAccuracy = 0.001, heatAllPoints = False, doNotThin = True, heatBelowThreshold = True, heatAllEDomain = True, setThresholdToZero = False, addToSuite = False ) : """ Returns the result of self.toPointwise_withLinearXYs( ).heat( ... ). See method XYs1d.heat for more information. If setThresholdToZero is True and self's cross section at the first point is 0., then the heated cross section's first value will also be 0. """ styles = self.findAttributeInAncestry( 'styles' ) currentstyle = styles[style.derivedFrom] if( not( isinstance( currentstyle, ( stylesModule.evaluated, stylesModule.heated ) ) ) ) : TypeError( 'Form to heat is not heatable form: its style moniker is "%s"' % style.label ) currentTemperature = PQUModule.PQU( currentstyle.temperature.getValueAs( 'K' ), 'K' ) * PQUModule.PQU( '1 k' ) currentTemperature = currentTemperature.getValueAs( self.domainUnit ) newTemperature = PQUModule.PQU( style.temperature.getValueAs( 'K' ), 'K' ) * PQUModule.PQU( '1 k' ) newTemperature = newTemperature.getValueAs( self.domainUnit ) reactionSuite = self.getRootAncestor( ) projectile = reactionSuite.PoPs[reactionSuite.projectile] projectileMass = projectile.getMass( 'amu' ) targetID = reactionSuite.target if( targetID in reactionSuite.PoPs.aliases ) : targetID = reactionSuite.PoPs[targetID].pid target = reactionSuite.PoPs[targetID] if( projectileMass == 0.0 ) : massRatio = 0.0 else : massRatio = target.getMass( 'amu' ) / projectileMass linear = self.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = 1e-8 ) heated = linear.heat( currentTemperature, newTemperature, massRatio, EMin, lowerlimit, upperlimit, interpolationAccuracy, heatAllPoints, doNotThin, heatBelowThreshold, heatAllEDomain, setThresholdToZero = setThresholdToZero ) heated.label = style.label if( addToSuite ) : self.add( heated ) return( heated )
[docs] def check( self, info ) : """ Check cross section data for correct threshold, negative cross sections, etc. Returns a list of any warnings encountered during checking. :param dict info: A Python dictionary containing the parameters that control the cross section checking. :keyword boolean CoulombReaction: True if target and projectile are both charged particles, or if two or more products are charged particles. :keyword float Q: a parameter of `info`: if Q is positive (and CoulombReaction=False), cross section must start at crossSectionEnergyMin, otherwise cross section threshold must agree with Q-value :keyword float kinematicFactor: a parameter of `info`: equal to ( (targetMass + projectileMass) / targetMass ) :keyword string dThreshold: a parameter of `info`: allowable threshold energy mismatch as a string suitable for the PQU class :keyword float crossSectionEnergyMin: a parameter of `info`: non-threshold cross section must start at this limit (usually 1e-5 eV) :keyword float crossSectionEnergyMax: a parameter of `info`: the cross section must extend up to limit (usually 20 MeV) """ from fudge.gnds import warning warnings = [] # FIXME: domain is giving incorrect answers for threshold reactions with resonance contributions! lower = PQUModule.PQU( self.domainMin, self.domainUnit ) upper = PQUModule.PQU( self.domainMax, self.domainUnit ) # BRB6 hardwired thresh = PQUModule.PQU( 0, 'eV' ) if 'Q' in info: thresh = -info['Q'] * info['kinematicFactor'] if not isinstance(info['Q'], PQUModule.PQU): thresh = PQUModule.PQU(thresh, 'eV') if not info['CoulombChannel']: # if Q is positive, cross section must start at info['crossSectionEnergyMin'] (usually 1e-5 eV) # otherwise, cross section threshold must agree with Q-value: if thresh.value>0: if abs(thresh-lower) > PQUModule.PQU( info['dThreshold'] ): warnings.append( warning.threshold_mismatch( lower, thresh, self ) ) elif lower != PQUModule.PQU(info['crossSectionEnergyMin']): # ignore 2nd,3rd,4th-chance fission (they have Q>0 but are still threshold reactions): from fudge.gnds import channels parent = self.ancestor if (not hasattr( parent, 'outputChannel' ) or ( parent.outputChannel.getFissionGenre() in (None, channels.fissionGenreTotal, channels.fissionGenreFirstChance) ) ): warnings.append( warning.threshold_mismatch( lower, PQUModule.PQU(info['crossSectionEnergyMin']), self ) ) else: # charged-particle reaction generally doesn't 'turn on' right at threshold due to Coulomb barrier. # In this case, just ensure the cross section stays zero until at or above threshold: if (lower < thresh): warnings.append( warning.Coulomb_threshold_mismatch( lower, thresh, self ) ) # cross section must extend up to limit (usually 20 MeV): if upper < PQUModule.PQU( info['crossSectionEnergyMax'] ): warnings.append( warning.gapInCrossSection( upper, PQUModule.PQU( info['crossSectionEnergyMax'] ), self ) ) evaluatedCrossSection = self.evaluated if( isinstance( evaluatedCrossSection, resonancesWithBackground ) ) : # ensure no gaps between resonance and background: resonances = info['reactionSuite'].resonances if resonances.resolved is not None: resDomainMax = resonances.resolved.domainMax if resonances.unresolved is not None and not resonances.unresolved.evaluated.useForSelfShieldingOnly: resDomainMax = resonances.unresolved.domainMax bckDomainMin = evaluatedCrossSection.background.domainMin if bckDomainMin > resDomainMax: warnings.append( warning.gapInCrossSection(resDomainMax,bckDomainMin, self ) ) linearCrossSection = self[info['reconstructedStyleName']] elif( isinstance( evaluatedCrossSection, reference ) and isinstance( evaluatedCrossSection.link, resonancesWithBackground ) ) : linearCrossSection = evaluatedCrossSection.link.ancestor[info['reconstructedStyleName']] elif isinstance( evaluatedCrossSection, CoulombPlusNuclearElastic ) : return warnings # already checked as part of doubleDifferentialCrossSection else: # can it be converted to XYs1d linear? try: linearCrossSection = evaluatedCrossSection.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = 1e-8 ) except Exception as e: warnings.append( warning.ExceptionRaised( e, self ) ) if info['failOnException']: raise return warnings # test for negative values, and for non-zero cross section at threshold if( linearCrossSection.rangeMin < 0 ) : for i,(en,xsc) in enumerate(linearCrossSection): if xsc < 0: warnings.append( warning.negativeCrossSection( PQUModule.PQU(en, linearCrossSection.axes[-1].unit), i, self ) ) if thresh.value>0: if linearCrossSection[0][1] != 0: warnings.append( warning.nonZero_crossSection_at_threshold( linearCrossSection[0][1], self ) ) return warnings
[docs] def findEntity( self, entityName, attribute = None, value = None ): for form in self : if( form.moniker == entityName ) : return( form ) return ancestryModule.ancestry.findEntity( self, entityName, attribute, value )
[docs] def evaluateWithUncertainty( self, E, useCovariance = True, covariance = None, covarianceSuite = None ) : """ :param E: :param useCovariance: use this to override covarance usage :type useCovariance: bool :param covariance: :param covarianceSuite: :return: """ import fudge.gnds.covariances.base as covModule if( not isinstance( E, PQUModule.PQU ) ) : raise TypeError( "E must be an PQUModule.PQU instance") ptwise = self.hasLinearForm() if ptwise is None: ptwise = self.toPointwise_withLinearXYs( lowerEps = lowerEps, upperEps = upperEps ) EinDomainUnit = E.getValueAs( ptwise.domainUnit ) if EinDomainUnit < ptwise.domainMin or EinDomainUnit > ptwise.domainMax: meanValue = 0.0 else: meanValue = ptwise.evaluate( EinDomainUnit ) # We might be done if not useCovariance: return PQUModule.PQU( meanValue, unit=ptwise.rangeUnit ) # Get that the covariance goes with the data. covariance = self.getMatchingCovariance(covariance,covarianceSuite) if covariance is None: return PQUModule.PQU( meanValue, unit = ptwise.rangeUnit ) else: try: return( PQUModule.PQU( meanValue, unit = ptwise.rangeUnit, uncertainty = covariance.getUncertaintyVector( self.evaluated, relative = False ).evaluate(EinDomainUnit) ) ) except IndexError as err: # FIXME: a kludge until cov routines working again, try it on 27Al(n,tot) print ("WARNING: Could not extract uncertianty, got error %s" % str(err)) return PQUModule.PQU( meanValue, unit = ptwise.rangeUnit )
# if covariance is None: # if hasattr(self.evaluated, 'uncertainty') and self.evaluated.uncertainty: # if hasattr(self.evaluated.uncertainty.data,'link'): # covariance = self.evaluated.uncertainty.data.link['eval'] # elif covarianceSuite is not None: # covariance = self.evaluated.uncertainty.data.follow(startNode=covarianceSuite)['eval'] # else: return PQUModule.PQU( meanValue, unit = ptwise.rangeUnit() ) # else: return PQUModule.PQU( meanValue, unit = ptwise.rangeUnit() ) # else: # if covariance is not( isinstance( covariance, covModule.covarianceMatrix ) ): # raise TypeError( 'covariance must be of type covarianceMatrix, got %s'%str(type(covariance))) # # Compute uncertainty on convolution given mean value and covariance. # try: # uncertainty = covariance.getUncertaintyVector( self.evaluated, relative = False ).evaluate( EinDomainUnit ) # except: # print "WARNING: could not get uncertainty in evaluateWithUncertainty for %s" % str(covariance.__class__) # return PQUModule.PQU( meanValue, unit = ptwise.rangeUnit() ) # if uncertainty is None: # # We haven't changed units on uncertainty or the meanValue, so we can reconstruct the physical quantity easily. # return( PQUModule.PQU( meanValue, unit = ptwise.rangeUnit(), uncertainty = uncertainty.evaluate(EinDomainUnit) ) )
[docs] def getMatchingCovariance(self, covariance = None, covarianceSuite = None): ''' Retrieve the uncertainty that goes with this cross section, if we can find it :return: ''' import fudge.gnds.covariances.base as covModule # Get that the covariance goes with the data. if covariance is None: if hasattr(self.evaluated, 'uncertainty') and self.evaluated.uncertainty: if hasattr(self.evaluated.uncertainty.data,'link') and self.evaluated.uncertainty.data.link is not None: covariance = self.evaluated.uncertainty.data.link['eval'] elif covarianceSuite is not None: covariance = self.evaluated.uncertainty.data.follow(startNode=covarianceSuite)['eval'] else: if covariance is not( isinstance( covariance, covModule.covarianceMatrix ) ): raise TypeError( 'covariance must be of type covarianceMatrix, got %s'%str(type(covariance))) return covariance
[docs] def integrateTwoFunctionsWithUncertainty( self, f2, domainMin = None, domainMax = None, useCovariance = True, covariance = None, covarianceSuite = None, normalize = False ) : """ Computes the spectrum (i.e. weighted) integral of self with the spectrum (i.e. weight) specified by ``spectrum`` optionally from Emin to Emax. If the covariance is provided, the uncertainty on the spectrum integral will be computed. :param f2: spectrum over which to average :type f2: XYs1d instance :param domainMin: Lower integration limit. If None (the default), then the lower limit of self's domain is used :type domainMin: PQU or None :param domainMax: Upper integration limit. If None (the default), then the upper limit of self's domain is used :type domainMax: PQU or None :param useCovariance: use this to override covarance usage :type useCovariance: bool :param covariance: covariance to use when computing uncertainty on the spectral average. If None (default: None), no uncertainty is computed. :type covariance: covariance instance or None :param normalize: if set to True, normalize integral of the spectrum over the interval so we are doing a spectrum average :type normalize: bool :rtype: PQU :How does it work?: Given a weighting spectrum :math:`\phi(E)`, we intend to average the cross section in self as follows: .. math:: < \sigma > = \int_{E_{min}}^{E_{max}} dE \phi(E) \sigma(E) To compute the uncertainty, we resort to the basis function expansion of the covariance as follows: .. math:: \Delta^2\sigma(E,E') = \sum_{ij} \Delta^2\sigma_{ij} B_i(E) B_j(E') In the ENDF format, all covariances are assumed to be grouped in energy so the basis functions are simple window functions. With this, .. math:: \delta <\sigma> = \sqrt{ \int dE dE' \phi(E)\phi(E') \Delta^2\sigma(E,E') } = \sqrt{ \sum_{ij} \Delta^2\sigma_{ij} <B_i> <B_j> } """ # Check that the inputs are of the correct type if not isinstance( f2, XYsModule.XYs1d ): raise TypeError( "spectrum must be an XYs1d instance") # Convert the cross section toXYs1d ptwise = self.hasLinearForm() if ptwise is None: ptwise = self.toPointwise_withLinearXYs( lowerEps = lowerEps, upperEps = upperEps ) # Check domains if( domainMin is None ) : domainMin = PQUModule.PQU( max( ptwise.domainMin, f2.domainMin ), ptwise.domainUnit ) elif( not isinstance( domainMin, PQUModule.PQU ) ) : raise TypeError( "domainMin must be an PQUModule.PQU instance") if( domainMax is None ) : domainMax = PQUModule.PQU( min( ptwise.domainMax, f2.domainMax ), ptwise.domainUnit ) elif( not isinstance( domainMax, PQUModule.PQU ) ) : raise TypeError( "domainMax must be an PQUModule.PQU instance") # Convolve spectrum and self to get mean value meanValue = ptwise.integrateTwoFunctions(f2,domainMin,domainMax) # Normalize the mean if we're averaging if normalize and meanValue.value != 0.0: norm = f2.integrate(domainMin,domainMax) if norm.value == 0.0: raise ValueError('zero norm (%s) while integrating function with %s '%(str(norm),str(self.ancestor.ancestor))) meanValue = meanValue/norm # We might be done if not useCovariance: return meanValue # already a PQU # Get that the covariance goes with the data. covariance = self.getMatchingCovariance(covariance,covarianceSuite) if covariance is None: return meanValue try: theCovariance = covariance.toCovarianceMatrix() # may be redundant, but at least we'll get a usable data type except Exception as err: print ( "WARNING: could not get covariance in integrateTwoFunctionsWithUncertainty for form %s, got error message '%s'" % (str(covariance.__class__), err)) return meanValue # Compute weighting vector from spectrum and possibly the cross section (if covariance is relative) grid = theCovariance.matrix.axes[-1].values.values gridUnit = theCovariance.matrix.axes[-1].unit covGroupBdries = list(grid) if theCovariance.type == 'absolute': phi = f2.group( covGroupBdries, norm=None ) elif theCovariance.type == 'relative': phi = f2.group( covGroupBdries, ptwise, norm=None ) else: raise ValueError( "Unknown covariance type: %s"%str(type(covariance))) # Compute the uncertainty itself import numpy phi = numpy.matrix( phi ) theArray = theCovariance.matrix.array.constructArray() try: coco = ( phi * theArray * phi.T )[0,0] if coco < 0.0: print ("WARNING: covariance of spectrum integral is %s < 0.0" % str(coco)) uncertainty = PQUModule.PQU( math.sqrt( max( coco, 0.0 ) ), unit=meanValue.unit ) if normalize and meanValue.value != 0.0: uncertainty = uncertainty/norm # it worked, return result return PQUModule.PQU( meanValue.value, unit=meanValue.unit, uncertainty=uncertainty.getValueAs(meanValue.unit) ) except Exception as err: print ( "WARNING: could not compute uncertainty in integrateTwoFunctionsWithUncertainty for form %s, got error message '%s'" % (str(covariance.__class__), err)) return meanValue
[docs] def processGriddedCrossSections( self, style, verbosity = 0, indent = '', incrementalIndent = ' ', isPhotoAtomic = False ) : crossSection = style.findFormMatchingDerivedStyle( self ) ys = crossSection.ysMappedToXs( Ys1d, style.grid, label = style.label ) if( isPhotoAtomic ) : ys.interpolation = standardsModule.interpolation.loglogToken if( ( isPhotoAtomic ) and ( ys.Ys.start > 0 ) ) : ys.Ys.values[0] = 1e-10 self.add( ys )
[docs]def parseXMLNode( crossSectionElement, xPath, linkData ): """ Reads an xml <crossSection> element into fudge, including all cross section forms (XYs1d, regions1d, etc.) contained inside the crossSection. """ xPath.append( crossSectionElement.tag ) crossSectionComponent = component( ) for form in crossSectionElement : formClass = { Ys1d.moniker : Ys1d, XYs1d.moniker : XYs1d, regions1d.moniker : regions1d, gridded1d.moniker : gridded1d, reference.moniker : reference, CoulombPlusNuclearElastic.moniker : CoulombPlusNuclearElastic, resonancesWithBackground.moniker : resonancesWithBackground, }.get( form.tag ) if( formClass is None ) : raise Exception( "unknown cross section form: %s" % form.tag ) newForm = formClass.parseXMLNode( form, xPath = xPath, linkData = linkData ) crossSectionComponent.add( newForm ) xPath.pop( ) return( crossSectionComponent )