3
$\begingroup$

I'm reading an excellent answer regarding Mobius transformation and I'm trying to understand the last line of the following code:

(* The initial grid in the xy-plane *)
gridSpan = 1.2; step = 0.2;
plotSpan = 12;
xGrid = Table[{x, y, 0}, {y, -gridSpan, gridSpan, step}, 
  {x, -gridSpan, gridSpan, step/10}];
yGrid = Table[{x, y, 0}, {x, -gridSpan, gridSpan, step}, 
  {y, -gridSpan, 
    gridSpan, step/10}];
grid = Join[xGrid, yGrid];
(* {0,0} is problematic. *)
grid = DeleteCases[grid, {_?(NumericQ[#] &), _, _}?(Norm[#] < 0.0001 &), Infinity];

The documentation of DeleteCases says:

DeleteCases[expr,pattern,levelspec] removes all parts of expr on levels specified by levelspec that match pattern.

I have difficulties understanding the pattern:

{_?(NumericQ[#] &), _, _}?(Norm[#] < 0.0001 &)

and I don't understand what Infinity level means here.

The documentation of PatternTest contains only simple examples.

Would anyone dissect the code (in the last line) or point me to a reference?


For now I can only understand # and & are for pure functions and ? means patter test. But I don't understand the syntax of the whole thing.

$\endgroup$
8
$\begingroup$

In a nutshell, it matches a list of 3 items, the first one must be numeric and the norm of the "vector" must be less than 1/10000.

The pattern that goes {_, _, _} means a list of 3 items or a three dimensional vector.

Add to that NumericQ and the first component must be numeric. NumericQ[#]& is redundant.

Then, Norm[#]<0.0001& means the length of the vector must be less than 1/10000.

Here is a test case that deletes the 3D zero vector:

myList = {
   {a, b, c},
   {1, b, c},
   {0, 0, 0},
   {0, 0},
   {1, 0, 0}
   };

DeleteCases[myList, {_?(NumericQ[#] &), _, _}?(Norm[#] < 0.0001 &)]

(*  {{a, b, c}, {1, b, c}, {0, 0}, {1, 0, 0}}  *)  

For me, the trick to reading it is to look at it without the pattern test parts ?(...). Then, look at it with only one of the pattern test phrases at a time. Pulling the entire pattern out of the DeleteCases as you did in your question also makes it easier to decipher.

When writing patterns like these, we can start with some test cases and build up our pattern, testing each part of it. For instance, we might test the following code to understand what each part does:

DeleteCases[myList,  {_, _, _}  ]
DeleteCases[myList,  {_?NumericQ, _, _}  ]
DeleteCases[myList,  {_, _, _} ?(Norm[#] < 2 &) ]
DeleteCases[myList,  {_, _, _} ?(Norm[#] < 1 &) ]

To understand the Levelspec option of DeleteCases, we really need an example that has more than one level. Let's consider the following code:

newList = {
   {0, 0, 0},
   {1, 0, {1, 0, 0}},
   {2, 0, {0, 0, 0, {2, 0, 0}}},
   {0, 0},
   {3, 0, 0}
   };

DeleteCases[newList,  {_?NumericQ, _, _}  ]
DeleteCases[newList,  {_, _, _} ?(Quiet@Norm[#] < 4 &) ]
DeleteCases[newList,  {_, _, _} ?(Quiet@Norm[#] < 4 &), ∞ ]

Note that the default Levelspec for DeleteCases is {1}, meaning only check the top level, do not try to match lower elements of the nested list.

The 1st DeleteCases returns only {{0,0}}, which indicates all of the other top level elements of newList are lists of 3 elements. But are they vectors in the sense of the Norm?

In the 2nd DeleteCases we added Quiet@ in front of Norm to suppress the warning messages that some top level elements of newList are not something we can take the norm of. Since the oddball top level elements do not satisfy the norm pattern, they are not deleted. The 2nd DeleteCases returns

(*  {{1, 0, {1, 0, 0}}, {2, 0, {0, 0, 0, {2, 0, 0}}}, {0, 0}}  *)

But does the norm pattern match at any lower levels? Now we include the Infinity levelspec. In a nutshell, Infinity tells DeleteCases to check all the levels of a nested list, not just the first. Infinity is especially useful when we apply DeleteCases to complicated expressions whose top level elements are not simple expressions.

Adding the Infinity levelspec will cause DeleteCases to descend as far as possible into each top level element of its argument. Sometimes we do not know ahead of time how many levels there will be, so we use Infinity to cover all the possibilities.

The 3rd DeleteCases returns

(*  {{1, 0}, {2, 0, {0, 0, 0}}, {0, 0}}  *)

With the Infinity levelspec, DeleteCases found the patterns {1,0,0} and {2,0,0} at lower levels and deleted just those patterns.

Note that DeleteCases returned an expression containing a 3D zero vector at one of the lower levels. This 3D zero vector was not deleted because DeleteCases only applies the pattern test to parts that are in the original. After DeleteCases changed a 4-component list to 3-component list containing a 3-component zero vector, it did not pattern test the 3-component zero vector. More simply, DeleteCases only pattern tests its input and not any of its output.

What would happen if we had used levelspec 1, 2, 3, {2}, or {3} instead of Infinity in the above test cases? Each of these means something different.

$\endgroup$
  • $\begingroup$ Thank you very much for the details! Would you mind explaining what level Infinity means in DeleteCases? $\endgroup$ – Jack Sep 22 '17 at 0:08
  • 1
    $\begingroup$ @Jack Added a new example to illustrate the use of Infinity as the levelspec of DeleteCases. In a nutshell, Infinity means check all the levels, not just the first. Infinity is especially useful when you apply DeleteCases to a complicated expression that is not a simple list or vector. $\endgroup$ – LouisB Sep 22 '17 at 1:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.