2525 'energyout' , 'mu' , 'musurface' , 'polar' , 'azimuthal' , 'distribcell' , 'delayedgroup' ,
2626 'energyfunction' , 'cellfrom' , 'materialfrom' , 'legendre' , 'spatiallegendre' ,
2727 'sphericalharmonics' , 'zernike' , 'zernikeradial' , 'particle' , 'cellinstance' ,
28- 'collision' , 'time' , 'parentnuclide' , 'weight'
28+ 'collision' , 'time' , 'parentnuclide' , 'weight' , 'meshborn' , 'meshsurface' ,
29+ 'meshmaterial' ,
2930)
3031
3132_CURRENT_NAMES = (
@@ -900,8 +901,6 @@ def mesh(self, mesh):
900901
901902 @property
902903 def shape (self ):
903- if isinstance (self , MeshSurfaceFilter ):
904- return (self .num_bins ,)
905904 return self .mesh .dimension
906905
907906 @property
@@ -992,8 +991,11 @@ def to_xml_element(self):
992991 XML element containing filter data
993992
994993 """
995- element = super ().to_xml_element ()
996- element [0 ].text = str (self .mesh .id )
994+ element = ET .Element ('filter' )
995+ element .set ('id' , str (self .id ))
996+ element .set ('type' , self .short_name .lower ())
997+ subelement = ET .SubElement (element , 'bins' )
998+ subelement .text = str (self .mesh .id )
997999 if self .translation is not None :
9981000 element .set ('translation' , ' ' .join (map (str , self .translation )))
9991001 return element
@@ -1039,6 +1041,181 @@ class MeshBornFilter(MeshFilter):
10391041 """
10401042
10411043
1044+ class MeshMaterialFilter (MeshFilter ):
1045+ """Filter events by combinations of mesh elements and materials.
1046+
1047+ .. versionadded:: 0.15.3
1048+
1049+ Parameters
1050+ ----------
1051+ mesh : openmc.MeshBase
1052+ The mesh object that events will be tallied onto
1053+ bins : iterable of 2-tuples or numpy.ndarray
1054+ Combinations of (mesh element, material) to tally, given as 2-tuples.
1055+ The first value in the tuple represents the index of the mesh element,
1056+ and the second value indicates the material (either a
1057+ :class:`openmc.Material` instance of the ID).
1058+ filter_id : int
1059+ Unique identifier for the filter
1060+
1061+ """
1062+ def __init__ (self , mesh : openmc .MeshBase , bins , filter_id = None ):
1063+ self .mesh = mesh
1064+ self .bins = bins
1065+ self .id = filter_id
1066+ self ._translation = None
1067+
1068+ @classmethod
1069+ def from_volumes (cls , mesh : openmc .MeshBase , volumes : openmc .MeshMaterialVolumes ):
1070+ """Construct a MeshMaterialFilter from a MeshMaterialVolumes object.
1071+
1072+ Parameters
1073+ ----------
1074+ mesh : openmc.MeshBase
1075+ The mesh object that events will be tallied onto
1076+ volumes : openmc.MeshMaterialVolumes
1077+ The mesh material volumes to use for the filter
1078+
1079+ Returns
1080+ -------
1081+ MeshMaterialFilter
1082+ A new MeshMaterialFilter instance
1083+
1084+ """
1085+ bins = list (zip (* np .where (volumes ._materials > - 1 )))
1086+ return cls (mesh , bins )
1087+
1088+ def __hash__ (self ):
1089+ data = (type (self ).__name__ , self .mesh .id , tuple (self .bins .ravel ()))
1090+ return hash (data )
1091+
1092+ def __repr__ (self ):
1093+ string = type (self ).__name__ + '\n '
1094+ string += '{: <16}=\t {}\n ' .format ('\t ID' , self .id )
1095+ string += '{: <16}=\t {}\n ' .format ('\t Mesh ID' , self .mesh .id )
1096+ string += '{: <16}=\n {}\n ' .format ('\t Bins' , self .bins )
1097+ string += '{: <16}=\t {}\n ' .format ('\t Translation' , self .translation )
1098+ return string
1099+
1100+ @property
1101+ def shape (self ):
1102+ return (self .num_bins ,)
1103+
1104+ @property
1105+ def mesh (self ):
1106+ return self ._mesh
1107+
1108+ @mesh .setter
1109+ def mesh (self , mesh ):
1110+ cv .check_type ('filter mesh' , mesh , openmc .MeshBase )
1111+ self ._mesh = mesh
1112+
1113+ @Filter .bins .setter
1114+ def bins (self , bins ):
1115+ pairs = np .empty ((len (bins ), 2 ), dtype = int )
1116+ for i , (elem , mat ) in enumerate (bins ):
1117+ cv .check_type ('element' , elem , Integral )
1118+ cv .check_type ('material' , mat , (Integral , openmc .Material ))
1119+ pairs [i , 0 ] = elem
1120+ pairs [i , 1 ] = mat if isinstance (mat , Integral ) else mat .id
1121+ self ._bins = pairs
1122+
1123+ def to_xml_element (self ):
1124+ """Return XML element representing the filter.
1125+
1126+ Returns
1127+ -------
1128+ element : lxml.etree._Element
1129+ XML element containing filter data
1130+
1131+ """
1132+ element = ET .Element ('filter' )
1133+ element .set ('id' , str (self .id ))
1134+ element .set ('type' , self .short_name .lower ())
1135+ element .set ('mesh' , str (self .mesh .id ))
1136+
1137+ if self .translation is not None :
1138+ element .set ('translation' , ' ' .join (map (str , self .translation )))
1139+
1140+ subelement = ET .SubElement (element , 'bins' )
1141+ subelement .text = ' ' .join (str (i ) for i in self .bins .ravel ())
1142+
1143+ return element
1144+
1145+ @classmethod
1146+ def from_xml_element (cls , elem : ET .Element , ** kwargs ) -> MeshMaterialFilter :
1147+ filter_id = int (elem .get ('id' ))
1148+ mesh_id = int (elem .get ('mesh' ))
1149+ mesh_obj = kwargs ['meshes' ][mesh_id ]
1150+ bins = [int (x ) for x in get_text (elem , 'bins' ).split ()]
1151+ bins = list (zip (bins [::2 ], bins [1 ::2 ]))
1152+ out = cls (mesh_obj , bins , filter_id = filter_id )
1153+
1154+ translation = elem .get ('translation' )
1155+ if translation :
1156+ out .translation = [float (x ) for x in translation .split ()]
1157+ return out
1158+
1159+ @classmethod
1160+ def from_hdf5 (cls , group , ** kwargs ):
1161+ if group ['type' ][()].decode () != cls .short_name .lower ():
1162+ raise ValueError ("Expected HDF5 data for filter type '"
1163+ + cls .short_name .lower () + "' but got '"
1164+ + group ['type' ][()].decode () + " instead" )
1165+
1166+ if 'meshes' not in kwargs :
1167+ raise ValueError (cls .__name__ + " requires a 'meshes' keyword "
1168+ "argument." )
1169+
1170+ mesh_id = group ['mesh' ][()]
1171+ mesh_obj = kwargs ['meshes' ][mesh_id ]
1172+ bins = group ['bins' ][()]
1173+ filter_id = int (group .name .split ('/' )[- 1 ].lstrip ('filter ' ))
1174+ out = cls (mesh_obj , bins , filter_id = filter_id )
1175+
1176+ translation = group .get ('translation' )
1177+ if translation :
1178+ out .translation = translation [()]
1179+
1180+ return out
1181+
1182+ def get_pandas_dataframe (self , data_size , stride , ** kwargs ):
1183+ """Builds a Pandas DataFrame for the Filter's bins.
1184+
1185+ This method constructs a Pandas DataFrame object for the filter with
1186+ columns annotated by filter bin information. This is a helper method for
1187+ :meth:`Tally.get_pandas_dataframe`.
1188+
1189+ Parameters
1190+ ----------
1191+ data_size : int
1192+ The total number of bins in the tally corresponding to this filter
1193+ stride : int
1194+ Stride in memory for the filter
1195+
1196+ Returns
1197+ -------
1198+ pandas.DataFrame
1199+ A Pandas DataFrame with a multi-index column for the cell instance.
1200+ The number of rows in the DataFrame is the same as the total number
1201+ of bins in the corresponding tally, with the filter bin appropriately
1202+ tiled to map to the corresponding tally bins.
1203+
1204+ See also
1205+ --------
1206+ Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
1207+
1208+ """
1209+ # Repeat and tile bins as necessary to account for other filters.
1210+ bins = np .repeat (self .bins , stride , axis = 0 )
1211+ tile_factor = data_size // len (bins )
1212+ bins = np .tile (bins , (tile_factor , 1 ))
1213+
1214+ columns = pd .MultiIndex .from_product ([[self .short_name .lower ()],
1215+ ['element' , 'material' ]])
1216+ return pd .DataFrame (bins , columns = columns )
1217+
1218+
10421219class MeshSurfaceFilter (MeshFilter ):
10431220 """Filter events by surface crossings on a mesh.
10441221
@@ -1065,6 +1242,9 @@ class MeshSurfaceFilter(MeshFilter):
10651242 The number of filter bins
10661243
10671244 """
1245+ @property
1246+ def shape (self ):
1247+ return (self .num_bins ,)
10681248
10691249 @MeshFilter .mesh .setter
10701250 def mesh (self , mesh ):
0 commit comments