Difficulties with parametric plots

In Sage I did this:

In Julia I tried doing the ‘same thing’:

Plots.GRBackend()

julia> r=25.1; i=0.2;
julia> f(a) = ( 1/2*(pi*a*i/r + 2*r)*cos(a), 1/2*(pi*a*i/r + 2*r)*sin(a) );
julia> plot( f, 0, 248*pi )`

And got this:`ERROR: MethodError: no method matching Float64(::Tuple{Float64, Float64})
Closest candidates are:
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number} at char.jl:50
  (::Type{T})(::Base.TwicePrecision) where T<:Number at twiceprecision.jl:243
  (::Type{T})(::Complex) where T<:Real at complex.jl:37
  ...
Stacktrace:
  [1] (::RecipesPipeline.var"#7#8"{Symbol})(x::Tuple{Float64, Float64})
    @ RecipesPipeline ~\.julia\packages\RecipesPipeline\CirY4\src\utils.jl:201
  [2] (::ComposedFunction{RecipesPipeline.var"#7#8"{Symbol}, typeof(f)})(x::Float64) (repeats 2 times)
    @ Base .\operators.jl:938
  [3] (::PlotUtils.var"#27#29"{ComposedFunction{ComposedFunction{RecipesPipeline.var"#7#8"{Symbol}, typeof(f)}, RecipesPipeline.var"#9#10"{Symbol}}})(x::Float64)
    @ PlotUtils ~\.julia\packages\PlotUtils\es5pb\src\adapted_grid.jl:46
  [4] _broadcast_getindex_evalf
    @ .\broadcast.jl:648 [inlined]
  [5] _broadcast_getindex
    @ .\broadcast.jl:621 [inlined]
  [6] getindex
    @ .\broadcast.jl:575 [inlined]
  [7] macro expansion
    @ .\broadcast.jl:984 [inlined]
  [8] macro expansion
    @ .\simdloop.jl:77 [inlined]
  [9] copyto!
    @ .\broadcast.jl:983 [inlined]
 [10] copyto!
    @ .\broadcast.jl:936 [inlined]
 [11] copy
    @ .\broadcast.jl:908 [inlined]
 [12] materialize
    @ .\broadcast.jl:883 [inlined]
 [13] adapted_grid(f::ComposedFunction{ComposedFunction{RecipesPipeline.var"#7#8"{Symbol}, typeof(f)}, RecipesPipeline.var"#9#10"{Symbol}}, minmax::Tuple{Float64, Float64}; max_recursions::Int64, max_curvature::Float64)
    @ PlotUtils ~\.julia\packages\PlotUtils\es5pb\src\adapted_grid.jl:57
 [14] adapted_grid(f::Function, minmax::Tuple{Float64, Float64})
    @ PlotUtils ~\.julia\packages\PlotUtils\es5pb\src\adapted_grid.jl:16
 [15] _scaled_adapted_grid(f::Function, xscale::Symbol, yscale::Symbol, xmin::Int64, xmax::Float64)
    @ RecipesPipeline ~\.julia\packages\RecipesPipeline\CirY4\src\user_recipe.jl:353
 [16] macro expansion
    @ ~\.julia\packages\RecipesPipeline\CirY4\src\user_recipe.jl:293 [inlined]
 [17] apply_recipe(plotattributes::AbstractDict{Symbol, Any}, f::Function, xmin::Number, xmax::Number)
    @ RecipesPipeline ~\.julia\packages\RecipesBase\92zOw\src\RecipesBase.jl:282
 [18] _process_userrecipes!(plt::Any, plotattributes::Any, args::Any)
    @ RecipesPipeline ~\.julia\packages\RecipesPipeline\CirY4\src\user_recipe.jl:36
 [19] recipe_pipeline!(plt::Any, plotattributes::Any, args::Any)
    @ RecipesPipeline ~\.julia\packages\RecipesPipeline\CirY4\src\RecipesPipeline.jl:70
 [20] _plot!(plt::Plots.Plot, plotattributes::Any, args::Any)
    @ Plots ~\.julia\packages\Plots\lzHOt\src\plot.jl:172
 [21] plot(::Any, ::Vararg{Any, N} where N; kw::Any)
    @ Plots ~\.julia\packages\Plots\lzHOt\src\plot.jl:58
 [22] plot(::Any, ::Any, ::Vararg{Any, N} where N)
    @ Plots ~\.julia\packages\Plots\lzHOt\src\plot.jl:52
 [23] top-level scope
    @ REPL[4]:1

I would do something like this:

julia> using Plots

julia> f(a) = ( (1/2)*(pi*a*i/r + 2r)cos(a), 1/2(pi*a*i/r + 2r)*sin(a) )
f (generic function with 1 method)

julia> x = 0:0.1:2*pi # range of x
0.0:0.1:6.2

julia> plot(f.(x)) # note the dot, which means compute f for all values in the x range

which produces:
image

But don’t take my suggestion as the final word, I do not plot parametric functions often.

I tried/got:


julia> f(a) = ( (1/2)*(pi*a*i/r + 2r)cos(a), 1/2(pi*a*i/r + 2r)*sin(a) )
f (generic function with 1 method)

julia> plot( f(x) )
ERROR: MethodError: no method matching +(::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, ::Float64)
For element-wise addition, use broadcasting with dot syntax: array .+ scalar
...

So then I tried:

julia> using Plots; gr();
julia> i=0.2;r=25.1; x=r:i:248*pi;

julia>  f(a) = ( (1/2)*(pi*a*i/r + 2r)cos(a), 1/2(pi*a*i/r + 2r)*sin(a) )
f (generic function with 1 method)

julia> plot( f, x )

It didn’t crash, but instead gave me:

While I’m at it, how do you post julia session clips without the * disappearing?

I’ve been using the preformatted text button from the editor, but it produces different (& weird) results compared to the way it display your REPL clip above.

I just copy/pasted, and put everything in triple back ticks, so the code looks nice.

What you missed from the previous example was a dot “.”, in:

plot(f.(x))

That dot means a broadcasting operation, that is, that you want to compute the value of f to all the elements of x:

julia> x = 1:2
1:2

julia> f(x) = x + 1
f (generic function with 1 method)

julia> f.(x) # look the dot!
2-element Vector{Int64}:
 2
 3


Take a look at PSA: make it easier to help you

Also, note that this thread has gotten pretty far from the original question. It might be worth opening a new thread (or threads) with your specific questions. People that know a lot about plotting may not scroll far enough to see you plotting questions for example.

1 Like

I’m as yet unfamiliar with “broadcasting”.

With the dot I got:

I don’t understand why it goes eliptical, but maybe that is something to do with broadcasting.

Time to take this subdiscussion into a new thread – as I’ve been admonished for not doing – despite that I was simply responding to a response.

Thanks for your help.

1 Like

I think you probably want a parenthesis there to clarify that multiplication by 1/2. in the process of copying I may have missed it. That will solve the shape of the curve.

The clearer way of plotting a parametric function I think is:

julia> using Plots
julia> i=0.2;r=25.1; 

julia> f(a) =  (1/2)*(pi*a*i/r + 2r)cos(a)
f (generic function with 2 methods)

julia> g(a) = (1/2)*(pi*a*i/r + 2r)*sin(a)
g (generic function with 1 method)

julia> plot(f,g,0,2π)

which produces:

Let’s ask if a moderator can split the plotting-related topics into a new thread so that the answers do not get lost.

Maybe @mbauman?

2 Likes

(Back from my 17 hours on the naughty step. That’s cruel thing to do to a new user.)

That looks good for a range of 0…2pi and 0…4pi; but by the time you get to 5 rotations (10pi) you begin to see something isn’t quite right:


And by the time you get to the 124 rotations I am looking for, you get this:

Thoughts?

julia> using Plots; gr();
julia> i=0.2; r=25.1;
julia> x(a) = 1/2*(pi*a*i/r + 2*r)*cos(a); y(a) = 1/2*(pi*a*i/r + 2*r)*sin(a);

julia> plot( x, y, 0, 2*pi )
julia> plot( x, y, 0, 4*pi )
julia> plot( x, y, 0, 10*pi )
julia> plot( x, y, 0, 248*pi )
julia> i=0.2;r=25.1; 

julia> f(a) =  (1/2)*(pi*a*i/r + 2r)cos(a)
f (generic function with 2 methods)

julia> g(a) = (1/2)*(pi*a*i/r + 2r)*sin(a)
g (generic function with 1 method)

julia> plot(f,g,0,2π)

Looks that those are due to the low sampling. I didn’t find anything in the documentation about this but funnily if you add another argument with the number of points, it looks better:

julia> plot(f,g,0,248π)

image

With 1000 points:

julia> plot(f,g,0,248π,1000)

image

And with 10 000:

julia> plot(f,g,0,248π,10_000)

image

It is, however annoying that I could not find this easily in the documentation, I was lucky of trying that exact combination of parameters and it worked (sometimes Plots.jl just do things very intuitively).

I was getting annoyed at the aspect ratio, so I added another argument:

julia> plot(f, g, 0, 248π, 10_000, aspect_ratio = 1)

Which makes it look better:

image

1 Like

Making all multiplications explicit cures the ‘drift’ in the iterations, and adding an aspect_ratio=1, makes it display round rather than oval:

julia> using Plots; gr();
julia> i=0.2;r=25.1;
julia> f(a) = ( 1/2*(pi*a*i/r + 2*r)*cos(a), 1/2*(pi*a*i/r + 2*r)*sin(a) )
f (generic function with 1 method)
julia> plot( f.(r:i:248*pi) )

image

julia> plot( f.(r:i:248*pi), aspect_ratio=1 )

image

Reading about broadcasting, it seems this form of the call to plot(), iterates the function over the range to produce a list of tuples(?), which then get passed to plot()?

I really wish the Plots documentation would define exactly what form(s) of inputs plot() can take, rather than giving bare list of some of them without further explanation.

For example: given the few examples that pass functions, where it states

Functions can typically be used in place of input data, and they will be mapped as needed. 2D and 3D parametric plots can also be created, and ranges can be given as vectors or min/max.

it seems reasonable that this should work:

plot( f, 25.1, 248*pi )
ERROR: MethodError: no method matching Float64(::Tuple{Float64, Float64})
Closest candidates are:
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number} at char.jl:50
  (::Type{T})(::Base.TwicePrecision) where T<:Number at twiceprecision.jl:243
  (::Type{T})(::Complex) where T<:Real at complex.jl:37
  ...

Or this:

plot( f, 25.1:0.2:248*pi )

But that produces:
image

Essentially, whilst I’ve have arrived (with your extensive help ) at a solution above that works; I have no confidence that I would have ever got there on my own, no matter how much I ready the docs or watched the talking heads videos.

I think that Plots is a clear example of too much magic. Maybe I should look at one of the back-ends that it wraps over; myabe that would be clearer. Now all I need to do is work out which of the half dozen I should favor. (and unlearn whatever I retained from Plots ;( )

@Buk, the parametric curve example provided in the help file is quite clear:

using Plots
tmin = 0
tmax = 4π
tvec = range(tmin, tmax, length = 100)
plot(sin.(tvec), cos.(tvec))

It seems to be a big ask to expect to have a nice plot result while doing “hundreds” of tours around a circle and sampling it very parsimoniously.

Supplying a range with step size fixes the problem:

...
plot( x, y, r:i:248*pi, aspect_ratio=1 )

Gives:

Of course documentation could be better (remember these things are developed by voluntary people and we can help improving that as well). But any package that has as much options as Plots will be hard to use if one wants to always adapt what we want to do to the most custom options implemented for that package.

What I would take from this experience is not that learning how to do this in Plots is very hard. Is that there is an extremely powerful syntax, the dot, or broadcasting operations, that allow you to evaluate any function on any domain range. This is not a syntax of Plots, it is a syntax of the general programming language. Given that possibility, it easy to just generate the x and y data given any function that one wants to plot, and just plot it using the most simple plots syntax:

i=0.2;r=25.1;
f(a) = ( 1/2*(pi*a*i/r + 2*r)*cos(a), 1/2*(pi*a*i/r + 2*r)*sin(a) )
x = r:i:248*pi
y = f.(x)

up to now nothing had anything to do with Plots, explicitly. Here is where Plots come in:

using Plots
plot(y)

The other two options that are more common that aspect_ratio are the size and the limits of the plot. If you set:

plot!(xlim=[-50,50],ylim=[-50,50],size=[500,500])

you get your properly shaped plot:
image

The – first of three – parametric examples is clear; iff you are familiar with the syntax&semantics of the range() (function/operator/constructor?); but that example also uses the .() syntax which is new to me, hence I moved onto the next two examples. They pass the functions themselves rather than what they generate; which is what I am more familiar with.

The latter of which plot(sin, cos, tmin, tmax) produces a perfectly nice curve despite not explicitly giving the step size of number of subdivisions. I can see (now) that whatever the implicit number of steps is, when divided into 0…2pi, it will work better than the same default value divided into 25.1…248pi.

Talking of defaults; I do wish the default aspect ratio was 1; but I guess that’s only useful to someone only plotting circles. :slight_smile:

1 Like

Maybe I’ll come to see it that way once I get more familiar with Julia.

What I can say is that recently, I’ve also been experimenting with another ‘math environment’* that I am equally as unfamiliar with – more so, I’d never heard of it until 2 weeks ago, whereas I’ve been dipping my toes( and running away shivering) in Julia for a couple of years on and off.

And when I went to plot this same function, I read their single page help on plotting** and had a working plot after 3 or 4 attempts.

And I put that down to the simplicity of their 5 separate plot functions: plot(), parametric_plot(), plot3d(), parametric_plot3d(). implicit_plot3d(); makes the documentation problem (they’re also volunteers) much simpler and clearer.

From my yet lowly perspective, this seems to be a case where (over) use of multi-dispatch creates more problems than it solves.

My eventual goal is to write a parameterised script/program that produces a 3D plot that contains several 2D plots on different unaligned planes that effectively layout and define/dimension a 3D complex solid (or surface) that is also shown. Right now, that seems a long way off in Julia.

And it may be too ambitious, but I found that unless I have something real and substantial to achieve by learning another language, I never make progress. Step-by-step tutorials and monkey-see, monkey-do sessions have never worked for me – I get bored, and retain nothing – but I seem to be in the minority in that.

*(I realise that Julia is waaay more than that. That’s partly why I’m trying to get to grips with it.)

(**Again; not advocacy, just compare and contrast. That other environment has its (many) own qwerks and frustrations :slight_smile: )

That is indeed very ambitious :slight_smile: . Perhaps for that complicated layout Makie is the best option? Their showcase is quite impressive: http://juliaplots.org/MakieReferenceImages/gallery/index.html

(I am certain that no programming language is by itself the easiest way to do plots, particularly the simpler ones, probably that one is Origin. But, as you said, we learn programming languages for other reasons, and plotting comes along as a bonus).

Thank you for that.

This contains/demonstrates most of the functionality I require. (I think.)

I even understand most of the syntax now. The list comprehensions will give me pause for thought. I’ve had trouble with those in the past; and each language (Haskell, scad, python, erlang, OCaml, Miranda Q, D etc.) seems to have its own slightly different syntax for them.

1 Like