4

I want to plot several curves of functions which are hard to calculate on the same plot. I would like to add to the same plot the sum of these functions without recalculating them twice at each point.

So instead of

 Plot[{Sin[x], Sin[2 x], Sin[x] + Sin[2 x]}, {x, 0, 6 Pi}]

I would like somthing like

 Plot[{aa = Sin[x], bb = Sin[2 x], aa + bb}, {x, 0, 6 Pi}]
m_goldberg
  • 107,779
  • 16
  • 103
  • 257
  • 3
    precalculate points in advance and then use ListPlot? – BlacKow May 10 '16 at 15:06
  • 3
    Try Plot[With[{aa = Sin[x], bb = Sin[2 x]}, {aa, bb, aa + bb}], {x, 0, 6 Pi}, Evaluated -> False] (untested). Might give three lines of a single colour. – Szabolcs May 10 '16 at 15:11
  • @Szabolcs wouldn't it recalculate aa? how will it choose sample rate? – BlacKow May 10 '16 at 15:14
  • @BlacKow First Sin[x] gets calculated, then the value re-used in both aa and aa+bb. Did I miss something? Sorry, I cannot test right now. – Szabolcs May 10 '16 at 15:19
  • 1
    @Szabolcs It works just like you said (all three lines in the same color, though). – Jens May 10 '16 at 15:28
  • 1
    "without recalculating them twice at each point." - due to the adaptive sampling, it is quite possible that Plot[] will evaluate the sum at points where neither of the components were evaluated, and vice versa. – J. M.'s missing motivation May 10 '16 at 18:27

2 Answers2

7

How about this:

f[x_] := Sin[x]
Plot[{#1, #2, #1 + #2} &[f[x], f[2 x]], {x, 0, 4}]

Strangely enough, this solution is slower that expected:

f[x_] := NIntegrate[Sin[1/y^2], {y, -x, x}] (*slow function*)

AbsoluteTiming[Plot[{f[x], f[2 x], f[x] + f[2 x]}, {x, 0, 1}]] (*naïve approach*)
AbsoluteTiming[Plot[{#1, #2, #1 + #2} &[f[x], f[2 x]], {x, 0, 1}]] (*my solution*)
AbsoluteTiming[Plot[With[{aa=f[x],bb=f[2x]},{aa,bb,aa+bb}],{x,0,1},Evaluated->False]] (*Szabolcs' comment*)

(*65.7*)
(*106.2*)
(*102.0*)

We do get a substantial improvement with memoization:

f[x_] := f[x] = NIntegrate[Sin[1/y^2], {y, -x, x}]
AbsoluteTiming[Plot[{f[x], f[2 x], f[x] + f[2 x]}, {x, 0, 1}]] (*naïve approach with memoization*)

(*40.5*)

Finally, the best approach so far is to follow BlacKow's suggestion and precalculate the function at discrete points:

f[x_] := NIntegrate[Sin[1/y^2], {y, -x, x}] (*two slow*)
g[x_] := NIntegrate[Cos[1/y^2], {y, -x, x}] (*functions*)

AbsoluteTiming[
  points = Range[0, 1, .01];
  F = f /@ points;
  G = g /@ points;
  ListPlot[{Transpose@{points, F}, Transpose@{points, G}, 
       Transpose@{points, F + G}}, Joined -> True]
]

(*3.4*)

though it can get tricky to choose the appropriate spacing.

1

A slight variation on AccidentalFourierTransform's version of BlacKow's suggestion.

f[x_] := NIntegrate[Sin[1/y^2], {y, -x, x}]

AbsoluteTiming[
  pts1 = Table[{x, f[x]}, {x, Subdivide[0., 1., 101]}]; 
  pts2 = Table[{x, f[2 x]}, {x, Subdivide[0., 1., 101]}]; 
  pts3 = MapThread[{#1[[1]], #1[[2]] + #2[[2]]} &, {pts1, pts2}]; 
  ListLinePlot[{pts1, pts2, pts3}]]

result

Since I am running on a six year old iMac, I don't think the slightly faster time is due to better processor speed.

m_goldberg
  • 107,779
  • 16
  • 103
  • 257
  • I get about 10% increase if calculate pts3 = Transpose@{pts1[[All, 1]], pts1[[All, 2]] + pts2[[All, 2]]}; – BlacKow May 10 '16 at 22:42