Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

designing complex data structure's dependences

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.

like image 758
aaragon Avatar asked Apr 01 '17 21:04

aaragon


People also ask

What are complex data structures?

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.

Which data structure is used to represent complex relationships between the nodes?

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.

What are the 4 data structures?

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.

How to design a custom data structure in Java?

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 ...

What are the types of dependencies in a pipelined processor?

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.

What are the factors that determine the complexity of a system?

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.

Which dependencies may introduce stalls in the pipeline?

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.


1 Answers

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.

like image 99
stovfl Avatar answered Oct 18 '22 13:10

stovfl