Day_01 - Julia in the Jupyter notebook#

Getting Conda and Julia to meet#

I reinstalled Julia via

pacman -Syu julia,

then created a conda environment to build a clean Jupyter notebook.

  1. conda create -n julia

  2. conda activate julia

  3. conda install jupyterlab

Then, I had to rebuild the IJulia package:

]build("IJulia")

Now, I’m up and running in the Jupyter notebook. Pretty cool so far.

Playing with some vector math and notation#

x = [1, 2, 3];

It looks like Julia suppresses output with the ; like Matlab.

print(transpose(x));
[1 2 3]

This was an interesting choice here. The default shape for a vector is 1D, e.g. (N, ), but you need to do a tranpose to multiply vectors either dot product or outer product

print(x*transpose(x))
print('\n',transpose(x)*x)
[1 2 3; 2 4 6; 3 6 9]
14

The result is that the 1D vector has an implicit second dimension. This is like Matlab. A (3, ) vectors is really treated as a (3, 1) column vector.

There are some cool Linear Algebra things I might be able to build with this kind of framework.

print("size(x) = ", size(x))
print("\nsize(transpose(x)) = ", size(transpose(x)))
size(x) = (
3,)
size(transpose(x)) = (1, 3)

Plotting first time to plot#

Man, I really thought I messed something up with my first plot. It was taking forever. Then, I tried it in the REPL and same thing. Then, I found the first time to plot (issue?). It seems that getting the Julia environment ready to display data takes some computational work.

using Plots
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80] (cache misses: wrong dep version loaded (2))
Failed to precompile Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80] to "/home/ryan/.julia/compiled/v1.11/Plots/jl_olBouX".

Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:35
  [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool; flags::Cmd, cacheflags::Base.CacheFlags, reasons::Dict{String, Int64}, isext::Bool)
    @ Base ./loading.jl:3085
  [3] (::Base.var"#1082#1083"{Base.PkgId})()
    @ Base ./loading.jl:2492
  [4] mkpidlock(f::Base.var"#1082#1083"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
    @ FileWatching.Pidfile ~/projects/julia/usr/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:95
  [5] #mkpidlock#6
    @ ~/projects/julia/usr/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:90 [inlined]
  [6] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
    @ FileWatching.Pidfile ~/projects/julia/usr/share/julia/stdlib/v1.11/FileWatching/src/pidfile.jl:116
  [7] #invokelatest#2
    @ ./essentials.jl:1057 [inlined]
  [8] invokelatest
    @ ./essentials.jl:1052 [inlined]
  [9] maybe_cachefile_lock(f::Base.var"#1082#1083"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
    @ Base ./loading.jl:3609
 [10] maybe_cachefile_lock
    @ ./loading.jl:3606 [inlined]
 [11] _require(pkg::Base.PkgId, env::String)
    @ Base ./loading.jl:2488
 [12] __require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base ./loading.jl:2315
 [13] #invoke_in_world#3
    @ ./essentials.jl:1089 [inlined]
 [14] invoke_in_world
    @ ./essentials.jl:1086 [inlined]
 [15] _require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base ./loading.jl:2302
 [16] macro expansion
    @ ./loading.jl:2241 [inlined]
 [17] macro expansion
    @ ./lock.jl:273 [inlined]
 [18] __require(into::Module, mod::Symbol)
    @ Base ./loading.jl:2198
 [19] #invoke_in_world#3
    @ ./essentials.jl:1089 [inlined]
 [20] invoke_in_world
    @ ./essentials.jl:1086 [inlined]
 [21] require(into::Module, mod::Symbol)
    @ Base ./loading.jl:2191
x = range(-5, 5, length = 50)
y = x.^2
plot(x, y)

In Matplotlib, the more plots you add, the more lines you have. Here it looks like the lines get overwritten more like Matlab. Ah, but adding a plot! adds the lines to the current plot.

plot(x, x.^3)
plot!(x, x.^2)
plot!(x, 1/2*x)

Animated plots + help with functions#

I found this awesome animated gif of a sine+cosine moving camera+tracking line. There were a bunch of plot calls, but one function stood out, @gif. I hadn’t seen a MATLAB/Python equivalent so I ran the code to get the gif. Success.

default(legend = false)
x = y = range(-5, 5, length = 40)
zs = zeros(0, 40)
n = 100

@gif for i in range(0, stop = 2π, length = n)
    f(x, y) = sin(x + 10sin(i)) + cos(y)

    # create a plot with 3 subplots and a custom layout
    l = @layout [a{0.7w} b; c{0.2h}]
    p = plot(x, y, f, st = [:surface, :contourf], layout = l)

    # induce a slight oscillating camera angle sweep, in degrees (azimuth, altitude)
    plot!(p[1], camera = (10 * (1 + cos(i)), 40))

    # add a tracking line
    fixed_x = zeros(40)
    z = map(f, fixed_x, y)
    plot!(p[1], fixed_x, y, z, line = (:black, 5, 0.2))
    vline!(p[2], [0], line = (:black, 5))

    # add to and show the tracked values over time
    global zs = vcat(zs, z')
    plot!(p[3], zs, alpha = 0.2, palette = cgrad(:blues).colors)
end
[ Info: Saved animation to /home/ryan/Documents/Career_docs/Julia-learning/tmp.gif

A couple things stood out in the example I found.

  1. Multiplication was implied, e.g. 2pi and 10sin. I don’t know if can bring myself to write code this way, but it would help avoid a lot of first-time programmer errors.

  2. The linspace equivalent is just range(start, stop, length = <>). This is nice because it can be taxing to jump back-and-forth between range and linspace.

  3. Anonymous functions are so straight-forward: f(x, y) =... creates a 2-input function. It feels so natural and lovely.

  4. The @gif function appears to be do some sort of magic. There’s an iterator i that goes from $0-2\pi$ and I’m guessing it grabs whatever plots are defined inside the loop and squishing them into one glorious gif.

Time to check the docs. This took me a couple tries, but I found the Accessing documentation on docs.julialang.org.

?@gif

Builds an Animation using one frame per loop iteration, then create an animated GIF.

Example:

  p = plot(1)
  @gif for x=0:0.1:5
    push!(p, 1, sin(x))
  end

This macro supports additional parameters, that may be added after the main loop body.

  • Add fps=n with positive Integer n, to specify the desired frames per second.

  • Add every n with positive Integer n, to take only one frame every nth iteration.

  • Add when <cond> where <cond> is an Expression resulting in a Boolean, to take a frame only when <cond> returns true. Is incompatible with every.

Cool! Check out the example given

p = plot(1)
@gif for x=0:0.1:4pi
push!(p, 1, sin(x))
end
[ Info: Saved animation to /home/ryan/Documents/Career_docs/Julia-learning/tmp.gif
?push!
search: push! put! pushfirst!
push!(collection, items...) -> collection

Insert one or more items in collection. If collection is an ordered container, the items are inserted at the end (in the given order).

Examples

julia> push!([1, 2, 3], 4, 5, 6)
6-element Vector{Int64}:
 1
 2
 3
 4
 5
 6

If collection is ordered, use append! to add all the elements of another collection to it. The result of the preceding example is equivalent to append!([1, 2, 3], [4, 5, 6]). For AbstractSet objects, union! can be used instead.

See sizehint! for notes about the performance model.

See also pushfirst!.


push!(q::Deque{T}, x)

Add an element to the back


push!(s::IntDisjointSets{T})

Make a new subset with an automatically chosen new element x. Returns the new element. Throw an ArgumentError if the capacity of the set would be exceeded.


push!(s::DisjointSets{T}, x::T)

Make a new subset with an automatically chosen new element x. Returns the new element.


push!(h::BinaryHeap, value)

Adds the value element to the heap h.


push!(sc, k=>v)

Argument sc is a SortedDict or SortedMultiDict and k=>v is a key-value pair. This inserts the key-value pair into the container. If the key is already present, this overwrites the old value. The return value is sc. Time: O(c log n)


push!(sc, k=>v)

Argument sc is a SortedDict or SortedMultiDict and k=>v is a key-value pair. This inserts the key-value pair into the container. If the key is already present, this overwrites the old value. The return value is sc. Time: O(c log n)


push!(sc, k)

Argument sc is a SortedSet and k is a key. This inserts the key into the container. If the key is already present, this overwrites the old value. (This is not necessarily a no-op; see below for remarks about the customizing the sort order.) The return value is sc. Time: O(c log n)


push!(cb::CircularBuffer, data)

Add an element to the back and overwrite front if full.


push!(tree, key)

Inserts key in the tree if it is not present.

Wrapping up#

Great second day of Julia work. I’m interested to dive into the objects and functions plot and push. They are so useful, but I don’t really understand how they speak to each other right now. That @gif function would be so useful in MATLAB/Matplotlib. Its such an intuitive way to create an animation that is easy to share and display.