14
$\begingroup$

I have a function f which takes a number as input, and returns a list of numbers (the length of the list is constant). f is hard to calculate (each evaluation takes a long time).

I want to plot the different components of f in different colors.

If I use this command:

Plot[f[x], {x, -2, 2}]

all the lines are drawn in the same color.
If I use this command:

Plot[{f[x][[1]], f[x][[2]], f[x][[3]]}, {x, -2, 2}]

(assuming the list has three components) the lines are drawn in different colors, but the function is called three times the necessary amount.

Note that this is a numeric function, it cannot be evaluated with a symbolic argument (i.e. the function definition begins with f[x_Real]:=), so there is no use in using Evaluate like in this question.

$\endgroup$
4

4 Answers 4

9
$\begingroup$

At risk of stating the obvious, if you are willing to give up the adaptive sampling, exclusions, etc. of Plot you could use ListLinePlot:

f[x_?NumericQ] := x + Mod[x, {1, 2, 3}]

ListLinePlot[Transpose@Table[f[x], {x, 0, 10, 0.01}], PlotStyle -> Thick] 

Mathematica graphics


Better I think is to restyle the Graphics data produced by plot, as Heike did for Plotting piecewise function with distinct colors in each section and which I refactored in my answer. Applied here:

Module[{i = 1},
  Plot[f[x], {x, 0, 10}, PlotStyle -> Thick] /.
    x_Line :> {ColorData[1][i++], x}
]

enter image description here


Even nicer is Simon Woods' method which styles the plot while it is created, posted in answer to:

Also useful and very interesting is the solution by wxffles in the follow-up question:

$\endgroup$
4
  • 2
    $\begingroup$ (+1) Because MMA uses adaptive sampling for each component of the function separately, memoizing doesn't usually save a lot of evaluation: the arguments used to plot the first component only slightly overlap the arguments used to plot the second component, etc. (Sow the values of x with an EvaluationMonitor and then ListPlot them afterwards to see this.) Thus, this appears to be the only really effective workaround when good plotting arguments (values of $x$) are known in advance. $\endgroup$
    – whuber
    Jun 5, 2012 at 14:21
  • 1
    $\begingroup$ @whuber: My thoughts exactly. It seems that there is no way to make MMA's adaptive algorithm take into account all the values of a certain component that were already calculated as a result of evaluating f for the other components, and that's a shame. If evaluating f three (or more) times the necessary amount is more costly than the evaluations that the adaptive algorithm saves us (which is almost always the case for functions that take reasonably long to evaluate), this answer seems to be the best workaround. Accepted. $\endgroup$
    – Joe
    Jun 6, 2012 at 8:32
  • $\begingroup$ @Joe thanks for the Accept. Sorry I didn't have something better for you. $\endgroup$
    – Mr.Wizard
    Jun 6, 2012 at 8:35
  • $\begingroup$ +1 for the Module[{i=0}...ColorData[1][i++] alone! I'd have spent hours fiddling with MapIndexed's... $\endgroup$
    – Aisamu
    Apr 28, 2014 at 12:13
8
$\begingroup$

Please consider the following simple example

i = 0;
f[x_?NumericQ] := {x^2, 2 x^2, x^2 + 1};
Plot[f[x], {x, 0, 1}, EvaluationMonitor :> i++];
i

(* Out[30]= 471 *)

and now the same code for only the first component of f

i = 0;
Plot[x^2, {x, 0, 1}, EvaluationMonitor :> i++];
i

(* Out[33]= 157 *)

If you now recall, that $3\cdot 157 = 471$ you see that even in your Plot[f[x],...] call the function is called more often than one would naively expect. Therefore, I claim you can skip hard thinking about this issue.

$\endgroup$
1
  • $\begingroup$ You are correct. However, if I still want to plot all the components of f with a number of evaluations more close to 157, I need to use the approach described in @Mr.Wizard's answer. $\endgroup$
    – Joe
    Jun 6, 2012 at 8:38
5
$\begingroup$

You could memoize the function:

f[x_] := f[x] = x + Mod[x, {1, 2, 3}]

Plot[{f[x][[1]], f[x][[2]], f[x][[3]]}, {x, 0, 10}, PlotStyle -> Thick]

Mathematica graphics

If you don't want to use memory globally for this or memoize for all calls to f you could make a copy:

Module[{g},
  g[x_] := g[x] = f[x];
  Plot[{g[x][[1]], g[x][[2]], g[x][[3]]}, {x, 0, 10}, PlotStyle -> Thick]
]
$\endgroup$
5
  • $\begingroup$ This is a nice workaround in case there is no way to address the problem directly, and it has the slight disadvantage of memory use. I would like to wait and see if someone comes up with a more direct approach. $\endgroup$
    – Joe
    Jun 5, 2012 at 11:55
  • $\begingroup$ @Joe please do wait; I'm hoping someone has a perfect solution. $\endgroup$
    – Mr.Wizard
    Jun 5, 2012 at 12:00
  • $\begingroup$ From @whuber's comment, I learned that memoizing doesn't actually save function evaluations. $\endgroup$
    – Joe
    Jun 6, 2012 at 8:42
  • 1
    $\begingroup$ @Joe it can save evaluations, but not as many as reliably as I had thought. f[x_Real] := (i++; x + {Sin[x], Sqrt[x], Mod[x, 3]}) then: i = 0; Plot[{f[x][[1]], f[x][[2]], f[x][[3]]}, {x, 0, 10}, PlotStyle -> Thick]; i Compare: i = 0; Module[{g}, g[x_] := g[x] = f[x]; Plot[{g[x][[1]], g[x][[2]], g[x][[3]]}, {x, 0, 10}, PlotStyle -> Thick]]; i $\endgroup$
    – Mr.Wizard
    Jun 6, 2012 at 9:19
  • $\begingroup$ You're right @Mr.Wizard, in your example memoizing saves evaluations, but it does not save two thirds of the evaluations (I got 715 instead of 953). Of course we can't expect exactly two thirds, since there may be some overhead, by I think that even asymptotically we won't get two thirds. $\endgroup$
    – Joe
    Jun 6, 2012 at 9:52
1
$\begingroup$

If your functions are continuous, you may use something like (naive example follows):

f[x_Real] := {2 x, 3 x};
Plot[f[x], {x, -2, 2}] /. {Hue[x__], Line[y__], Line[z__]} -> {Hue[x],
    Line[y], Hue[0, 1, 1], Line[z]}

enter image description here

$\endgroup$
3
  • $\begingroup$ Doesn't work well on my example, does it? (This was one of the first things I tried.) $\endgroup$
    – Mr.Wizard
    Jun 5, 2012 at 12:23
  • $\begingroup$ I also tried to get really clever and use Plot[{##&[], Sequence @@ f[x], ##&[]} . . . but that's no good at all. Maybe you can find a way to make it work. $\endgroup$
    – Mr.Wizard
    Jun 5, 2012 at 12:24
  • $\begingroup$ @Mr.Wizard Works only for continuous functions, as I said ... $\endgroup$ Jun 5, 2012 at 12:25

Your Answer

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

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