3
$\begingroup$

Related problems have been asked and answered many times, but I don't see a solution for my problem.

I have defined a function f which returns a vector of values when supplied with a numeric argument. I would like to test my implementation and my first step is to plot it. This produces a set of curves, all the same colour. If I supply a PlotLegends argument, only one curve is labelled.

Obviously, it would be helpful to identify the different components of the vector.

As a toy example

f[x_?NumericQ] := {Cos[x], Sin[x]} Plot[f[x], {x, 0, 2 Pi}, PlotLegends -> {1, 2}] 

enter image description here

There are some obvious work arounds, but they all involve extra work

  • Redefine my function f, but I'd rather test the function I've implemented
  • Make a helper function for each component of the vector, but this would increase computation
  • Build a table of values, and use ListLinePlot

I'm really looking for a way of telling Mathematica that this function returns a vector of known size, but I can't see how to do it.

$\endgroup$
11
  • $\begingroup$Maybe f01[x_?NumericQ] := {Cos[x], Sin[x]}; vectorLength = 2; Plot[Evaluate @ Table[Indexed[f01[x], i], {i, vectorLength}], {x, 0, 2 Pi}, PlotLegends -> {1, 2}] ?. I'm not sure. I don't fullly understand Indexed[..]$\endgroup$
    – andre314
    Commentedyesterday
  • $\begingroup$@andre314 Thanks for the suggestion, it gives the curves requested. I don't fully understand how it's working, but I see that the Sin and Cos curves have different numbers of points, so I think it is evaluating the function independently to compute each curve. It would be nice to avoid this.$\endgroup$
    – mikado
    Commentedyesterday
  • $\begingroup$Plot doesn’t evaluate your function until later—so it doesn’t know it is a list and that it should plot multiple curves. Andre314’s ‘Evaluate’ lets plot know to expect curves. In other words, Plot ‘Hold’s your first argument unevaluated.$\endgroup$Commentedyesterday
  • $\begingroup$@CraigCarter exactly the problem. Of course, Evaluate would do nothing here. So how do I get round it?$\endgroup$
    – mikado
    Commentedyesterday
  • 1
    $\begingroup$I've tried a few things. No luck. Using TagSet (f /: Length[f] = 2 , f /: ListQ[f] = True )--that didn't work. FunctionDeclaration: decl = FunctionDeclaration[sincos, Typed["Real32" -> {"Real32", "Real32"}]@Function[x, {Sin[x], Cos[x]}]] and FunctionCompile[decl, Function[Typed[arg, "Real32"], sincos[arg]]] and I couldn't get that to work either.$\endgroup$Commentedyesterday

4 Answers 4

3
$\begingroup$

Simplest

I don't know why Plot[] can parse the array output of a function into individual lines for each scalar component but not style them independently. The best I can come up with is to reprocess the lines:

Plot[f[x], {x, 0, 2 Pi}] // ListLinePlot[ Cases[#, Line[p_] :> p, Infinity], PlotLegends -> {1, 2}, Options@# ] & 

enter image description here

More robust

If you're going to add Exclusions, then the graph of a function will consist of separate lines, and the above will give each piece its own color. It's a bit more complicated to unify the components. The solution below relies on the current structure of the output of exclusion lines and the individual pieces of the graph of a function. There's no guarantee that WRI won't change this in the future (and the structure has changed in the past).

myLine // ClearAll; asymptote // ClearAll; myLine[p_] /; With[{width = MinMax[p[[All, 1]]]}, Abs[Subtract @@ width] < 0.0001 Max[Abs[width] + Sqrt@$MinMachineNumber]] := asymptote[p]; myLine /: HoldPattern@{a___, myLine[p_], myLine[q_], b___} /; p[[-1, 1]] < q[[1, 1]] := {a, myLine[Join[p, {{(p[[-1, 1]] + q[[1, 1]])/2, Indeterminate}}, q]], b}; Plot[{Tan[x], f[x]}, {x, 0, 2 Pi}, ExclusionsStyle -> {Dashed, Gray}] // With[{plt = Normal@# /. Line -> myLine}, Show[ Graphics[{ Cases[plt, {___, _asymptote, ___}, Infinity] /. asymptote -> Line Cases[plt, {___, _Point, ___}, Infinity] }], ListLinePlot[Cases[plt, HoldPattern[myLine[p_]] :> p, Infinity], PlotLegends -> {1, 2, 3}], Options@# ] ] & 

enter image description here

This looks identical to the output of the following:

Plot[{Tan[x], {Cos[x], Sin[x]}}, {x, 0, 2 Pi}, ExclusionsStyle -> {Dashed, Gray}, PlotLegends -> {1, 2, 3}] 

One internal difference is the coordinate plot highlighting is missing in for the golden #2 plot in the straight Plot[] code immediately above, but it is added by ListLinePlot[] in the fix.

If you want more, complicated features like filling and mesh and so forth, you'll have to figure out how to dive in and adjust them. It's too bad Plot[] doesn't do this for you.

$\endgroup$
    2
    $\begingroup$

    This might not be satisfying, because it sounds like you want to be able to make Plot just work with stuff you've already defined. It's frustrating when we paint ourselves into a corner like that, but I think this can lead to better design in the long run, because it can encourage you to separate your concerns. For example, you could do something along these lines (I know the f you provided is a toy example, but I can only work with what you've provided, so more info might be needed to adapt this to your "real" context):

    fPure = {Cos, Sin}; fImpl[arg_?NumericQ] := Comap[fPure, arg]; 

    So, fImpl is the function you use, but now we have the abstraction which we can plot with:

    Plot[Evaluate[Comap[fPure, x]], {x, 0, 2 Pi}, PlotLegends -> {1, 2}] 

    enter image description here

    Since you've already hard-coded knowledge of the number of functions to plot (because your PlotLegends assumes exactly two), we can also do something a bit more transparent:

    Plot[{fPure[[1]][x], fPure[[2]][x]}, {x, 0, 2 Pi}, PlotLegends -> {1, 2}] 

    Of course, this probably seems "backward" from your perspective, but again I'm only going off of what you've provided, and so I can't suggest anything more organic for your actual context.

    $\endgroup$
      1
      $\begingroup$

      If your objective is to just get a nice plot and to minimize the number of numerical evaluations and use the function you have constructed. Here is a hacky workaround:

      Store points evaluated by plot as downvalues (in fhold):

      ClearAll[f]; f[x_?NumericQ] := fhold[x] = {Sin[x], Cos[x]} Plot[f[x], {x, 0, 2 Pi}]; 

      Extract the points that plot used.

      tmp = Select[ReleaseHold /@ (DownValues[fhold] /. fhold -> Identity), NumericQ[First[#]] &] xvals = First /@ tmp; yvals = Last /@ tmp; plotPoints = {Transpose[{xvals, First /@ yvals}], Transpose[{xvals, Last /@ yvals}]} 

      Then plot the lists:

      ListLinePlot[plotPoints, PlotLegends -> {1, 2}] 

      This doesn't answer your question of how to get plot to recognize that f is a list of length 2. But, this could be rolled up into a function and you wouldn't see the difference.

      $\endgroup$
        0
        $\begingroup$

        You might do it like this:

        f[x_] := {Cos[x], Sin[x]} (Plot[#, {x, 0, 2 Pi}, PlotLegends -> #] & /@ {f[x]})[[1]] 

        with the following effect:

        enter image description here

        Otherwise, like this:

        f[x_] := {Cos[x], Sin[x]} First[Plot[#, {x, 0, 2 Pi}, PlotLegends -> (# /. {Cos[_] -> "1", Sin[_] -> "2"})] & /@ {f[x]}] 

        yielding the following:

        enter image description here

        Have fun!

        $\endgroup$
        2
        • 3
          $\begingroup$@Alexi_Boulbitch, I think the OP only wants f to be defined for NumericQ.$\endgroup$Commentedyesterday
        • $\begingroup$@Craig Carter This works equally with the definition f[x_?NumericQ] := {Cos[x], Sin[x]} . I just thought that it may be an unnecessary complication.$\endgroup$Commented11 hours ago

        Start asking to get answers

        Find the answer to your question by asking.

        Ask question

        Explore related questions

        See similar questions with these tags.