4
$\begingroup$

I'm trying to plot a spherical harmonic coloured by phase.

e.g.

psi[l_, m_] := SphericalHarmonicY[l, m, θ, ϕ]
sphWithPhase[f_] := 
 SphericalPlot3D[Abs[f]^2, {θ, 0, Pi}, {ϕ, 0, 2 Pi}, 
  ColorFunction -> 
   Function[{x, y, z, θ, ϕ, r}, Evaluate[Hue[Arg[f]]]], 
  PlotRange -> All, ColorFunctionScaling -> False]
sphWithPhase[psi[1, -1]]

But it doesn't work. TracePrint shows that Function[] is renaming its arguments so that then the theta and phi don't match up to the plotted variables.

Whereas e.g.

SphericalPlot3D[Abs[psi[1, 1]]^2, {θ, 0, Pi}, {ϕ, 0, 2 Pi}, 
 ColorFunction -> 
  Function[{x, y, z, θ, ϕ, r}, 
   Evaluate[Hue[Arg[psi[1, 1]]]]]]

does work.

Can anyone help explain this to me, I find the help pages confusing?

Thanks.

$\endgroup$
  • $\begingroup$ Do you get what you need if you remove the option ColorFunctionScaling -> False and use ColorFunction -> Function[{x, y, z, \[Theta]1, \[Phi]1, r}, Hue[Arg[f /. {\[Theta] -> \[Theta]1, \[Phi] -> \[Phi]1}]]]? $\endgroup$ – kglr Nov 11 at 17:13
  • 1
    $\begingroup$ ... alternatively, ColorFunction -> (Hue[Arg[f /. {\[Theta] -> #4, \[Phi] -> #5}]] &)? $\endgroup$ – kglr Nov 11 at 17:17
  • $\begingroup$ Related: (10086) $\endgroup$ – Mr.Wizard Nov 11 at 18:14
  • 2
    $\begingroup$ I support Lukas Lang's answer and recommendation, but another terse fix: ColorFunction -> (Function[{x, y, z, θ, ϕ, r}, #] & @ Hue[Arg[f]]) $\endgroup$ – Mr.Wizard Nov 11 at 18:41
5
$\begingroup$

Let's look at a simpler example:

makeFunc[body_] := Function[{x}, body]
makeFunc[x^2]
(* Function[{x$}, x^2] *)

As you've correctly identified, the problem is that the arguments of Function are renamed. Interestingly, this replacement is performed during the evaluation of the definition of makeFunc, not as part of the evaluation of Function[…]: The evaluator notices that the expression body contains symbols that might clash with the arguments of the Function[…] expression. To prevent this from happening, the functions arguments are renamed. Some reading on the topic:

To fix the issue, you have a few options:

  • Hide the fact that there is a scoping construct:

    makeFunc[body_] := Function@@Hold[{x}, body]
    makeFunc[x^2]
    (* Function[{x}, x^2] *)
    
  • Perform a manual replacement of the variables inside the Function, as suggested by @kglr:

    makeFunc[body_]:=Function[{x1}, body /. x -> x1]
    makeFunc[x^2]
    (* Function[{x1$}, x^2 /. x -> x1$] *)
    makeFunc[x^2][y]
    (* y^2 *)
    
  • Use the "correct" way to pass symbolic expressions ("correct" as in "used by built-in functions and not dependent on magic variable names"): Pass in the symbols as well:

     makeFunc[body_, sym_] := Function[{sym}, body]
     makeFunc[x^2, x]
     (* Function[{x}, x^2] *)
     makeFunc[y^2, y]
     (* Function[{x}, x^2] *)
    

    While it is the most work for the user, this approach has several advantages:

    • It does not break if the user defined x as something else already - the user can just choose a different symbol here
    • If you employ the proper Hold* attributes, you can even allow the user to reuse already defined symbols, similar to how Table localizes its iterator variable.
    • It does not break when used inside a package, since there is no issue with contexts of the symbols
    • It is very clear in intent, since the user doesn't have to know which symbols are to be used, since they have to be manually specified.
$\endgroup$
  • $\begingroup$ Good lesson, but where is your sphWithPhase[psi[1, -1]]? $\endgroup$ – Alex Trounev Nov 11 at 20:13
  • $\begingroup$ Thank you. I have it working now. $\endgroup$ – CTR Nov 11 at 21:12
  • $\begingroup$ My version: psi[l_, m_] := SphericalHarmonicY[l, m, [Theta], [Phi]] sphWithPhase[f_, [Theta]_, [Phi]_] := SphericalPlot3D[Abs[f]^2, {[Theta], 0, Pi}, {[Phi], 0, 2 Pi}, ColorFunction -> Function[{x, y, z, [Theta], [Phi], r}, Evaluate[Hue[Arg[f]/(2*[Pi])]]], PlotRange -> All, ColorFunctionScaling -> False] sphWithPhase[psi[1, -1] + psi[1, 1], [Theta], [Phi]] $\endgroup$ – CTR Nov 11 at 21:14
3
$\begingroup$

Here we can use function Texture[]. Note that function Arg[f] is discontinuous, so there are two sectors without coverage. Create a texture

 texture[l_, m_] := 
 DensityPlot[
  Arg[SphericalHarmonicY[l, m, \[Theta], \[Phi]]], {\[Theta], 0, 
   Pi}, {\[Phi], 0, 2 Pi}, ColorFunction -> Hue, PlotPoints -> 200, 
  Frame -> False]

Now use this to cover

sphWithPhase[l_, m_] := 
 SphericalPlot3D[
  Abs[SphericalHarmonicY[l, m, \[Theta], \[Phi]]]^2, {\[Theta], 0, 
   Pi}, {\[Phi], 0, 2.01 Pi}, Mesh -> None, 
  PlotStyle -> Texture[texture[l, m]], Lighting -> "Neutral", 
  Boxed -> False, Axes -> False, PlotRange -> All, PlotPoints -> 150]
sphWithPhase[1, -1]

Figure 1

Now we show how to modify the source code to get a similar result. We use a hint from Mr.Wizard.

psi[l_, m_] := SphericalHarmonicY[l, m, \[Theta], \[Phi]]
sphWithPhase[f_] := 
 SphericalPlot3D[Abs[f]^2, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi}, 
  ColorFunction -> (Function[{x, y, z, \[Theta], \[Phi], r}, #] &@
     Hue[Arg[f]/2/Pi]), PlotRange -> All, 
  ColorFunctionScaling -> False, Mesh -> None, Boxed -> False, 
  Axes -> False]
sphWithPhase[psi[1, -1]]

Figure 2

Now we use the hint from @kglr.

psi[l_, m_] := SphericalHarmonicY[l, m, \[Theta], \[Phi]]
sphWithPhase[f_] := 
 SphericalPlot3D[Abs[f]^2, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi}, 
  ColorFunction -> (Hue[(Arg[f /. {\[Theta] -> #4, \[Phi] -> #5}] + 
          Pi)/2/Pi] &), PlotRange -> All, 
  ColorFunctionScaling -> False, Mesh -> None, Boxed -> False, 
  Axes -> False]
sphWithPhase[psi[1, -1]]

Figure 3

$\endgroup$

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.