1+ from __future__ import annotations
12from ast import literal_eval
23from collections import defaultdict
34import copy
45import hashlib
56import itertools
6- import os
7- from pathlib import Path
87import pickle
98import threading
9+ from typing import Literal , Tuple , Optional
1010
1111from PySide6 .QtWidgets import QItemDelegate , QColorDialog , QLineEdit , QMessageBox
1212from PySide6 .QtCore import QAbstractTableModel , QModelIndex , Qt , QSize , QEvent
2828_MODEL_PROPERTIES = ('temperature' , 'density' )
2929_PROPERTY_INDICES = {'temperature' : 0 , 'density' : 1 }
3030
31- _REACTION_UNITS = 'Reactions per Source Particle'
32- _PRODUCTION_UNITS = 'Particles Produced per Source Particle'
33- _ENERGY_UNITS = 'eV per Source Particle'
31+ _REACTION_UNITS = 'reactions/source'
32+ _PRODUCTION_UNITS = 'particles/source'
33+ _ENERGY_UNITS = 'eV/source'
34+
35+ _REACTION_UNITS_VOL = 'reactions/cm³/source'
36+ _PRODUCTION_UNITS_VOL = 'particles/cm³/source'
37+ _ENERGY_UNITS_VOL = 'eV/cm³/source'
38+
3439
3540_SPATIAL_FILTERS = (openmc .UniverseFilter ,
3641 openmc .MaterialFilter ,
4247_PRODUCTIONS = ('delayed-nu-fission' , 'prompt-nu-fission' , 'nu-fission' ,
4348 'nu-scatter' , 'H1-production' , 'H2-production' ,
4449 'H3-production' , 'He3-production' , 'He4-production' )
50+ _ENERGY_SCORES = {'heating' , 'heating-local' , 'kappa-fission' ,
51+ 'fission-q-prompt' , 'fission-q-recoverable' ,
52+ 'damage-energy' }
4553
4654_SCORE_UNITS = {p : _PRODUCTION_UNITS for p in _PRODUCTIONS }
47- _SCORE_UNITS ['flux' ] = 'Particle-cm/Particle'
48- _SCORE_UNITS ['current' ] = 'Particles per source Particle'
49- _SCORE_UNITS ['events' ] = 'Events per Source Particle'
50- _SCORE_UNITS ['inverse-velocity' ] = 'Particle-seconds per Source Particle'
51- _SCORE_UNITS ['heating' ] = _ENERGY_UNITS
52- _SCORE_UNITS ['heating-local' ] = _ENERGY_UNITS
53- _SCORE_UNITS ['kappa-fission' ] = _ENERGY_UNITS
54- _SCORE_UNITS ['fission-q-prompt' ] = _ENERGY_UNITS
55- _SCORE_UNITS ['fission-q-recoverable' ] = _ENERGY_UNITS
56- _SCORE_UNITS ['decay-rate' ] = 'Seconds^-1'
57- _SCORE_UNITS ['damage-energy' ] = _ENERGY_UNITS
55+ _SCORE_UNITS ['flux' ] = 'particle-cm/source'
56+ _SCORE_UNITS ['current' ] = 'particle/source'
57+ _SCORE_UNITS ['events' ] = 'events/source'
58+ _SCORE_UNITS ['inverse-velocity' ] = 'particle-s/source'
59+ _SCORE_UNITS ['decay-rate' ] = 'particle/s/source'
60+ _SCORE_UNITS .update ({s : _ENERGY_UNITS for s in _ENERGY_SCORES })
61+
62+ _SCORE_UNITS_VOL = {p : _PRODUCTION_UNITS_VOL for p in _PRODUCTIONS }
63+ _SCORE_UNITS_VOL ['flux' ] = 'particle/cm²/source'
64+ _SCORE_UNITS_VOL ['current' ] = 'particle/cm³/source'
65+ _SCORE_UNITS_VOL ['events' ] = 'events/cm³/source'
66+ _SCORE_UNITS_VOL ['inverse-velocity' ] = 'particle-s/cm³/source'
67+ _SCORE_UNITS_VOL ['decay-rate' ] = 'particle/s/cm³/source'
68+ _SCORE_UNITS .update ({s : _ENERGY_UNITS_VOL for s in _ENERGY_SCORES })
69+
5870
5971_TALLY_VALUES = {'Mean' : 'mean' ,
6072 'Std. Dev.' : 'std_dev' ,
6173 'Rel. Error' : 'rel_err' }
6274
75+ TallyValueType = Literal ['mean' , 'std_dev' , 'rel_err' ]
76+
6377
6478def hash_file (path ):
6579 # return the md5 hash of a file
@@ -382,11 +396,11 @@ def storeCurrent(self):
382396 """ Add current view to previousViews list """
383397 self .previousViews .append (copy .deepcopy (self .currentView ))
384398
385- def create_tally_image (self , view = None ):
399+ def create_tally_image (self , view : Optional [ PlotView ] = None ):
386400 """
387401 Parameters
388402 ----------
389- view :
403+ view : PlotView
390404 View used to set bounds of the tally data
391405
392406 Returns
@@ -436,6 +450,10 @@ def create_tally_image(self, view=None):
436450 contains_cellinstance = tally .contains_filter (openmc .CellInstanceFilter )
437451
438452 if tally .contains_filter (openmc .MeshFilter ):
453+ # Check for volume normalization in order to change units
454+ if view .tallyVolumeNorm :
455+ units_out = _SCORE_UNITS_VOL .get (scores [0 ], _REACTION_UNITS_VOL )
456+
439457 if tally_value == 'rel_err' :
440458 # get both the std. dev. data and mean data
441459 # to create the relative error data
@@ -635,7 +653,10 @@ def _create_distribcell_image(self, tally, tally_value, scores, nuclides, cellin
635653
636654 return image_data , None , data_min , data_max
637655
638- def _create_tally_mesh_image (self , tally , tally_value , scores , nuclides , view = None ):
656+ def _create_tally_mesh_image (
657+ self , tally : openmc .Tally , tally_value : TallyValueType ,
658+ scores : Tuple [str ], nuclides : Tuple [str ], view : PlotView = None
659+ ):
639660 # some variables used throughout
640661 if view is None :
641662 view = self .currentView
@@ -652,57 +673,10 @@ def _do_op(array, tally_value, ax=0):
652673 # start with reshaped data
653674 data = tally .get_reshaped_data (tally_value )
654675
655- # determine basis indices
656- if view .basis == 'xy' :
657- h_ind = 0
658- v_ind = 1
659- ax = 2
660- elif view .basis == 'yz' :
661- h_ind = 1
662- v_ind = 2
663- ax = 0
664- else :
665- h_ind = 0
666- v_ind = 2
667- ax = 1
668-
669- # adjust corners of the mesh for a translation
670- # applied to the mesh filter
671- lower_left = mesh .lower_left
672- upper_right = mesh .upper_right
673- width = mesh .width
674- dimension = mesh .dimension
675- if hasattr (mesh_filter , 'translation' ) and mesh_filter .translation is not None :
676- lower_left += mesh_filter .translation
677- upper_right += mesh_filter .translation
678-
679- # For 2D meshes, add an extra z dimension
680- if len (mesh .dimension ) == 2 :
681- lower_left = np .hstack ((lower_left , - 1e50 ))
682- upper_right = np .hstack ((upper_right , 1e50 ))
683- width = np .hstack ((width , 2e50 ))
684- dimension = np .hstack ((dimension , 1 ))
685-
686- # reduce data to the visible slice of the mesh values
687- k = int ((view .origin [ax ] - lower_left [ax ]) // width [ax ])
688-
689- # setup slice
690- data_slice = [None , None , None ]
691- data_slice [h_ind ] = slice (dimension [h_ind ])
692- data_slice [v_ind ] = slice (dimension [v_ind ])
693- data_slice [ax ] = k
694-
695- if k < 0 or k > dimension [ax ]:
696- return (None , None , None , None )
697-
698676 # move mesh axes to the end of the filters
699677 filter_idx = [type (filter ) for filter in tally .filters ].index (openmc .MeshFilter )
700678 data = np .moveaxis (data , filter_idx , - 1 )
701679
702- # reshape data (with zyx ordering for mesh data)
703- data = data .reshape (data .shape [:- 1 ] + tuple (dimension [::- 1 ]))
704- data = data [..., data_slice [2 ], data_slice [1 ], data_slice [0 ]]
705-
706680 # sum over the rest of the tally filters
707681 for tally_filter in tally .filters :
708682 if type (tally_filter ) == openmc .MeshFilter :
@@ -738,18 +712,36 @@ def _do_op(array, tally_value, ax=0):
738712 selected_scores .append (idx )
739713 data = _do_op (data [np .array (selected_scores )], tally_value )
740714
715+ # Account for mesh filter translation
716+ if mesh_filter .translation is not None :
717+ t = mesh_filter .translation
718+ origin = (view .origin [0 ] - t [0 ], view .origin [1 ] - t [1 ], view .origin [2 ] - t [2 ])
719+ else :
720+ origin = view .origin
721+
722+ # Get mesh bins from openmc.lib
723+ mesh_cpp = openmc .lib .meshes [mesh .id ]
724+ mesh_bins = mesh_cpp .get_plot_bins (
725+ origin = origin ,
726+ width = (view .width , view .height ),
727+ basis = view .basis ,
728+ pixels = (view .h_res , view .v_res ),
729+ )
730+
731+ # Apply volume normalization
732+ if view .tallyVolumeNorm :
733+ data /= mesh_cpp .volumes
734+
735+ # set image data
736+ image_data = np .full_like (self .ids , np .nan , dtype = float )
737+ mask = (mesh_bins >= 0 )
738+ image_data [mask ] = data [mesh_bins [mask ]]
739+
741740 # get dataset's min/max
742741 data_min = np .min (data )
743742 data_max = np .max (data )
744743
745- # set image data, reverse y-axis
746- image_data = data [::- 1 , ...]
747-
748- # return data extents (in cm) for the tally
749- extents = [lower_left [h_ind ], upper_right [h_ind ],
750- lower_left [v_ind ], upper_right [v_ind ]]
751-
752- return image_data , extents , data_min , data_max
744+ return image_data , None , data_min , data_max
753745
754746 @property
755747 def cell_ids (self ):
@@ -939,6 +931,7 @@ def __init__(self):
939931 self .tallyDataMax = np .inf
940932 self .tallyDataLogScale = False
941933 self .tallyMaskZeroValues = False
934+ self .tallyVolumeNorm = False
942935 self .clipTallyData = False
943936 self .tallyValue = "Mean"
944937 self .tallyContours = False
0 commit comments