Skip to content

Commit 8aa5067

Browse files
authored
Improve performance for large models with DomainViewDict (#138)
* Implement DomainViewDict to reduce PlotView size * Improve performance of random color sampling * More speedup of getDomains staticmethod * Avoid global dictionaries and instead implement custom deepcopy for DomainViewDict * Revert default resolution to 1000x1000
1 parent e772fb9 commit 8aa5067

3 files changed

Lines changed: 81 additions & 31 deletions

File tree

openmc_plotter/main_window.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ def editDomainColor(self, kind, id):
10421042
dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
10431043
if dlg.exec():
10441044
new_color = dlg.currentColor().getRgb()[:3]
1045-
domain[id].color = new_color
1045+
domain.set_color(id, new_color)
10461046

10471047
self.applyChanges()
10481048

@@ -1052,7 +1052,7 @@ def toggleDomainMask(self, state, kind, id):
10521052
else:
10531053
domain = self.model.activeView.materials
10541054

1055-
domain[id].masked = bool(state)
1055+
domain.set_masked(id, bool(state))
10561056
self.applyChanges()
10571057

10581058
def toggleDomainHighlight(self, state, kind, id):
@@ -1061,7 +1061,7 @@ def toggleDomainHighlight(self, state, kind, id):
10611061
else:
10621062
domain = self.model.activeView.materials
10631063

1064-
domain[id].highlight = bool(state)
1064+
domain.set_highlight(id, bool(state))
10651065
self.applyChanges()
10661066

10671067
# Helper methods:

openmc_plotter/plot_colors.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
21
import numpy as np
32

43

5-
# for consistent, but random, colors
6-
def reset_seed():
7-
np.random.seed(10)
8-
9-
104
def random_rgb():
115
return tuple(np.random.choice(range(256), size=3))
126

openmc_plotter/plotmodel.py

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from ast import literal_eval
33
from collections import defaultdict
44
import copy
5+
from ctypes import c_int32, c_char_p
56
import hashlib
67
import itertools
78
import pickle
@@ -17,7 +18,7 @@
1718

1819
from . import __version__
1920
from .statepointmodel import StatePointModel
20-
from .plot_colors import random_rgb, reset_seed
21+
from .plot_colors import random_rgb
2122

2223
ID, NAME, COLOR, COLORLABEL, MASK, HIGHLIGHT = range(6)
2324

@@ -170,9 +171,6 @@ def __init__(self, use_settings_pkl, model_path):
170171
self.appliedScores = ()
171172
self.appliedNuclides = ()
172173

173-
# reset random number seed for consistent
174-
# coloring when reloading a model
175-
reset_seed()
176174
self.previousViews = []
177175
self.subsequentViews = []
178176

@@ -333,11 +331,11 @@ def makePlot(self):
333331
# generate colors if not present
334332
for cell_id, cell in cv.cells.items():
335333
if cell.color is None:
336-
cell.color = random_rgb()
334+
cv.cells.set_color(cell_id, random_rgb())
337335

338336
for mat_id, mat in cv.materials.items():
339337
if mat.color is None:
340-
mat.color = random_rgb()
338+
cv.material.set_color(mat_id, random_rgb())
341339

342340
# construct image data
343341
domain[_OVERLAP] = DomainView(_OVERLAP, "Overlap", cv.overlap_color)
@@ -999,8 +997,9 @@ def __init__(self, origin=(0, 0, 0), width=10, height=10, restore_view=None,
999997
self.materials = restore_view.materials
1000998
self.selectedTally = restore_view.selectedTally
1001999
else:
1002-
self.cells = self.getDomains('cell')
1003-
self.materials = self.getDomains('material')
1000+
rng = np.random.RandomState(10)
1001+
self.cells = self.getDomains('cell', rng)
1002+
self.materials = self.getDomains('material', rng)
10041003
self.selectedTally = None
10051004

10061005
def __getattr__(self, name):
@@ -1025,7 +1024,7 @@ def __hash__(self):
10251024
return hash(self.__dict__.__str__() + self.__str__())
10261025

10271026
@staticmethod
1028-
def getDomains(domain_type):
1027+
def getDomains(domain_type, rng):
10291028
""" Return dictionary of domain settings.
10301029
10311030
Retrieve cell or material ID numbers and names from .xml files
@@ -1046,26 +1045,39 @@ def getDomains(domain_type):
10461045
raise ValueError("Domain type, {}, requested is neither "
10471046
"'cell' nor 'material'.".format(domain_type))
10481047

1049-
lib_domain = None
1048+
# Get number of domains, functions for ID/name, and dictionary for defaults
10501049
if domain_type == 'cell':
1051-
lib_domain = openmc.lib.cells
1050+
num_domain = len(openmc.lib.cells)
1051+
get_id = openmc.lib.core._dll.openmc_cell_get_id
1052+
get_name = openmc.lib.core._dll.openmc_cell_get_name
10521053
elif domain_type == 'material':
1053-
lib_domain = openmc.lib.materials
1054-
1055-
domains = {}
1056-
for domain, domain_obj in lib_domain.items():
1057-
name = domain_obj.name
1058-
domains[domain] = DomainView(domain, name, random_rgb())
1054+
num_domain = len(openmc.lib.materials)
1055+
get_id = openmc.lib.core._dll.openmc_material_get_id
1056+
get_name = openmc.lib.core._dll.openmc_material_get_name
1057+
1058+
# Sample default colors for each domain
1059+
colors = rng.randint(256, size=(num_domain, 3))
1060+
1061+
domain_id_c = c_int32()
1062+
name_c = c_char_p()
1063+
defaults = {}
1064+
for i, color in enumerate(colors):
1065+
# Get ID and name for each domain
1066+
get_id(i, domain_id_c)
1067+
get_name(i, name_c)
1068+
domain_id = domain_id_c.value
1069+
name = name_c.value.decode()
1070+
1071+
# Create default domain view for this domain
1072+
defaults[domain_id] = DomainView(domain_id, name, color)
10591073

10601074
# always add void to a material domain at the end
10611075
if domain_type == 'material':
10621076
void_id = _VOID_REGION
1063-
domains[void_id] = DomainView(void_id, "VOID",
1064-
(255, 255, 255),
1065-
False,
1066-
False)
1077+
defaults[void_id] = DomainView(void_id, "VOID", (255, 255, 255),
1078+
False, False)
10671079

1068-
return domains
1080+
return DomainViewDict(defaults)
10691081

10701082
def adopt_plotbase(self, view):
10711083
"""
@@ -1085,6 +1097,50 @@ def adopt_plotbase(self, view):
10851097
self.basis = view.basis
10861098

10871099

1100+
class DomainViewDict(dict):
1101+
"""Dictionary of domain ID to DomainView objects with shared defaults
1102+
1103+
When the active/current view changes in the plotter, this dictionary gets
1104+
deepcopied. To avoid the dictionary being huge for models with lots of
1105+
cells/materials, defaults are stored separately and the key/value pairs in
1106+
this dictionary represent modifications to the default pairs. When an item
1107+
is looked up, if there is no locally modified version we pull the value from
1108+
the defaults dictionary.
1109+
1110+
"""
1111+
def __init__(self, defaults: dict):
1112+
self.defaults = defaults
1113+
1114+
def __getitem__(self, key) -> DomainView:
1115+
if key in self:
1116+
return super().__getitem__(key)
1117+
else:
1118+
# If key is not present, pull it from the defaults
1119+
return self.defaults[key]
1120+
1121+
def __deepcopy__(self, memo):
1122+
cls = self.__class__
1123+
obj = cls.__new__(cls)
1124+
memo[id(self)] = obj
1125+
for key, value in self.items():
1126+
obj[key] = copy.deepcopy(value)
1127+
# Shallow copy the defaults dictionary
1128+
obj.defaults = self.defaults
1129+
return obj
1130+
1131+
def set_color(self, key: int, color):
1132+
domain = self[key]
1133+
self[key] = DomainView(domain.id, domain.name, color, domain.masked, domain.highlight)
1134+
1135+
def set_masked(self, key: int, masked: bool):
1136+
domain = self[key]
1137+
self[key] = DomainView(domain.id, domain.name, domain.color, masked, domain.highlight)
1138+
1139+
def set_highlight(self, key: int, highlight: bool):
1140+
domain = self[key]
1141+
self[key] = DomainView(domain.id, domain.name, domain.color, domain.masked, highlight)
1142+
1143+
10881144
class DomainView:
10891145
"""Represents view settings for OpenMC cell or material.
10901146

0 commit comments

Comments
 (0)