I'm in the process of designing a finite element library. For a given problem, the finite element mesh used can have elements of different dimensions (for example tetrahedra and triangles), and combining different elements of the same dimension is also possible (for example tetrahedra and hexahedra). Therefore, I need a data structure that stores the finite elements' information. The most fundamental information is the elements' connectivity (the node IDs that define the element). For example, I need to store somehow that triangular element 4 is connected to nodes 5, 6, and 10.
My first attempt was to create a list whose index is the dimension (0,1,2 or 3) and that stores dictionaries. These dictionaries have string keys (identifiers) and the values are numpy arrays (each row representing an element connectivity). I need to do this because the numpy arrays for a given dimension have different shapes depending on the string identifiers.
This is the class:
import os
from collections import OrderedDict
import numpy.ma as ma
flatten = lambda l: [item for sublist in l for item in sublist]
class ElementData(list):
def __init__(self, *args, **kwargs):
self.reset()
super(ElementData, self).__init__(*args, **kwargs)
def __iter__(self):
for k, v in self[self.idx].items():
for i, e in enumerate(v):
yield (k,i,e) if not ma.is_masked(e) else (k,i, None)
self.reset()
def __call__(self, idx):
self.idx = idx-1
return self
def __getitem__(self, index):
if index >= len(self):
self.expand(index)
return super(ElementData, self).__getitem__(index)
def __setitem__(self, index, value):
if index >= len(self):
self.expand(index)
list.__setitem__(self, index, value)
def __str__(self):
return "Element dimensions present: {}\n".format([i for i in range(len(self)) if self[i]]) + super(ElementData, self).__str__()
def keys(self):
return flatten([list(self[i].keys()) for i in range(len(self))])
def reset(self):
self.idx = -1
self.d = -1
def expand(self, index):
self.d = max(index, self.d)
for i in range(index + 1 - len(self)):
self.append(OrderedDict())
def strip(self, value=None):
if not callable(value):
saved_value, value = value, lambda k,v: saved_value
return ElementData([OrderedDict({k:value(k, v) for k,v in i.items()}) for i in super(ElementData, self).__iter__()])
def numElements(self, d):
def elementsOfDimension(d):
# loop over etypes
nelems = 0
for v in self[d].values():
nelems += v.shape[0] if not isinstance(v, ma.MaskedArray) else v.shape[0] - v.mask.any(axis=1).sum()
return nelems
# compute the number of all elements
if d == -1:
nelems = 0
for i in range(self.d+1):
nelems += elementsOfDimension(i)
return nelems
else: # of specific dimension only
return elementsOfDimension(d)
The class works nicely, and it allows me to loop seamlessly through all items of a particular dimension. However, there are other data associated with each element that's stored separately, for example its material. Therefore, I decided to use the same data structure to refer to other properties. To that end I use the strip
function of the class, to return me the entire structure without the numpy arrays.
The problem that I is that the original data structure is dynamic, and if I change it, I have to modify every other structure that depends on it. I really think I went in the wrong direction while designing this class. Perhaps there's a simpler way to approach this problem? I thought about storing the extra information next to the numpy arrays (as tuples for example), but I don't know whether this is good or not. The choices made while designing software can really make our life miserable later, and I'm starting realize this now.
UPDATE
Using the class above, one example could be the following:
Element dimensions present: [0, 1, 2]
[OrderedDict([('n1', array([[0],
[1],
[3]]))]), OrderedDict([('l2', array([[1, 2]]))]), OrderedDict([('q4', array([[0, 1, 5, 4],
[5, 1, 2, 6],
[6, 2, 3, 7],
[7, 3, 0, 4],
[4, 5, 6, 7]]))])]
where the data structure has been used to store elements of 0 (node), 1 (line) and 2 (quadrangles) dimensions.
Complex types are nested data structures composed of primitive data types. These data structures can also be composed of other complex types. Some examples of complex types include struct(row), array/list, map and union. Complex types are supported by most programming languages including Python, C++ and Java.
Graphs are a data structure used to represent a visual of relationships between data vertices (the Nodes of a graph). The links that connect vertices together are called edges.
When we think of data structures, there are generally four forms: Linear: arrays, lists. Tree: binary, heaps, space partitioning etc. Hash: distributed hash table, hash tree etc.
The Process of Designing a Custom Data Structure 1 Step 1: Requirements#N#The first step of every design process is to actually decide on what you want your new structure to... 2 Step 2: Design#N#Looking at what’s available in the standard libraries, we come across the NSPointerArray — an array that... 3 Step 3: Implementation More ...
There are mainly three types of dependencies possible in a pipelined processor. These are : These dependencies may introduce stalls in the pipeline. Stall : A stall is a cycle in the pipeline without new input. This dependency arises due to the resource conflict in the pipeline.
The main deriving factors of complexity are dependencies and obscurity. Dependency refers to the interdependency of the various parts of a system on each other. The more dependencies there are for a part of the system to function as per its specs more complex that part would become.
These dependencies may introduce stalls in the pipeline. Stall : A stall is a cycle in the pipeline without new input. This dependency arises due to the resource conflict in the pipeline. A resource conflict is a situation when more than one instruction tries to access the same resource in the same cycle.
Comment: the design goes against the logical structure of the program.
I used the given Element data example and didn't expect to get the whole picture at once.
Comment: Each element has a unique dimension (a triangle has always dimension 2, as a tetrahedron has always dimension 3 and a node dimension 0).
Sorry, I misinterpreted from the question "... elements of different dimensions..." with "Element have different dimensions". I adapted my Proposal.
Comment: ... the problem arises when I modify the data structure (for example by adding elements)
As long as you don't show a Data example for this problem, I can't thinking above a solution.
My Proposal:
class Dimension(object):
"""
Base class for all Dimensions
Dimension has no knowledge to what the data belongs to
"""
def __init__(self, data=None):
pass
class Element(object):
"""
Base class for all Elements
Hold on class Dimension(object):
"""
def __init__(self, dimension=None):
pass
class Triangle(Element):
def __init__(self, dimension):
super().__init__(dimension=dimension)
class Tetrahedron(Element):
def __init__(self, dimension):
super().__init__(dimension=dimension)
class Node(Element):
def __init__(self, dimension):
super().__init__(dimension=dimension)
Usage: Building your example Element
node = Node(dimension=[0,1,3])
line = Triangle(dimension=[0,2])
quad = Tetrahedron(dimension=[[0, 1, 5, 4], [5, 1, 2, 6], [6, 2, 3, 7], [7, 3, 0, 4], [4, 5, 6, 7]])
New Future Element, only to show class Element
could extended:
# New future dimensions - No changes to class Element() required
class FutureNodTriTet(Element):
def __init__(self, dimension):
super().__init__(dimension=dimension)
future_NTT = FutureNodTriTet(dimension=[0, (1,3), (4,5,6)])
Please comment if this is closer to your needs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With