From c8ec2b75049c5f527a5139c27423cfc7a4476429 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 25 Mar 2024 13:27:06 -0500 Subject: [PATCH 1/5] Implement DomainViewDict to reduce PlotView size --- openmc_plotter/main_window.py | 6 ++-- openmc_plotter/plotmodel.py | 54 ++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/openmc_plotter/main_window.py b/openmc_plotter/main_window.py index 7a7b204..dee649a 100755 --- a/openmc_plotter/main_window.py +++ b/openmc_plotter/main_window.py @@ -1042,7 +1042,7 @@ def editDomainColor(self, kind, id): dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color)) if dlg.exec(): new_color = dlg.currentColor().getRgb()[:3] - domain[id].color = new_color + domain.set_color(id, new_color) self.applyChanges() @@ -1052,7 +1052,7 @@ def toggleDomainMask(self, state, kind, id): else: domain = self.model.activeView.materials - domain[id].masked = bool(state) + domain.set_masked(id, bool(state)) self.applyChanges() def toggleDomainHighlight(self, state, kind, id): @@ -1061,7 +1061,7 @@ def toggleDomainHighlight(self, state, kind, id): else: domain = self.model.activeView.materials - domain[id].highlight = bool(state) + domain.set_highlight(id, bool(state)) self.applyChanges() # Helper methods: diff --git a/openmc_plotter/plotmodel.py b/openmc_plotter/plotmodel.py index 8a70048..b8d77d1 100644 --- a/openmc_plotter/plotmodel.py +++ b/openmc_plotter/plotmodel.py @@ -333,11 +333,11 @@ def makePlot(self): # generate colors if not present for cell_id, cell in cv.cells.items(): if cell.color is None: - cell.color = random_rgb() + cv.cells.set_color(cell_id, random_rgb()) for mat_id, mat in cv.materials.items(): if mat.color is None: - mat.color = random_rgb() + cv.material.set_color(mat_id, random_rgb()) # construct image data domain[_OVERLAP] = DomainView(_OVERLAP, "Overlap", cv.overlap_color) @@ -1049,10 +1049,11 @@ def getDomains(domain_type): lib_domain = None if domain_type == 'cell': lib_domain = openmc.lib.cells + domains = DEFAULT_CELL_DOMAIN_VIEW elif domain_type == 'material': lib_domain = openmc.lib.materials + domains = DEFAULT_MATERIAL_DOMAIN_VIEW - domains = {} for domain, domain_obj in lib_domain.items(): name = domain_obj.name domains[domain] = DomainView(domain, name, random_rgb()) @@ -1065,7 +1066,7 @@ def getDomains(domain_type): False, False) - return domains + return DomainViewDict(domain_type) def adopt_plotbase(self, view): """ @@ -1085,6 +1086,51 @@ def adopt_plotbase(self, view): self.basis = view.basis +# To avoid deepcopying the default domain view for every single cell/material, +# we keep a global dictionary that gets populated at startup. Further domain +# view customizations are saved as modifications to the default ones +DEFAULT_CELL_DOMAIN_VIEW = {} +DEFAULT_MATERIAL_DOMAIN_VIEW = {} + + +class DomainViewDict(dict): + """Dictionary of domain ID to DomainView objects, backed by global dict + + When the active/current view changes in the plotter, this dictionary gets + deepcopied. To avoid the dictionary being huge for models with lots of + cells/materials, default DomainView objects are stored in global + dictionaries and the key/value pairs in this dictionary represent + modifications to the default pairs. When an item is looked up, if there is + no locally modified version we pull the value from the global dictionary. + + """ + def __init__(self, domain_type: str): + self.domain_type = domain_type + + def __getitem__(self, key) -> DomainView: + if key in self: + return super().__getitem__(key) + else: + # If key is not present, default to pulling the value from the + # global dictionary + if self.domain_type == 'cell': + return DEFAULT_CELL_DOMAIN_VIEW[key] + else: + return DEFAULT_MATERIAL_DOMAIN_VIEW[key] + + def set_color(self, key: int, color): + domain = self[key] + self[key] = DomainView(domain.id, domain.name, color, domain.masked, domain.highlight) + + def set_masked(self, key: int, masked: bool): + domain = self[key] + self[key] = DomainView(domain.id, domain.name, domain.color, masked, domain.highlight) + + def set_highlight(self, key: int, highlight: bool): + domain = self[key] + self[key] = DomainView(domain.id, domain.name, domain.color, domain.masked, highlight) + + class DomainView: """Represents view settings for OpenMC cell or material. From 5a2fb1f5d084af49b0614ca37d8ca67656de80f2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 26 Mar 2024 23:48:41 -0500 Subject: [PATCH 2/5] Improve performance of random color sampling --- openmc_plotter/plot_colors.py | 6 ------ openmc_plotter/plotmodel.py | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/openmc_plotter/plot_colors.py b/openmc_plotter/plot_colors.py index 9c973d1..a77f27b 100644 --- a/openmc_plotter/plot_colors.py +++ b/openmc_plotter/plot_colors.py @@ -1,12 +1,6 @@ - import numpy as np -# for consistent, but random, colors -def reset_seed(): - np.random.seed(10) - - def random_rgb(): return tuple(np.random.choice(range(256), size=3)) diff --git a/openmc_plotter/plotmodel.py b/openmc_plotter/plotmodel.py index b8d77d1..d74295c 100644 --- a/openmc_plotter/plotmodel.py +++ b/openmc_plotter/plotmodel.py @@ -17,7 +17,7 @@ from . import __version__ from .statepointmodel import StatePointModel -from .plot_colors import random_rgb, reset_seed +from .plot_colors import random_rgb ID, NAME, COLOR, COLORLABEL, MASK, HIGHLIGHT = range(6) @@ -170,9 +170,6 @@ def __init__(self, use_settings_pkl, model_path): self.appliedScores = () self.appliedNuclides = () - # reset random number seed for consistent - # coloring when reloading a model - reset_seed() self.previousViews = [] self.subsequentViews = [] @@ -999,8 +996,9 @@ def __init__(self, origin=(0, 0, 0), width=10, height=10, restore_view=None, self.materials = restore_view.materials self.selectedTally = restore_view.selectedTally else: - self.cells = self.getDomains('cell') - self.materials = self.getDomains('material') + rng = np.random.RandomState(10) + self.cells = self.getDomains('cell', rng) + self.materials = self.getDomains('material', rng) self.selectedTally = None def __getattr__(self, name): @@ -1025,7 +1023,7 @@ def __hash__(self): return hash(self.__dict__.__str__() + self.__str__()) @staticmethod - def getDomains(domain_type): + def getDomains(domain_type, rng): """ Return dictionary of domain settings. Retrieve cell or material ID numbers and names from .xml files @@ -1054,9 +1052,13 @@ def getDomains(domain_type): lib_domain = openmc.lib.materials domains = DEFAULT_MATERIAL_DOMAIN_VIEW - for domain, domain_obj in lib_domain.items(): + # Sample default colors for each domain + num_domain = len(lib_domain) + colors = rng.randint(256, size=(num_domain, 3)) + + for (domain, domain_obj), color in zip(lib_domain.items(), colors): name = domain_obj.name - domains[domain] = DomainView(domain, name, random_rgb()) + domains[domain] = DomainView(domain, name, color) # always add void to a material domain at the end if domain_type == 'material': From 8cb062d9f9b18e6c370d71b254ded9867dc83af9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 27 Mar 2024 09:27:39 -0500 Subject: [PATCH 3/5] More speedup of getDomains staticmethod --- openmc_plotter/plotmodel.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/openmc_plotter/plotmodel.py b/openmc_plotter/plotmodel.py index d74295c..c8df976 100644 --- a/openmc_plotter/plotmodel.py +++ b/openmc_plotter/plotmodel.py @@ -2,6 +2,7 @@ from ast import literal_eval from collections import defaultdict import copy +from ctypes import c_int32, c_char_p import hashlib import itertools import pickle @@ -1044,29 +1045,38 @@ def getDomains(domain_type, rng): raise ValueError("Domain type, {}, requested is neither " "'cell' nor 'material'.".format(domain_type)) - lib_domain = None + # Get number of domains, functions for ID/name, and dictionary for defaults if domain_type == 'cell': - lib_domain = openmc.lib.cells + num_domain = len(openmc.lib.cells) + get_id = openmc.lib.core._dll.openmc_cell_get_id + get_name = openmc.lib.core._dll.openmc_cell_get_name domains = DEFAULT_CELL_DOMAIN_VIEW elif domain_type == 'material': - lib_domain = openmc.lib.materials + num_domain = len(openmc.lib.materials) + get_id = openmc.lib.core._dll.openmc_material_get_id + get_name = openmc.lib.core._dll.openmc_material_get_name domains = DEFAULT_MATERIAL_DOMAIN_VIEW # Sample default colors for each domain - num_domain = len(lib_domain) colors = rng.randint(256, size=(num_domain, 3)) - for (domain, domain_obj), color in zip(lib_domain.items(), colors): - name = domain_obj.name - domains[domain] = DomainView(domain, name, color) + domain_id_c = c_int32() + name_c = c_char_p() + for i, color in enumerate(colors): + # Get ID and name for each domain + get_id(i, domain_id_c) + get_name(i, name_c) + domain_id = domain_id_c.value + name = name_c.value.decode() + + # Create default domain view for this domain + domains[domain_id] = DomainView(domain_id, name, color) # always add void to a material domain at the end if domain_type == 'material': void_id = _VOID_REGION - domains[void_id] = DomainView(void_id, "VOID", - (255, 255, 255), - False, - False) + domains[void_id] = DomainView(void_id, "VOID", (255, 255, 255), + False, False) return DomainViewDict(domain_type) From 09ec18c9a20f235434033d9c0ed295de2488f9fe Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 30 Mar 2024 11:32:42 +0100 Subject: [PATCH 4/5] Avoid global dictionaries and instead implement custom deepcopy for DomainViewDict --- openmc_plotter/plotmodel.py | 52 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/openmc_plotter/plotmodel.py b/openmc_plotter/plotmodel.py index c8df976..9d75614 100644 --- a/openmc_plotter/plotmodel.py +++ b/openmc_plotter/plotmodel.py @@ -796,8 +796,8 @@ def __init__(self, origin=(0, 0, 0), width=10, height=10): self.origin = origin self.width = width self.height = height - self.h_res = 1000 - self.v_res = 1000 + self.h_res = 100 + self.v_res = 100 self.basis = 'xy' self.color_overlaps = False @@ -1050,18 +1050,17 @@ def getDomains(domain_type, rng): num_domain = len(openmc.lib.cells) get_id = openmc.lib.core._dll.openmc_cell_get_id get_name = openmc.lib.core._dll.openmc_cell_get_name - domains = DEFAULT_CELL_DOMAIN_VIEW elif domain_type == 'material': num_domain = len(openmc.lib.materials) get_id = openmc.lib.core._dll.openmc_material_get_id get_name = openmc.lib.core._dll.openmc_material_get_name - domains = DEFAULT_MATERIAL_DOMAIN_VIEW # Sample default colors for each domain colors = rng.randint(256, size=(num_domain, 3)) domain_id_c = c_int32() name_c = c_char_p() + defaults = {} for i, color in enumerate(colors): # Get ID and name for each domain get_id(i, domain_id_c) @@ -1070,15 +1069,15 @@ def getDomains(domain_type, rng): name = name_c.value.decode() # Create default domain view for this domain - domains[domain_id] = DomainView(domain_id, name, color) + defaults[domain_id] = DomainView(domain_id, name, color) # always add void to a material domain at the end if domain_type == 'material': void_id = _VOID_REGION - domains[void_id] = DomainView(void_id, "VOID", (255, 255, 255), + defaults[void_id] = DomainView(void_id, "VOID", (255, 255, 255), False, False) - return DomainViewDict(domain_type) + return DomainViewDict(defaults) def adopt_plotbase(self, view): """ @@ -1098,37 +1097,36 @@ def adopt_plotbase(self, view): self.basis = view.basis -# To avoid deepcopying the default domain view for every single cell/material, -# we keep a global dictionary that gets populated at startup. Further domain -# view customizations are saved as modifications to the default ones -DEFAULT_CELL_DOMAIN_VIEW = {} -DEFAULT_MATERIAL_DOMAIN_VIEW = {} - - class DomainViewDict(dict): - """Dictionary of domain ID to DomainView objects, backed by global dict + """Dictionary of domain ID to DomainView objects with shared defaults When the active/current view changes in the plotter, this dictionary gets deepcopied. To avoid the dictionary being huge for models with lots of - cells/materials, default DomainView objects are stored in global - dictionaries and the key/value pairs in this dictionary represent - modifications to the default pairs. When an item is looked up, if there is - no locally modified version we pull the value from the global dictionary. + cells/materials, defaults are stored separately and the key/value pairs in + this dictionary represent modifications to the default pairs. When an item + is looked up, if there is no locally modified version we pull the value from + the defaults dictionary. """ - def __init__(self, domain_type: str): - self.domain_type = domain_type + def __init__(self, defaults: dict): + self.defaults = defaults def __getitem__(self, key) -> DomainView: if key in self: return super().__getitem__(key) else: - # If key is not present, default to pulling the value from the - # global dictionary - if self.domain_type == 'cell': - return DEFAULT_CELL_DOMAIN_VIEW[key] - else: - return DEFAULT_MATERIAL_DOMAIN_VIEW[key] + # If key is not present, pull it from the defaults + return self.defaults[key] + + def __deepcopy__(self, memo): + cls = self.__class__ + obj = cls.__new__(cls) + memo[id(self)] = obj + for key, value in self.items(): + obj[key] = copy.deepcopy(value) + # Shallow copy the defaults dictionary + obj.defaults = self.defaults + return obj def set_color(self, key: int, color): domain = self[key] From e3b94adf6cf2c85ff79179f3a26edc26bcb1d7dc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 2 Apr 2024 16:25:06 +0200 Subject: [PATCH 5/5] Revert default resolution to 1000x1000 --- openmc_plotter/plotmodel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc_plotter/plotmodel.py b/openmc_plotter/plotmodel.py index 9d75614..e0c43ac 100644 --- a/openmc_plotter/plotmodel.py +++ b/openmc_plotter/plotmodel.py @@ -796,8 +796,8 @@ def __init__(self, origin=(0, 0, 0), width=10, height=10): self.origin = origin self.width = width self.height = height - self.h_res = 100 - self.v_res = 100 + self.h_res = 1000 + self.v_res = 1000 self.basis = 'xy' self.color_overlaps = False