0
$\begingroup$

While solving my study problem (I need to programme an algorithm called "slab decomposition") I faced some troubles connected with sorting an array. So, I have an association array, where a key is a number of a slab and a value is a list of cells that slab consists of.

I've written a function that searches a minimal y-value of the cell in the slab, here it is:

 MyMin[cell_, slab_] := 
 Module[{pair = {}, y = {}}, For[i = 1, i <= Length@cell, ++i,
   If[Min[cell[[i, 1]], cell[[Mod[i, Length@cell] + 1, 1]]] < 
       slab[[1]] && (Max[cell[[i, 1]], 
          cell[[Mod[i, Length@cell] + 1, 1]]] > slab[[2]] || 
        Abs[Max[cell[[i, 1]], cell[[Mod[i, Length@cell] + 1, 1]]] - 
           slab[[2]]] < 10^-10) || 
     Max[cell[[i, 1]], cell[[Mod[i, Length@cell] + 1, 1]]] > 
       slab[[2]] && (Min[cell[[i, 1]], 
          cell[[Mod[i, Length@cell] + 1, 1]]] < slab[[1]] || 
        Abs[Min[cell[[i, 1]], cell[[Mod[i, Length@cell] + 1, 1]]] - 
           slab[[1]]] < 10^-10),
    AppendTo[pair, {i, Mod[i, Length@cell] + 1}]]];
  If[pair === {}, Return[Infinity]];
  For[i = 1, i <= Length[pair], ++i, 
   For[j = 1, j <= 2, ++j, 
    AppendTo[
     y, ((slab[[j]] - 
         cell[[pair[[i, 1]], 1]]) (cell[[pair[[i, 2]], 2]] - 
         cell[[pair[[i, 1]], 2]]))/(cell[[pair[[i, 2]], 1]] - 
        cell[[pair[[i, 1]], 1]]) + cell[[pair[[i, 1]], 2]]]]]; 
  Return[Min[y]]]

I try to create an association array where the lists as the values will be sorted by the cell's minimal coordinate in this slab.

<|Table[i -> 
    SortBy[cellsInSlabs[i], MyMin[cells[[#]], slabs[[i]]] &], {i, 
    Length@slabs}]|>;

Unfortunately, this command gives the right answer only for 8 or 9 first slabs, then the arrays are just sorted in ascending order. But when I apply this command for the random slab outside the Table, it works correct. E.g.:

In[42]:= SortBy[cellsInSlabs[93], MyMin[cells[[#]], slabs[[93]]] &]

Out[42]= {26, 61, 47, 71, 134, 136, 167, 148, 149, 115, 112, 50, 32}

If you managed to understand me and my problem, please, write down here, cause I just can't understand what's wrong

Some explanations. cellsInSlabs - the association array with the structure: "the number of the slab" -> "the list of numbers of cells in this slab". E.g. (firts 10 elements):

<|1 -> {15, 16, 40}, 2 -> {13, 15, 16, 17, 18, 40}, 
 3 -> {12, 13, 14, 15, 16, 17, 18, 19, 40}, 
 4 -> {12, 14, 16, 17, 18, 19, 40, 93, 95}, 
 5 -> {12, 14, 17, 18, 19, 91, 93, 95, 122}, 
 6 -> {12, 14, 17, 18, 19, 20, 38, 39, 66, 91, 93, 95, 122}, 
 7 -> {12, 14, 20, 38, 39, 66, 90, 91, 93, 95, 122}, 
 8 -> {20, 38, 39, 66, 88, 90, 91, 93, 94, 95, 122}, 
 9 -> {20, 38, 39, 66, 88, 90, 91, 94, 122, 145, 146}, 
 10 -> {11, 20, 38, 39, 41, 55, 56, 66, 88, 90, 91, 94, 122, 145, 
   146}|>

cells is a list of lists of points of verices, so an element of this list looks like {{x1,y1},{x2,y2},{x3,y3}} (I have only triangles in my task). E.g. (from 1st to the 5th)

{{{1.5, 0}, {1.47106, 0.195487}, {1.32664, 0.0868638}}, {{1.47106, 
   0.195487}, {1.39, 0.375888}, {1.2832, 0.287133}}, {{1.32664, 
   0.0868638}, {1.47106, 0.195487}, {1.2832, 0.287133}}, {{1.39, 
   0.375888}, {1.26934, 0.53282}, {1.2832, 0.287133}}, {{1.26934, 
   0.53282}, {1.1212, 0.664302}, {1.12445, 0.42743}}}

So if I write cells[[cellsInSlabs[1][[1]]]], I get {{-1.47106, 0.195487}, {-1.5, 0}, {-1.33672, 0.147156}}, which is the list of points of the vertices of the 15th cell.

slabs is a list consisting of lists that contain an initial and a final x-coordinate of each slab. E.g. (first 5 slabs)

{{-1.5, -1.47106}, {-1.47106, -1.39}, {-1.39, -1.33672}, {-1.33672, \
-1.30743}, {-1.30743, -1.26934}}

As I already mentioned, I have some troubles while making a Table. If I create it, I get these results (from 1st to 15th slab):

<|1 -> {16, 40, 15}, 2 -> {17, 18, 16, 40, 15, 13}, 
 3 -> {19, 17, 18, 16, 40, 15, 13, 14, 12}, 
 4 -> {19, 17, 18, 16, 40, 95, 14, 12, 93}, 
 5 -> {19, 17, 18, 93, 14, 12, 91, 95, 122}, 
 6 -> {19, 17, 18, 93, 14, 12, 20, 38, 39, 66, 91, 95, 122}, 
 7 -> {93, 14, 12, 20, 38, 39, 66, 90, 91, 95, 122}, 
 8 -> {20, 93, 95, 38, 39, 66, 88, 90, 91, 94, 122}, 
 9 -> {20, 38, 39, 66, 88, 90, 91, 94, 122, 145, 146}, 
 10 -> {11, 20, 38, 39, 41, 55, 56, 66, 88, 90, 91, 94, 122, 145, 
   146}, 11 -> {11, 20, 39, 41, 55, 56, 88, 90, 91, 94, 119, 121, 122,
    145, 146}, 
 12 -> {11, 41, 55, 56, 88, 89, 90, 91, 92, 94, 119, 121, 122, 145, 
   146}, 13 -> {11, 41, 55, 56, 88, 89, 92, 94, 119, 121, 145, 146, 
   147}, 14 -> {11, 41, 55, 56, 89, 92, 119, 121, 145, 146, 147, 196},
  15 -> {11, 21, 37, 41, 55, 56, 65, 75, 89, 92, 119, 121, 145, 146, 
   147, 196}|>

The output is correct for 1-8 slabs (the numbers of cells are sorted there by their minimal y-coordinate in this very slab, the way of sorting you can see above). But then the numbers of slabs are just sorted in the ascending way. If I sort any of these arrays outside the table, the output is correct. Here is the e.g. for 12th slab:

In[47]:= SortBy[cellsInSlabs[12], MyMin[cells[[#]], slabs[[12]]] &]

Out[47]= {41, 56, 89, 92, 90, 91, 122, 145, 146, 94, 88, 121, 119, 55, 11}

I check it with the following command:

In[48]:= 
SortBy[cellsInSlabs[12], MyMin[cells[[#]], slabs[[12]]] &] /. 
 y_Integer :> MyMin[cells[[y]], slabs[[12]]]

Out[48]= {-0.693474, -0.644355, -0.421685, -0.410052, -0.154418, \
-0.154418, -0.14279, -0.14279, 0.0683472, 0.170552, 0.259021, \
0.377257, 0.442765, 0.45263, 0.641756}
$\endgroup$
2
  • 4
    $\begingroup$ Can you please rewrite your question with the following information: the structure of a cell including a small example, the structure of a slab including a small example, the sorting rule you're trying to implement including expected output for some sample input(s). $\endgroup$ Commented Jan 5 at 22:43
  • 4
    $\begingroup$ As it stands, we don't have cellsInSlabs or slabs or cells to look at. We have no idea what the output {26, 61, 47, 71, 134, 136, 167, 148, 149, 115, 112, 50, 32} means. And you're expecting us to reverse engineer code that you've already said is wrong but that we have no way of knowing how it might be wrong. $\endgroup$ Commented Jan 5 at 22:51

3 Answers 3

1
$\begingroup$

Okay, I went ahead and spent some time on this, and it's probably a risk without your test data, but whatever...

ExtremaInXInterval helper function

This basically truncates an edge to some x interval and then extracts the endpoints. The x interval here is not a slab directly, but will be the part of the slab that intersects with the edge. This function is called from MinYInSlab, so look at that function to see how the arguments are created to pass to ExtremaInXInterval. I hope that I've named the symbols clearly (if superfluously) so the implementation can be understood.

ExtremaInXInterval[edge:{pt1 : {x1_, y1_}, pt2 : {x2_, y2_}}, xinterval : Interval[{xmin_, xmax_}]] :=
  With[
    {edgeVector = pt2 - pt1,
     scalingFactors = {Max[xmin, x1] - x1, Min[xmax, x2] - x1}/(x2 - x1)},
    pt1 + edgeVector*# & /@ scalingFactors]

Finding the y minimum

The strategy is to find two sets of candidate points, join the two sets, and then minimize the y coordinate. The two sets are: (1) vertices of the cell that are in the slab, and (2) endpoints of edges obtained by mapping ExtremaInXInterval over the edges. For (2), we ignore edges that are wholly contained in the slab, and we ignore edges that have no intersection with the slab (in the first case, we will have already collected those points in (1), and in the second case these points are excluded by definition).

This can be cleaned up and streamlined, but I'm trying to be clear and explicit about the strategy.

MinYInSlab[cell : {{_, _} ..}, slab : {_, _}] :=
  With[
    {edges = Partition[cell, 2, 1, {1, 1}]},
    With[
      {candidateVerticesWithinSlab = 
         Select[cell, IntervalMemberQ[Interval[slab], First@#] &],
       edgesWithinSlab = 
         Select[edges, IntervalMemberQ[Interval[slab], Interval[First /@ #]] &]},
      With[
        {edgesWithIntervalIntersection =
           DeleteCases[
             Comap[{SortBy[First], IntervalIntersection[Interval[slab], Interval[First /@ #]] &}] /@ 
              Complement[edges, edgesWithinSlab], 
             {_, Interval[]}]},
        Min[Join[candidateVerticesWithinSlab, Catenate[ExtremaInXInterval @@@ edgesWithIntervalIntersection]][[All, -1]]]]]]

The Interval-related stuff can pretty easily be replaced with checks for <= and >= using the raw coordinates. I just thought it was a bit easier to understand with a slight abstraction.

Demonstration

Test data:

SeedRandom[17];
cells = RandomReal[{-5, 5}, {20, 3, 2}];
slabs = Partition[Subdivide[-5, 5, 10], 2, 1];

Check all cells against first slab:

MinYInSlab[#, slabs[[1]]] & /@ cells
(* {\[Infinity], -0.126856, \[Infinity], \[Infinity], \[Infinity], \[Infinity], \[Infinity], 4.02875, \[Infinity], \[Infinity], \[Infinity], -1.07985, \[Infinity], 1.52114, \[Infinity], \[Infinity], \[Infinity], \[Infinity], -1.93656, 4.44364} *)

If there is no intersection between a cell and a slab, the min y is \[Infinity]. It seems like you already have a way to collect cells by slab intersection, so this shouldn't be an issue.

Obviously I implemented this to work with cells and slabs directly, not via an index into an association. I'm not sure how important it is to have the indices--it seemed an unnecessary indirection.

Here is a mechanism I came up with to test by inspection:

With[
  {slabIdx = 1, cellIdx = 2},
  Graphics[
    {Red, If[MinYInSlab[cells[[cellIdx]], slabs[[slabIdx]]] < Infinity, InfiniteLine[{0, MinYInSlab[cells[[cellIdx]], slabs[[slabIdx]]]}, {1, 0}], Nothing], 
     Blue, InfiniteLine[{slabs[[slabIdx, 1]], 0}, {0, 1}], InfiniteLine[{slabs[[slabIdx, 2]], 0}, {0, 1}], 
     EdgeForm[Black], Polygon[cells[[cellIdx]]]},
    Axes -> True]]

enter image description here

Alter the slabIdx and cellIdx to see pairwise application of the MinYInSlab function. I spot checked several cases--I didn't test exhaustively.

$\endgroup$
0
$\begingroup$

I'm going to just take a stab at it, and hopefully that will help us figure out what you're trying to do. I don't expect this to be correct, but it'll be a concrete implementation that you can critique. Then maybe we'll be able to evolve it to what you need.

As I understand it, you're trying to sort a list of indices, where each index points to a cell in some list of cells. The sorting you want to do is by the minimal y-coordinate of the cells. We can start with some basic helper functions, but before we do that, we'll need some data to play with.

SeedRandom[17];
newCells = RandomReal[{-5, 5}, {10, 3, 2}]
(* {{{4.49674, 2.96639}, {-3.49076, 3.12548}, {3.29782, 2.71363}},
    {{-3.26279, -1.40317}, {-4.43665, 0.629102}, {2.74742, 2.37383}},
    {{-0.63818, 1.21733}, {-0.937151, -2.22011}, {-3.76101, 2.66729}}, 
    {{0.41262, -0.713651}, {0.997959, 0.105716}, {2.61666, 4.3232}}, 
    {{1.61457, 0.572688}, {2.87354, 2.28397}, {2.70561, 3.80333}}, 
    {{-0.0746233, 1.73154}, {-0.461879, 0.861466}, {4.75557, 2.85115}}, 
    {{4.44988, 0.022977}, {-1.22912, 3.86266}, {-2.59208, -2.57831}}, 
    {{-4.09706, 4.09044}, {2.40559, -0.0425995}, {-1.41558, 3.45497}}, 
    {{1.13345, 4.05855}, {-0.193312, 3.52328}, {-3.04986, -4.54486}}, 
    {{-0.873478, 1.194}, {1.79089, -4.06452}, {0.0309595, 2.66797}}} *)

newSlabs =
  <|1 -> {1, 2, 3},
    2 -> {3, 4, 5, 6},
    3 -> {6, 7, 9, 10},
    4 -> {4, 5, 6, 7, 8}|>;

Now for a helper to extract the minimum y value from a cell:

CellYMin[cell : {{_, _} ...}] := Min[cell[[All, -1]]]

A quick test:

CellYMin /@ newCells
(* {2.71363, -1.40317, -2.22011, -0.713651, 0.572688, -2.85115, -2.57831, -0.0425995, -4.54486, -4.06452} *)

Now let's say we have a list of cell indices that we want to sort. We can use SortBy:

SortBy[Range@Length@newCells, CellYMin[newCells[[#]]] &]
(* {9, 10, 6, 7, 3, 2, 4, 8, 5, 1} *)

We can map over our slab association:

SortBy[CellYMin[newCells[[#]]] &] /@ newSlabs
(* <|1 -> {3, 2, 1}, 
     2 -> {6, 3, 4, 5}, 
     3 -> {9, 10, 6, 7}, 
     4 -> {6, 7, 4, 8, 5}|> *)

Alternatively, you could sort your cells first and use that ordering rather than compute the min y value each time:

order = OrderingBy[newCells, CellYMin]
(* {9, 10, 6, 7, 3, 2, 4, 8, 5, 1} *)

To use this, let's turn it into a function:

sortFn = PositionIndex[order]
(* <|9 -> {1}, 10 -> {2}, 6 -> {3}, 7 -> {4}, 3 -> {5}, 2 -> {6}, 4 -> {7}, 8 -> {8}, 5 -> {9}, 1 -> {10}|> *)

Try it out:

SortBy[sortFn] /@ newSlabs
(* <|1 -> {3, 2, 1}, 
     2 -> {6, 3, 4, 5}, 
     3 -> {9, 10, 6, 7}, 
     4 -> {6, 7, 4, 8, 5}|> *)

I'm wondering why you needed slabs (this thing with x coordinates in it) and why you tried to use slabs in your sort strategy. I'm assuming I've misunderstood something in your explanation. Also, without your actual data, I have no way to test this fully so you'll have to provide feedback if this doesn't work.

$\endgroup$
6
  • $\begingroup$ You a little bit misunderstood my task. Slab decomposition method is needed for identifying a position of a point in a system of cells. To do it I divide a plane into slabs by x-coordinate, and then in the relevant slab I search for the cell in which my point is located. That's why I made MyMin function. With the help of it I find the minimal point of a cell in this very slab (I tried to draw it here) $\endgroup$ Commented Jan 6 at 21:31
  • $\begingroup$ If you need, I can provide you with the input data file and my mathematica file $\endgroup$ Commented Jan 6 at 21:34
  • $\begingroup$ A little correct: with the help of MyMin I find the minimal y-coordinate of a concrete cell in a concrete slab $\endgroup$ Commented Jan 6 at 21:44
  • $\begingroup$ Okay, so when you said cellsInSlabs - the association array with the structure: "the number of the slab" -> "the list of numbers of cells in this slab" , "in" doesn't mean "contained in" but it means "intersects with". Is that correct? $\endgroup$ Commented Jan 6 at 23:06
  • $\begingroup$ So, a slab is an infinite vertical stripe of some width. And you specify your slabs as pairs of x-coordinates. Cells can be any size, and are not assumed to be contained within a slab. You need a function that takes a slab and a cell and finds the minimal y value on the perimeter of the cell that is within the slab. You need a function that sorts all cells within a slab according to the results of the previous function. You want to apply this function across all slabs. Really, it's across all slab-cell pairs, but you've already determined which cells intersect which slabs. Is that correct? $\endgroup$ Commented Jan 6 at 23:12
0
$\begingroup$

This is a partial answer, because maybe you don't want to go this direction. It's just a way to be lazy and let region functionality do the work for you. To find the minimum y coordinate of all the points on the perimeter of the cell, you could do this:

CellMinYInSlab[cellVertices_, {x0_, x1_}] :=
  Min[
    MeshCoordinates[
      BoundaryDiscretizeRegion[
        RegionIntersection[
          ImplicitRegion[x >= x0 && x <= x1, {x, y}], 
          Polygon[cellVertices]]]][[All, -1]]]

From there, do the usual mapping over all cells in a slab, etc etc.

$\endgroup$
3
  • $\begingroup$ Is there any way to do it without region functionality or somehow accelerate it? What's more, this code works almost correct now, in the result I have 4 mistakes out of 15 tested points $\endgroup$ Commented Jan 7 at 0:00
  • $\begingroup$ yeah, the region stuff can be slow. You'd just have to basically find intersections with slab boundaries and polygon perimeter if the min-y vertex wasn't fully contained in the slab. I assume that's what your original code tried to do, but honestly I still haven't tried to reverse engineer it. As for still having 4 mistakes, without your actual data to test with, it'll be hard to figure it out. Also, you might double check that those 4 are actually in error--maybe you've misinterpreted the data somehow. $\endgroup$ Commented Jan 7 at 17:33
  • $\begingroup$ As for implementing this again without region-related stuff, I'm not sure how much time I'll be willing to invest in that in the near future. And I probably won't bother at all if you don't provide some real data. Also, if the real data is very large, you don't have to provide it all--just a subset that shows some cases working and some cases not. Minimizing the data to isolate the problem is actually a useful exercise in and of itself, so I encourage you to do that. If you can get it down to ~2 slabs and ~4 cells where one slab correctly finds the min and one doesn't, that'd be ideal. $\endgroup$ Commented Jan 7 at 17:37

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.