5

Background

In an algorithm called finite element method, a continuous region is discretized into repeated sections with consistent geometry, over which linked equations are formed based on the assumption of continuity between them.

In this case, I have chosen to divide a shape up into an arbitrary grid, and am now trying to connect the elements' values together as I iterate through the elements. Here is an example of the kind of grid I am talking about:

Example grid adapted from https://people.sc.fsu.edu/~jburkardt/m_src/fem2d_poisson_rectangle/rectangle_elements.png

Indices

There are a bunch of related indices:

  • The element index (teal numbers), linear, row-major, range 0..ROWS*2.
  • The node index (brown numbers), linear, row-major, range 0..ROWS*COLS.
  • The local element vertex index (lavender numbers), counter-clockwise by element, range 0..2.
  • The coordinates of the actual point in space (stored with the element's struct, as well as the grid's struct)

Problem

In order to get to the next step in the algorithm, I need to iterate over each node and compute some sums of values indexed by local element indices and store them in another matrix. If, for example, I'm on node 8, my element lookup/access function is generically V(el_index, start_vertex, end_vertex), and my matrix of outputs is S(start_node, end_node):

  1. S(8,8) = V(el_14, vert_1, vert_1) + V(el_15, vert_1, vert_1) + V(el_04, vert_0, vert_0) + V(el_03, vert_0, vert_0) + V(el_2, vert_2, vert_2) + V(el_13, vert_2, vert_2)
  2. S(8,9) = V(el_15, vert_1, vert_2) + V(el_4, vert_0, vert_2)

and so on, for all of the connections (teal lines) from node 8. (The connections are symmetric, so once I compute S(7,8), I don't need to compute S(8,7).)

The problem is, the grid (and therefore everything else) is parameterized at runtime, so which node index + adjacency direction corresponds to which element index is dynamically determined. I need to tell the program, "Get me the element indices where the node index of vert_x is my current node index." That's the instruction that tells the program which element to access in V().

Is there a way I can relate these indices in a simple and transparent manner in Rust?

Attempts

  1. I tried computing some simple arithmetic functions modulo the row stride of the node matrix, but the result is messy and hard to debug, as well as requiring verbose bounds checking.
  2. I tried creating three HashMaps keyed by the different vertices of each triangular element, holding the values at each vertex, but the problem is that adjacent triangles share vertex numbers as well as spatial coordinates.
  3. I considered keying a HashMap with multiple keys, but the Rust docs didn't say anything about a HashMap with multiple keys.
bright-star
  • 6,016
  • 6
  • 42
  • 81
  • how did you come to this problem? – Fattie Feb 05 '17 at 22:08
  • @JoeBlow This problem is basically the heart of what makes FEM hard to program. A bunch of calculations are elementwise, but they get hooked up into a larger calculation at the end of the algorithm by gathering up sets of them organized by their adjacency. Those elementwise calculations produce small matrices of their own, even. – bright-star Feb 05 '17 at 22:12
  • so you're addressing a problem in finite element method. trying to solve a partial differential equation maybe? is it in the abstract, or something to do with image processing, laminar flow, or ?? Sorry, I was just nosey! :) chees... – Fattie Feb 05 '17 at 22:32
  • 1
    Hahahaha I didn't know what you meant. I'm implementing it for electrostatics now, namely Laplace's equation. It seems to work for anything where you can define tractable element functions that form integrands of some larger functional. – bright-star Feb 05 '17 at 22:37
  • fascinating. are you actually doing engineering (ie "to build an antenna" / "death ray") or some such, or abstract math? Again, sorry, just being nosey - far too drunk to assist :) – Fattie Feb 05 '17 at 22:42
  • Well, it's true that I could model some death rays with this if I could finish the danged thing ;) – bright-star Feb 05 '17 at 22:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134940/discussion-between-bright-star-and-joe-blow). – bright-star Feb 06 '17 at 05:03
  • 1
    @bright-star: I am sorry but being partially color-blind I just don't get your figure at all... would you have some naive code for computing S(8, 8) and S(8, 9) instead? Also, what are the range of values (element index, node index, local element vertex index and coordinates), it seems to me the vertex index is in `0..3` from the diagram. Finally, have you thought about eliminating bounds-checking by having a "null" outer-layer? – Matthieu M. Feb 06 '17 at 09:34
  • @MatthieuM. Crap, sorry! I didn't think of that. Let me change the palette to something more accessible. – bright-star Feb 06 '17 at 16:10
  • 2
    @bright-star: no worries; as mentioned a textual description might be easier anyway (don't know if any blind developer frequents SO, but images would be inaccessible to them). – Matthieu M. Feb 06 '17 at 16:12
  • @MatthieuM. I added ranges to the index descriptions. I don't think it makes sense in Rust to have a `Option` type (nullable) as an outer layer for this structure, but you're welcome to propose it. I'm not sure I can offer any naive code for `S()`, but I'll add a remark that `V()` is irrelevant to the problem. – bright-star Feb 06 '17 at 17:05
  • 1
    *I considered keying a `HashMap` with multiple keys, but the Rust docs didn't say anything about a `HashMap` with multiple keys.* A tuple can be a `HashMap` key; would that help? – user4815162342 Feb 07 '17 at 21:12
  • @user4815162342 That's a good idea. I sort of encoded the relevant value in the order of a fixed array, as you can see in the answer I posted. – bright-star Feb 07 '17 at 21:42

1 Answers1

0

I think this is a possible workaround, but I hope there's something better:

Nest one HashMap in another as per this question, with the node index as the first key, the element index as the second key/first value, and the element itself as the second value.

So, the iteration can look like this:

  1. Iterate through grid node index N
  2. Get all elements with N as the first key
  3. Note that the vertex indices and (relative) element indices have the following patterns, depending on where you number from:

    Node index numbering beginning from matrix top-left (usual in this programming domain):

    | Element (index ordinal) | Vertex |
    |-------------------------|--------|
    | 0 (min)                 | 1      |
    | 1                       | 2      |
    | 2                       | 1      |
    | 3                       | 2      |
    | 4                       | 0      |
    | 5                       | 0      |
    

    Node index numbering beginning as picture, from lower-left:

    | Element (index ordinal) | Vertex |
    |-------------------------|--------|
    | 0 (min)                 | 2      |
    | 1                       | 0      |
    | 2                       | 0      |
    | 3                       | 2      |
    | 4                       | 1      |
    | 5                       | 1      |
    

Since you can hardcode the relative order of elements like this, it might be better to use a HashMap<usize, Vec<Element>>, or even HashMap<usize, [Element; 6]>.

This method brings up the question of how to relate node index to element indices dynamically. How do you know which elements to insert into that HashMap? One way to accomplish this is to record the nodes the element vertices correspond to in the element struct as well, in the same order.

At that point, you can compute a list like adjacent_elements as you iterate through the matrix, and use the above patterns to figure out what to access (sadly, with bounds checking).

bright-star
  • 6,016
  • 6
  • 42
  • 81
  • Since the function for generating elements starts with the knowledge of which nodes to generate when (in the triangular case, a pair of elements whose "upper/lower left" vertex is the current node under iteration), this could be parameterized by the node index in the same way. That might be overcomplicating things again. – bright-star Feb 05 '17 at 20:35
  • Generating elements on the fly while iterating as previous comment suggests is too complicated: elements will be generated repeatedly, since they're adjacent to three nodes. – bright-star Feb 05 '17 at 20:58