3
$\begingroup$

FromPolarCoordinates has a very nice feature: if you supply several points, e. g. {{r1,phi1},{r2,phi2},...}, it will return a nested list with the converted cartesian coordinates in one go.

Right now, I have a helper function that is just the name-abbreviated form of the built-in function:

FromPolar[{r_, \[Phi]_}] := FromPolarCoordinates[{r, \[Phi]}]

The obvious drawback is that I have to call FromPolar once for every list (= point) separately. Additionally, it would be helpful to sort out all the points where the radius equals zero: for each element in the nested list, check if the first entry is zero, if yes, return {0,0}.

Something like FromPolar[{r_, \[Phi]_}] := If[r == 0, {0, 0}, FromPolarCoordinates[{r, \[Phi]}]], but for several point at once.

$\endgroup$
1
  • 1
    $\begingroup$ It's probably safer to literally identify the symbols: ClearAll[FromPolar]; FromPolar = FromPolarCoordinates; $\endgroup$
    – thorimur
    Aug 16, 2021 at 6:12

1 Answer 1

5
$\begingroup$

Ok, so if you were just using an abbreviation, I'd literally identify the symbols via Set: FromPolar = FromPolarCoordinates;. But since you want to modify it slightly, we can add another definition. To be careful, though, we want to make sure we only Map (/@) over lists of lists, and NOT over lists of other kinds of expressions. For that we'll check r and \[Phi] are not Lists via Except.

ClearAll[FromPolar];

FromPolar[l : {{_, _} ...}] := FromPolar /@ l

FromPolar[{r : Except[_List], \[Phi] : Except[_List]}] :=
    If[r == 0, {0, 0}, FromPolarCoordinates[{r, \[Phi]}]]

What we need to watch out for is when the argument is a pair of lists, e.g. {{0,1},{1,1}}; without Except, Mathematica would try to put the list {0,1} in for r_ and {1,1} in for \[Phi]_.

Technically, though, we didn't have to use Except; we could have just evaluated the l : {{_, _} ...} definition first and Mathematica would always check to see if the argument is a list of lists first, but this is a bit safer (at the cost of a little overhead).


You also expressed interest in a one-definition form! A typical way might be to use f[arg_] := Switch[arg, patt1, val1, patt2, val2, ...] or Replace. Here's a nifty way to do that with level specification in Map. When unspecified, Map (/@) assumes that you are applying its first argument to every element on exactly level 1, e.g. f /@ {a,b,c} is syntactic sugar for Map[f, {a,b,c}, {1}], and yields {f[a],f[b],f[c]}. (The list brackets in {1} mean (by convention) "exactly", as opposed to "up through" without them.) But you can also map at exactly level 0; Map[f, {a,b,c}, {0}] gets you f[{a,b,c}].

We also use Apply (@@), which takes an expression f and replaces the head of the second argument with it. E.g. f @@ g[a,b] (equivalently Apply[f, g[a,b]]) yields f[a,b], and likewise (since {a,b,c} is syntactic sugar for List[a,b,c]) f @@ {a,b,c} yields f[a,b,c]. Apply applied to a single argument, e.g. Apply[f], is the operator form of Apply: apply that to an expression and you get the normal Apply, i.e. Apply[f][expr] yields Apply[f, expr].

Finally, we'll use anonymous functions. For clarity we can use e.g. {a,b} |-> a + b for an anonymous function with two formal parameters that adds its arguments; we could also equivalently use #1 + #2 &. I'll use the latter for brevity.

So we can check if we should Map or not based on the argument pattern, then feed that into the level spec. Note that Boole converts True/False into 1/0.

ClearAll[FromPolar];

FromPolar[arg : ({{_?NumericQ, _?NumericQ}...} | {_?NumericQ, _?NumericQ})] :=
  Map[Apply[If[#1 == 0, {0,0}, FromPolarCoordinates[{#1, #2}]] &],
    arg,
    {Boole[MatchQ[arg, {___List}]]}
  ]

I didn't bother matching the full pattern in the Boole, since we know that if we've gotten that far, we must be in one of these two cases.

You'll notice I do a little more argument pattern-matching here to ensure we're using numeric values. When you pattern-match with ?, you're moving outside the pattern matcher and bringing in the main evaluator, so it's a bit more expensive than our other pattern. But it is a bit safer. Up to you if you want that!

You can also replace Apply[...] with simply If[First[#] == 0, {0,0}, FromPolarCoordinates[#]] &. This is in fact a little faster! But Apply is very useful, and good to get experience with (as with Map). As you can see, there are lots of ways to extract and address parts of expressions in Mathematica: with pattern matching, positionally, and structurally/functionally (e.g. head replacement (Apply) and Map). It's not always immediate which will be fastest, but generally the last and first are considered the best and most readable style. Sometimes, though, you really just want the First element. :)

Let me know if you're not familiar with anything here! I'd be happy to explain :)

Aside

By the way, if you're doing numeric stuff, you might also find it convenient to automatically put \[Phi] in the range $(-\pi,\pi]$, otherwise FromPolarCoordinates will throw an error. For some reason FromPolarCoordinates doesn't do this automatically! Fixing this means replacing \[Phi] in FromPolarCoordinates[{r, \[Phi]}] in the second definition with something like Mod[\[Phi], 2 Pi, Pi]—except that puts it into the range $[-\pi, \pi)$, and we need $(-\pi, \pi]$. So, this is weird: thanks to the symmetry of the interval, we can actually handle this by negating things twice, and use -Mod[-\[Phi], 2 Pi, -Pi] in place of \[Phi] in our call to FromPolarCoordinates.

$\endgroup$
12
  • $\begingroup$ I thought that FromPolar[list : {{_, _} ...}] := FromPolarCoordinates[list] was enough to walk the entire nested list, but it seems to be missing the map command. How can I coalesce the Map and the function definition? $\endgroup$ Aug 16, 2021 at 12:33
  • $\begingroup$ @user2286339 There are two things here: FromPolarCoordinates, under the hood, likely has two definitions for different argument patterns, just like our FromPolar here—and likewise, in the {{_,_}...} argument case, it likely Maps itself (or an internal version of itself) over the argument l. Map (/@) is what you want to use to "walk" a list! :) Second, you don't want map the function FromPolarCoordinates over your list—you want to map the function which [checks if the first coordinate is 0 via If, and only if not, applies FromPolarCoordinates] over the argument list. $\endgroup$
    – thorimur
    Aug 16, 2021 at 22:38
  • $\begingroup$ @user2286339 let me know if this answers your question—I might have misinterpreted. $\endgroup$
    – thorimur
    Aug 16, 2021 at 22:42
  • $\begingroup$ Your code sample has two definitions - the one with the Map followed by the actual implementation. What I am looking for is how I can merge them, if possible. $\endgroup$ Aug 16, 2021 at 22:55
  • $\begingroup$ @user2286339 yes, it's quite customary in mathematica to attach more than one definition to a symbol! the argument patterns are checked each time it's applied, and the definition of whichever matches is called. it's possible to make them just one by checking the argument pattern with a Switch, or maybe with a Replace for better extraction of expression parts, but is there a particular reason you want to do this? multiple definitions is typically considered better style afaik. $\endgroup$
    – thorimur
    Aug 16, 2021 at 23:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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