Julia und Project Jupyter¶

Samuel Belko; May 5, 2020¶

www.belko.xyz¶

Inhalt¶

  • Für wen ist Julia und Project Jupyter interessant?
  • Project Jupyter
    • allgemeine Informationen
    • Installierung
    • hilfreiche Quellen
  • Julia
    • allgemeine Informationen
    • Julia's Features
    • Julia REPL
    • Installierung
    • hilfreiche Quellen
    • ein nettes Beispiel
  • Quellen

Für wen ist Julia und Project Jupyter interessant?¶

  • beide sind sehr gut zusammen als auch separat für angewandte Mathe, Daten Verarbeitung & Modellierung geeignet
  • hauptsächlich für «Data science» und «Scientific computing» Communities
  • Jupyter Notebooks sind nützlich, wenn man z.B. Julia lernen möchte

Project Jupyter¶

  • umfasst mehrere open-source Projekte
  • Notebooks (.ipynb Dateien)
    • bestehen aus Zellen, die man ausführen kann
      • Zellen = kleine Containers für Code oder Gedanken (Text, Gleichungen)
      • nach jeder ausgeführen Zelle wird die Ausgabe der jeweiligen Zelle angezeigt
      • standardmäßig wird der Wert der letzten Zeile ausgegeben
      • man kann auch Bilder, Plots anzeigen lassen
    • Markdown (mit $\LaTeX$ Syntax) wird für die Text-Zellen benutzt
    • Syntaxhighlighting für die Code-Zellen
    • interaktive Elemente (widgets) lassen sich leicht einbauen
    • Unterstützung von Julia, Python, R und weiteren Programmiersprachen
    • praktisch fürs Experimentieren und zur Dokumentierung von einzelnen Schritten
  • JupyterLab (App)
    • webbasierte interaktive Umgebung für Notebooks, Code, und Dateien
      • Bilder, CSV Tabellen, pdf, .tex, .html, .json und viel mehr
    • zugang zur Kommandozeile, dient fast wie ein vollständinges IDE
    • bietet einen Präsentationmodus an
    • Notebooks lassen sich zu html, pdf, latex, markdown, slides,... leicht exportieren
      • auch diese Folien wurden mit JupyterLab erstellt
    • geeignet für angewandte Mathematik, Data Science etc.
      • Data Cleaning und Transformierung
      • exploratory data analysis (EDA)
      • numerische Simulierungen
      • statistische Modellierung
      • Visualisierung von Daten
      • Machine Learning

Installierung¶

  • erfolgt mit der Benutzung von entweder conda, pip, docker oder pipenv
    • Vorraussetzung: man kann die Kommandozeile eingermaßen benutzen
    • eventuell muss man Python und pip installieren
      • pip = package manager für Python, damit installiert man neue nicht Standard-Packages
  • siehe installation guide
  • damit man Julia in Notebooks benutzen kann muss man IJulia Package installieren, mehr dazu hier
  • probiere JupyterLab aus ohne irgendwas installieren zu müssen hier

Hilfreiche Quellen¶

  • JupyterLab - Getting started
  • Github - Markdown tutorial

Markdown intro:¶

Markdown = der einfachste Weg zur Textformatierung

# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6


**bold**

*italics*

Heading 1¶

Heading 2¶

Heading 3¶

Heading 4¶

Heading 5¶
Heading 6¶

bold

italics

- unordered list
- unordered list
    - unordered sublist
    - unordered sublist

---

> blackquote

1. ordered list
2. ordered list
  • unordered list
  • unordered list
    • unordered sublist
    • unordered sublist

blackquote

  1. ordered list
  2. ordered list
Here is some `inline code`.

A codeblock
```Julia
for i in 1:10
    println(i)
end
```

Here is a inline equation $x=5^{23}$ using $\LaTeX$.

Here is a url [my link](www.mylink.com).

Here is some inline code.

A codeblock

for i in 1:10
    println(i)
end

Here is a inline equation $x=5^{23}$ using $\LaTeX$.

Here is a url my link.

Julia¶

  • ist eine relativ junge Programmiersprache
    • Erscheinungsjahr: 2012
  • Free and open source (MIT licensed)
  • besitzt eine sehr nette Community
    • Jeff Bezanson, Alan Edelman, Stefan Karpinski, Viral B. Shah u.a.
  • kann sehr schnell sein (vergleichbar mit C, FORTRAN)
  • high level mit verständlicher Syntax
    • kurzer Code ist häufig einfacher zu lesen, zu maintainen und zu verbessern
    • extra (low-level) Codezeilen schaffen oft zusätzliche Fehlstellen
    • weniger Arbeitsaufwand
      • man braucht weniger Menschen um ein Julia Projekt zu entwickeln
        • dadurch sinkt der Kommunikations-Overhead und steigt die Effektivität
    • => Julia spart Ressourcen
  • ein großer Teil von Julia wurde in Julia selbst implementiert
    • sogar auch primitive Datentypen wie Int64, Float64 etc.
    • man brauch oft keine low-level Sprache zu können um die Implementierung zu verstehen
  • dynamische Typisierung
    • Die Datentypen von Expressions werden zuerst beim Laufzeit gebraucht
    • fühlt sich daher wie eine Skripting Sprache (interpreted lang.) an
      • interaktiv mit REPL
  • «Optionally typed»
    • man kann den Code verdeutlichen & bessere Performance erzielen
  • «General»
    • unterstützt verschiedene «Programming patterns» (object-oriented, functional)
  • geignet für Distributed Computing
  • es gibt zahlreiche Libraries für mathematische Problemstellungen
    • Statistik StatBase.jl
    • Optimierung Jump.j
    • Machine Learning Flux.jl, ScikitLearn.jl - scikit-learn (python) wrapper
    • ML & Differentiable programming Zygote.jl
    • Differenziale Gleichungen DifferentialEquations.jl
    • Plots.jl

Julia's Features¶

  • Indexing von Arrays startet bei 1

Julia's Type-System¶

  • nur Werte haben einen Datentyp, nicht Variablen
  • ein Wert hat nur den einen Datentyp, den er bei der Laufzeit hat
  • da alle Werte in Julia Objekte sind, haben auch Datentypen einen Datentyp
    • folglich kann man Funktionen auf diesen Datentypen definieren
  • primitive, composit, mutable composit types nennen wir concrete types
  • Datentypen lassen sich hierarchisch einordnen
    • dadurch beschreibt man das Verhalten von mehreren ähnlichen Strukturen auf einmal
      • z.B. square(x::Number) = x^2 funktioniert für Int64, Int32, Float64 etc.
    • es findet keine Vererbung von der Strukutur selbst statt
      • in mainstream OOP kombiniert man Code reuse & behavour (semantics)
    • ein «concrete Type» darf nur ein Subtype von einem «abstract Type» sein
  • Datentypen unterteilt anhand von ihren Datentyp
    • Declared Types (Objekte vom Typ DataType)
      • abstract types
        • helfen uns eine semantische Hierarchie aufzubauen
      • primitive types
        • plain old bits
      • composit types
        • Zusammenfassung von mehreren Variablen, «struct»
      • mutable composit types
        • «mutable struct»
    • UnionAll Type
      • der Typ von parametrisierten Typen
        • alle DataType Typen können generic sein
    • Type Unions
      • erstellt man mit Union Keyword z.B Union{Int, String}

multiple dispatch¶

  • im Gegensatz zu traditionellen object-oriented Sprachen, besitzt ein Datentyp keine Methoden sondern beim Aufruf einer Funktion wird die Methode gewählt, die am besten zu den Argumenten passt (alle Argumente werden betrachtet)
  • vereinfacht die Notation, ist stark verbunden mit dem Überladen von Funktionen
    • das Überladen von Funktionen ist in Mathe üblich (z.B. + bei Vektorräumen)
  • die wichtigste Design-Entscheidung von Julia
  • erlaubt gezielt Type-stable Funktionen aufzurufen
    • falls auch alle Funktionen die in einer Funktion aufgerufen werden Type-stable sind, kennt der Kompiler alle Datentypen der Variablen => sehr gute Performance
    • der Hauptgrund warum Julia so schnell sein kann

Macros¶

  • Die Struktur des Programms kann sich während der Laufzeit verändern
  • Metaprogrammierung

Julia REPL¶

  • «read-evaluate-print loop»
  • Julia Executable kommt mit «full-featured interactive command-line REPL»
  • verschiede Modi
    • The Julian mode
      • jeder Julia Befehl wird nach der Eingabe evaluiert und die Ausgabe wird angezeigt
    • Help Mode
      • starte mit ?
      • suche nach Dokumentation für gewisse Funktionalität
    • Shell Mode
      • starte mit ;
      • führe System-Befehle aus (Kommandozeile)
  • Dokumentation zu REPL
  • Syntax-Highlighting in REPL OhMyREPL

Installierung¶

  • einfach herunterladen und starten -> Julia Download
  • eventuell muss man eine symbolische Verknüpfung erstellen, falls man Julia (Julia REPL) mit julia als Befehl aufrufen möchte

Hilfreiche Quellen¶

  • Julia learning resources
  • Julia Dokumentation (sehr hilfreich)
  • golem.de - Julia Tutorial
  • Julialang - discourse (Forum)
  • Why does Julia work so well?
  • style guide
  • probiere Julia & Jupyter ntb. online aus
  • A Deep Introduction to Julia for Data Science and Scientific Computing
  • Juno - IDE für Julia
  • Julia verglichen mit anderen Prgrammiersprachen
    • Matlab, R, Python, C/C++, Common Lisp
  • Julia cheatsheet - Syntax
  • Quantitative Economics with Julia
  • eine interessante Anwendung von Multiple Dispatch

Ein nettes Beispiel - Polynomringe¶

In [1]:
# an immutable struct - a composit type
struct Poly{T}
    # Vector{T} is an alias for Array{T,1}, 
    # all elements have the same Type T, it is 1 dimensional
    coeff::Vector{T}
    # inner constructor - Julia creates a simple one by default
    function Poly(coeff::Vector{T}) where T 
        # remove zeros from the end of coeff        
        new{T}(removeTrailingZeros(coeff))
    end
end

# outer constructor - convience methods for object creation
# variable number of arguments
function Poly(coeff::T...) where T
    # coeff is a tuple, we need to convert it to Vector{T} i.e. Array{T,1}
    Poly(collect(coeff))
end
Out[1]:
Poly
In [2]:
# a generic function
function removeTrailingZeros(coeff::Vector{T}) where T
    if isempty(coeff)
        return Vector{T}()
    end
    
    i = 0
    while coeff[(end - i)] == zero(T)
        i += 1
        # the array is full of zeros
        if i == lastindex(coeff)
            return Vector{T}()
        end
    end
    # the last evalueated statement is returned
    coeff[1:end - i]
end
Out[2]:
removeTrailingZeros (generic function with 1 method)
In [3]:
# Polynomials of the same parametric type Int64 in global scope
# call inner constructor
p = Poly([1,2])
Out[3]:
Poly{Int64}([1, 2])
In [4]:
# call outer constructor
q = Poly(2,4,3)
r = Poly(0,1)
Out[4]:
Poly{Int64}([0, 1])
In [5]:
# show info about some object
dump(q)
Poly{Int64}
  coeff: Array{Int64}((3,)) [2, 4, 3]
In [6]:
# show macro, with `;` suppress the output of the cell
@show s = Poly(1,1,0,0,0);
s = Poly(1, 1, 0, 0, 0) = Poly{Int64}([1, 1])
In [7]:
# dot syntax to access members
s.coeff
Out[7]:
2-element Array{Int64,1}:
 1
 1
In [8]:
Poly(0,0,0,0)
Out[8]:
Poly{Int64}(Int64[])
In [9]:
# print the type of an object
typeof(s)
Out[9]:
Poly{Int64}
In [10]:
# make it behave as an array
# overloading - Multiple dispatch chooses the right method to use
import Base.getindex
# The syntax a[i,j,...] is converted by the compiler to getindex(a, i, j, ...)
function getindex(p::Poly{T}, i::Int) where T
    if i <= length(p)
        return p.coeff[i]
    else
        # custom zero based on the type T
        return zero(T)
    end
end
    
import Base.length
# overload length
function length(p::Poly{T}) where T
    length(p.coeff)
end
Out[10]:
length (generic function with 86 methods)
In [11]:
import Base.lastindex
# now we can use p[end]
function lastindex(p::Poly{T}) where T
    length(p)
end
    
import Base.iszero
# check if zero poly
function iszero(p::Poly{T}) where T
    isempty(p.coeff)
end
Out[11]:
iszero (generic function with 16 methods)
In [12]:
@show q
q[1], q[2], q[end], q[end-1], q[200], typeof(q), length(q), iszero(q)
q = Poly{Int64}([2, 4, 3])
Out[12]:
(2, 4, 3, 4, 0, Poly{Int64}, 3, false)
In [13]:
import Base.==
# we removed trailing zeros from coeff at init, OK
function ==(p::Poly{T}, q::Poly{T}) where T
    p.coeff == q.coeff
end
Out[13]:
== (generic function with 157 methods)
In [14]:
import Base.+
function +(p::Poly{T}, q::Poly{T}) where T
    # Arr. comprehension - create math. like arrays, we use the inner constructor and range 1:X (iterable)
    Poly([p[i] + q[i] for i in 1:max(length(p), length(q))])
end

import Base.-
# define inverse elements
function -(p::Poly{T}) where T
    Poly(- p.coeff)
end

function -(p::Poly{T}, q::Poly{T}) where T
    p + (-q)
end
Out[14]:
- (generic function with 176 methods)
In [15]:
# overload the zero and one function for polynomials
import Base.zero, Base.one
# additive identity element for the type of x, zero(x)
# define function with f(x) = ...
# var name left out, important is to know T, dispatch on type
zero(::Type{Poly{T}}) where T = Poly(Vector{T}())
one(::Type{Poly{T}}) where T = Poly([one(T)])
Out[15]:
one (generic function with 18 methods)
In [16]:
zero(Poly{Float64}), one(Poly{Float64}), one(Poly{Int64})
Out[16]:
(Poly{Float64}(Float64[]), Poly{Float64}([1.0]), Poly{Int64}([1]))
In [17]:
import Base.*
# mult. polynoms of the same type (!)
function *(p::Poly{T}, q::Poly{T}) where T
    # p or q is a zero polynomial => p*q = zero poly.
    if iszero(p) || iszero(q)
        return zero(Poly{T})
    else
        # Vector of type T with the right length
        newCoeff = Vector{T}(undef, length(p) + length(q) - 1)
            
        for i in 1:length(newCoeff)
            newCoeff[i] = sum([ p[j] * q[i-j + 1] for j in 1:i])
        end
        
        return Poly(newCoeff)
    end
end
Out[17]:
* (generic function with 358 methods)
In [18]:
# a small test of our implementation

# properties of a commutative group
# associative +
@show (p + q) + r == p + (q + r)
# defining property of the identity element wrt +
@show zero(Poly{Int64}) + p == p
# defining property of an inverse element wrt +
@show (-p) + p == zero(Poly{Int64})
# commutative +
@show p + q == q + p;
(p + q) + r == p + (q + r) = true
zero(Poly{Int64}) + p == p = true
-p + p == zero(Poly{Int64}) = true
p + q == q + p = true
In [19]:
# further properties of a commutative ring
# associative * 
@show (p*q)*r == p*(q*r)
# defining property of the neutral element wrt *
@show one(Poly{Int64}) * p == p * one(Poly{Int64}) == p
# distributive property
@show p*(q + r) == p * q + p * r
# commutative * 
@show p * q == q * p

# logical consequences
@show zero(Poly{Int64}) * p == zero(Poly{Int64})
@show (-p) * q == p * (-q) == -(p*q);
(p * q) * r == p * (q * r) = true
one(Poly{Int64}) * p == p * one(Poly{Int64}) == p = true
p * (q + r) == p * q + p * r = true
p * q == q * p = true
zero(Poly{Int64}) * p == zero(Poly{Int64}) = true
-p * q == p * -q == -(p * q) = true
In [20]:
# Yay a polynomial ring over "Z" (well not quite Z but ok..)
(p * q + r * s ) * (p - q * r) + zero(typeof(s)) - p * one(typeof(p))
Out[20]:
Poly{Int64}([1, 7, 4, -36, -75, -60, -18])
In [21]:
# pretty printing for Poly   
import Base.show
# When you print an object, Julia invokes the show function
function show(io::IO, p::Poly{T}) where T
    # get type with typeof(x)
    if iszero(p)
        print(io, "0" )
    elseif length(p) == 1
        print(io, "(", repr( p[1]), ")")
    else
        print(io, "(", repr( p[1]), ") +")
        for i in 2:(length(p) - 1)
            # use string interpolation as in Perl using $
            print(io, " (", repr(p[i]), ")", "x^$(i - 1) +" )
        end
        print(io, " (", repr(p[length(p)]), ")", "x^$(length(p) - 1)" )
    end
end
Out[21]:
show (generic function with 234 methods)
In [22]:
@show u = Poly(1,2,3);
u = Poly(1, 2, 3) = (1) + (2)x^1 + (3)x^2
In [23]:
q
Out[23]:
(2) + (4)x^1 + (3)x^2
In [24]:
# callable struct - functions are just callables
# evaluate at x
# with * between T and N we promote them to a common "greater" type
# we can evaluate an Int64 poly at Float64 value
function (p::Poly{T})(x::N) where {T, N}
    if iszero(p)
        return zero(x)
    else
        # a dot product of transposed coeff and x^i 
        # one coud also use LinearAlgebra.dot as ⋅ (\cdot<tab>)
        p.coeff' * [x^(i) for i in 0:(length(p)-1)]   
    end
end
In [25]:
@show u;
u = (1) + (2)x^1 + (3)x^2
In [26]:
# full support for unicode characters (\Gamma<tab> for Γ etc.)
# use ∘ (\circ<tab>) for function composition
u(0), u(1), u(0.5), p(q(s(π))), (p ∘ q ∘ s)(π)
Out[26]:
(1, 6, 2.75, 141.04947947833202, 141.04947947833202)
In [27]:
# multiple dispatch checks the function definition (signature),
# we could provide "promotion rules" to convert to common type
Poly(1,2) * Poly(1.1,1.2)
MethodError: no method matching *(::Poly{Int64}, ::Poly{Float64})
Closest candidates are:
  *(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:529
  *(::Poly{T}, !Matched::Poly{T}) where T at In[17]:5

Stacktrace:
 [1] top-level scope at In[27]:1
In [28]:
# how about a 2x2 matrix ring over a polynomial ring ???
A = [p s; p s]
B = [s p; r q]
# *, + are for Matrix type defined by default
X = A * B + B
Out[28]:
2×2 Array{Poly{Int64},2}:
 (2) + (5)x^1 + (3)x^2  (4) + (12)x^1 + (11)x^2 + (3)x^3
 (1) + (5)x^1 + (3)x^2  (5) + (14)x^1 + (14)x^2 + (3)x^3
In [29]:
# broadcasting
f(x) = x^2
f.([1,2,3])
Out[29]:
3-element Array{Int64,1}:
 1
 4
 9
In [30]:
# basic math functions are included
sin.([-π, 1/6 * π])
Out[30]:
2-element Array{Float64,1}:
 -1.2246467991473532e-16
  0.49999999999999994
In [31]:
# a callable polynomial u
# transpose with a sigle quote
u.([1,2,3,4])'
Out[31]:
1×4 LinearAlgebra.Adjoint{Int64,Array{Int64,1}}:
 6  17  34  57
In [32]:
# defined only for T subtype Number
function doubleCoeff(p::Poly{T}) where T <: Number
    Poly( 2 * p.coeff)
end
Out[32]:
doubleCoeff (generic function with 1 method)
In [33]:
doubleCoeff(Poly(1,2,3))
Out[33]:
(2) + (4)x^1 + (6)x^2
In [34]:
@which 2 * p.coeff
Out[34]:
*(A::Number, B::AbstractArray) in Base at arraymath.jl:52
In [35]:
X
Out[35]:
2×2 Array{Poly{Int64},2}:
 (2) + (5)x^1 + (3)x^2  (4) + (12)x^1 + (11)x^2 + (3)x^3
 (1) + (5)x^1 + (3)x^2  (5) + (14)x^1 + (14)x^2 + (3)x^3
In [36]:
# broadcast across more dimensions
doubleCoeff.(X)
Out[36]:
2×2 Array{Poly{Int64},2}:
 (4) + (10)x^1 + (6)x^2  (8) + (24)x^1 + (22)x^2 + (6)x^3
 (2) + (10)x^1 + (6)x^2  (10) + (28)x^1 + (28)x^2 + (6)x^3
In [37]:
# most operators are just functions with support for special syntax 
2 .* [1,2,3]
Out[37]:
3-element Array{Int64,1}:
 2
 4
 6
In [38]:
# How about a scalar multiplication??
# Can we generalize doubleCoeff ??
# use dot syntax to access memebers of a module
# define behaviour for Poly when broadcasted 
# -> behave as a scalar do not broatcast any further
Base.broadcastable(x::Poly{T}) where T = Ref(x)

# overload *
function *(s::T, p::Poly{T}) where T
    # beautiful syntax using .
    Poly(s .* p.coeff)
end
Out[38]:
* (generic function with 359 methods)
In [39]:
# and how about a polynomial ring over a polynomial ring ???
# p,q,r,s are Poly over "Z", now used as coeff.
τ = Poly(p,q,r,s)
μ = Poly(p, p, r)
ν = Poly(q,r)
ρ = τ * μ + ν - μ + one(typeof(ν))
Out[39]:
((3) + (6)x^1 + (7)x^2) + ((2) + (11)x^1 + (15)x^2 + (6)x^3)x^1 + ((2) + (9)x^1 + (15)x^2 + (6)x^3)x^2 + ((1) + (6)x^1 + (8)x^2 + (3)x^3)x^3 + ((1) + (3)x^1 + (3)x^2)x^4 + ((0) + (1)x^1 + (1)x^2)x^5
In [40]:
# scalar mulitplication
@show μ
@show μ == Poly(1) * μ
# "mult. by the scalar g(x)=x"  ≡ "mult. by Poly(0,1)"
@show Poly(0,1) * μ;
μ = ((1) + (2)x^1) + ((1) + (2)x^1)x^1 + ((0) + (1)x^1)x^2
μ == Poly(1) * μ = true
Poly(0, 1) * μ = ((0) + (1)x^1 + (2)x^2) + ((0) + (1)x^1 + (2)x^2)x^1 + ((0) + (0)x^1 + (1)x^2)x^2
In [41]:
@show p
@show p * μ;
p = (1) + (2)x^1
p * μ = ((1) + (4)x^1 + (4)x^2) + ((1) + (4)x^1 + (4)x^2)x^1 + ((0) + (1)x^1 + (2)x^2)x^2
In [42]:
# Can we call evaluate our polynomial over a polynomial at some polynomial ????

# overwrite / define new behavior for Poly{T}
# what is Poly{T}^n ??
# Integer is an abstract type that includes Int64, Int32 etc.
import Base.^
function ^(p::Poly{T}, n::Integer) where T
    # we may get an error here..
    un = Unsigned(n)
    # we assume at this point one(T) has an implentation
    res = Poly(one(T))
    for i in 1:n
        res *= p
    end
    res
end

import LinearAlgebra
LinearAlgebra.dot(p::Poly{N},q::Poly{N}) where N  = p * q
zero(p::Poly{N}) where N = zero(Poly{N})
Out[42]:
zero (generic function with 18 methods)
In [43]:
@show μ
@show q
@show μ(q)
@show μ(q) == μ[1] + μ[2] * q + μ[3] * q^2;
μ = ((1) + (2)x^1) + ((1) + (2)x^1)x^1 + ((0) + (1)x^1)x^2
q = (2) + (4)x^1 + (3)x^2
μ(q) = (3) + (14)x^1 + (27)x^2 + (34)x^3 + (24)x^4 + (9)x^5
μ(q) == μ[1] + μ[2] * q + μ[3] * q ^ 2 = true
In [ ]:
using Plots;
In [47]:
@show q
interval = -10:0.1:10
plot(interval, q.(interval), label="q")
q = (2) + (4)x^1 + (3)x^2
Out[47]:
-10 -5 0 5 10 0 100 200 300 q

Quellen¶

  • Jupyter
    • jupyter.org
    • Jupyter Documentation
  • Julia
    • julialang.org
    • juliacomputing.com
    • wikipedia - Matrix ring
    • wikipedia - Julia