Source code for mcnpy.region

"""
Copyright (c) 2011-2023 Massachusetts Institute of Technology, UChicago Argonne
LLC, and OpenMC contributors

Copyright (c) 2023 NuCoMP

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from collections import OrderedDict
from collections.abc import MutableSequence
from abc import ABC

from .wrap import wrappers, overrides
import mcnpy

globals().update({name+'Base': wrapper for name, wrapper in wrappers.items()})

[docs]class Region(RegionBase, ABC): """ A representation of the model object `Region`. Parameters ---------- """ def __and__(self, other): return Intersection((self, other)) def __or__(self, other): return Union((self, other)) def __invert__(self): return Complement(self)
[docs] def get_surfaces(self, surfaces=None): """Recursively find all surfaces referenced by a region and return them Parameters ---------- surfaces: collections.OrderedDict, optional Dictionary mapping surface IDs to :class:`mcnpy.Surface` instances Returns ------- surfaces: collections.OrderedDict Dictionary mapping surface IDs to :class:`mcnpy.Surface` instances """ if surfaces is None: surfaces = OrderedDict() for region in self: surfaces = region.get_surfaces(surfaces) return surfaces
[docs] def remove_redundant_surfaces(self, redundant_surfaces): """Recursively remove all redundant surfaces referenced by this region Parameters ---------- redundant_surfaces : dict Dictionary mapping redundant surface IDs to class:`mcnpy.Surface` instances that should replace them. """ for region in self: region.remove_redundant_surfaces(redundant_surfaces)
[docs] @staticmethod def from_expression(expression, surfaces, cells): """Generate a region given an infix expression. Parameters ---------- expression : str Boolean expression relating surface half-spaces. The possible operators are union '|', intersection ' ', and complement '~'. For example, '(1 -2) | 3 ~(4 -5)'. surfaces : dict Dictionary whose keys are suface IDs that appear in the Boolean expression and whose values are Surface objects. """ # Strip leading and trailing whitespace expression = expression.strip() # Convert the string expression into a list of tokens, i.e., operators # and surface half-spaces, representing the expression in infix # notation. i = 0 i_start = -1 tokens = [] while i < len(expression): if expression[i] in '()|~ ': # If special character appears immediately after a non-operator, # create a token with the apporpriate half-space if i_start >= 0: j = int(expression[i_start:i]) if j < 0: tokens.append(-surfaces[abs(j)]) else: if len(tokens) > 0: if tokens[len(tokens)-1] == '~': tokens.append(cells[abs(j)]) else: tokens.append(+surfaces[abs(j)]) else: tokens.append(+surfaces[abs(j)]) if expression[i] in '()|~': # For everything other than intersection, add the operator # to the list of tokens tokens.append(expression[i]) else: # Find next non-space character while expression[i+1] == ' ': i += 1 # If previous token is a halfspace or right parenthesis and next token # is not a left parenthese or union operator, that implies that the # whitespace is to be interpreted as an intersection operator if (i_start >= 0 or tokens[-1] == ')') and \ expression[i+1] not in ')|': tokens.append(' ') i_start = -1 else: # Check for invalid characters if expression[i] not in '-+0123456789': raise SyntaxError("Invalid character '{}' in expression" .format(expression[i])) # If we haven't yet reached the start of a word, start one if i_start < 0: i_start = i i += 1 # If we've reached the end and we're still in a word, create a # half-space token and add it to the list if i_start >= 0: j = int(expression[i_start:]) if j < 0: tokens.append(-surfaces[abs(j)]) else: if len(tokens) > 0: if tokens[len(tokens)-1] == '~': tokens.append(cells[abs(j)]) else: tokens.append(+surfaces[abs(j)]) else: tokens.append(+surfaces[abs(j)]) # The functions below are used to apply an operator to operands on the # output queue during the shunting yard algorithm. def can_be_combined(region): return isinstance(region, Complement) or hasattr(region, 'surface') def apply_operator(output, operator): r2 = output.pop() if operator == ' ': r1 = output.pop() if isinstance(r1, Intersection): r1 &= r2 output.append(r1) elif isinstance(r2, Intersection) and can_be_combined(r1): r2.insert(0, r1) output.append(r2) else: output.append(r1 & r2) elif operator == '|': r1 = output.pop() if isinstance(r1, Union): r1 |= r2 output.append(r1) elif isinstance(r2, Union) and can_be_combined(r1): r2.insert(0, r1) output.append(r2) else: output.append(r1 | r2) elif operator == '~': output.append(~r2) # The following is an implementation of the shunting yard algorithm to # generate an abstract syntax tree for the region expression. output = [] stack = [] precedence = {'|': 1, ' ': 2, '~': 3} associativity = {'|': 'left', ' ': 'left', '~': 'right'} #print('TOKENS:\n' + str(tokens) + '\n') for token in tokens: if token in (' ', '|', '~'): # Normal operators while stack: op = stack[-1] if (op not in ('(', ')') and ((associativity[token] == 'right' and precedence[token] < precedence[op]) or (associativity[token] == 'left' and precedence[token] <= precedence[op]))): apply_operator(output, stack.pop()) else: break stack.append(token) elif token == '(': # Left parentheses stack.append(token) elif token == ')': # Right parentheses while stack[-1] != '(': apply_operator(output, stack.pop()) if len(stack) == 0: raise SyntaxError('Mismatched parentheses in ' 'region specification.') stack.pop() else: # Surface halfspaces output.append(token) while stack: if stack[-1] in '()': raise SyntaxError('Mismatched parentheses in region ' 'specification.') apply_operator(output, stack.pop()) # Since we are generating an abstract syntax tree rather than a reverse # Polish notation expression, the output queue should have a single item # at the end #print('OUTPUT:'+'\n'+str(output[0])+'\n') return output[0]
[docs]class Intersection(IntersectionBase, Region, MutableSequence): """ A representation of the model object `Intersection`. Parameters ---------- nodes : iterable of mcnpy.Region Nodes for `Intersection`. """ def _init(self, nodes): self.nodes = nodes def __and__(self, other): new = Intersection(self) new &= other return new def __iand__(self, other): if isinstance(other, Intersection): self.extend(other) else: self.nodes.addUnique(other._e_object) #self.append(other) return self # Implement mutable sequence protocol by delegating to list def __getitem__(self, key): return self.nodes[key] def __setitem__(self, key, value): self.nodes[key] = value def __delitem__(self, key): del self.nodes[key] def __len__(self): return len(self.nodes)
[docs] def insert(self, index, value): self.nodes.insert(index, value)
def __str__(self): return '(' + ' '.join(map(str, self)) + ')'
[docs]class Union(UnionBase, Region, MutableSequence): """ A representation of the model object `Union`. Parameters ---------- nodes : iterable of mcnpy.Region Nodes for `Union`. """ def _init(self, nodes): self.nodes = nodes def __or__(self, other): new = Union(self) new |= other return new def __ior__(self, other): if isinstance(other, Union): self.extend(other) else: self.nodes.addUnique(other._e_object) #self.append(other) return self # Implement mutable sequence protocol by delegating to list def __getitem__(self, key): return self.nodes[key] def __setitem__(self, key, value): self.nodes[key] = value def __delitem__(self, key): del self.nodes[key] def __len__(self): return len(self.nodes)
[docs] def insert(self, index, value): self.nodes.insert(index, value)
def __str__(self): return '(' + ' | '.join(map(str, self)) + ')'
[docs]class Complement(ComplementBase, Region): """ A representation of the model object `Complement`. Parameters ---------- cell : mcnpy.Cell Cell for `Complement`. node : mcnpy.Region Node for `Complement`. """ def _init(self, node): if isinstance(node, mcnpy.Cell): #self.cell = node # I think this will decompose cell complements. # Sort of works, probably doesn't comply for nested complements. self.node = node.region else: self.node = node def __str__(self): if self.node is not None: if isinstance(self.node, mcnpy.Halfspace): return str(~self.node) else: return ('~' + str(self.node)).replace('~~', '') else: return ('~' + str(self.cell.region)).replace('~~', '')
[docs] def get_surfaces(self, surfaces=None): """Recursively find and return all the surfaces referenced by the node Parameters ---------- surfaces: collections.OrderedDict, optional Dictionary mapping surface IDs to :class:`mcnpy.Surface` instances Returns ------- surfaces: collections.OrderedDict Dictionary mapping surface IDs to :class:`mcnpy.Surface` instances """ if surfaces is None: surfaces = OrderedDict() #print('\nNode:', self.node) #if isinstance(self.node, mcnpy.surfaces.Halfspace): # surfaces = self.node.get_surfaces(surfaces) try: for region in self.node: surfaces = region.get_surfaces(surfaces) except: surfaces = self.node.get_surfaces(surfaces) return surfaces
[docs] def remove_redundant_surfaces(self, redundant_surfaces): """Recursively remove all redundant surfaces referenced by this region .. versionadded:: 0.12 Parameters ---------- redundant_surfaces : dict Dictionary mapping redundant surface IDs to class:`mcnpy.Surface` instances that should replace them. """ for region in self.node: region.remove_redundant_surfaces(redundant_surfaces)
for name, wrapper in overrides.items(): override = globals().get(name, None) if override is not None: overrides[name] = override