-
Notifications
You must be signed in to change notification settings - Fork 25
/
mainj.tex
3217 lines (2587 loc) · 127 KB
/
mainj.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{Wholesale copy of SIAM REVIEW PAPER}
\section{Scientific computing languages: The Julia innovation}
The original numerical computing language was Fortran, short for
``Formula Translating System'', released in 1957. Since those
early days, scientists have dreamed of writing high-level, generic
formulas and having them translated automatically into low-level,
efficient machine code, tailored to the particular data types they
need to apply the formulas to. Fortran made historic strides towards
realization of this dream, and its dominance in so many areas of
high-performance computing is a testament to its remarkable success.
The landscape of computing has changed dramatically over the years.
Modern scientific
computing environments such as MATLAB~\cite{matlab}, R~\cite{Rlang},
Mathematica~\cite{mathematica}, Octave~\cite{Octave},
Python (with NumPy)~\cite{numpy}, and SciLab~\cite{scilab} have grown in popularity
and fall under the general category
known as { {\it dynamic languages} or {\it dynamically typed languages}.
In these programming
languages, programmers write simple, high-level code without any
mention of types like \code{int}, \code{float} or \code{double} that
pervade {\it statically typed languages} such as C and Fortran.
Julia is dynamically typed, but it is different as it approaches statically typed performance.
New users can begin working with Julia as they did
in the traditional numerical computing languages, and work their way up when ready.
In Julia, types are implied by the
computation itself together with input values. As a result, Julia
programs are often completely generic and compute with data of
different input types without modification---a feature known as
``polymorphism."
% Much of the development of modern programming languages---both static
% and dynamic---can be understood by following the emergence of various
% forms of polymorphism.
% In addition to the increased leverage afforded by
% polymorphism, the The convenience of not needing to mention types or
% satisfy the requirements of a type checker gives dynamic systems a
% major advantage in productivity.
It is a fact that compilers need type information to emit efficient
code for high performance. It is also true that the convenience of not
typing gives dynamic systems a major advantage in
productivity. Accordingly, many researchers today do their day-to-day
work in dynamic languages. Still, C and Fortran remain the gold
standard for computationally-intensive problems because high-level
dynamic languages have lacked performance.
An unfortunate outcome of the currently popular languages is that the
most challenging areas of numerical computing have benefited the least
from the increased abstraction and productivity offered by higher
level languages. A pervasive idea that Julia wishes to
shatter is that numerical computing languages are ``prototyping"
languages and one must switch to C or Fortran for performance. The
consequences to scientific computing have been more serious than many
realize.
We recommend signing up at \url{juliabox.org} to use Julia
conveniently through the Julia prompt or the IJulia notebook. (We
like to say ``download,next,next,next" is three clicks too many!) If
you wish to, you can download Julia at \url{www.julialang.org}, along
with the IJulia notebook.
We wish to show how users can be coaxed into writing better software
that is comfortably familiar, easier to work with, easier to maintain,
and can perform very well. This paper brings together
\begin{itemize}
\item Examples to orient the new user and
\item The theory and principles that underly Julia's innovative design
\end{itemize}
specifically so that everyday users of numerical computing languages
may understand why Julia represents a truly fresh approach.
\subsection{Computing Research Transcends Communities}
Numerical computing research has always lived on the boundary of computer science, engineering, mathematics, and computational sciences.
Readers might enjoy trying to label the ``Top 10 algorithms"\cite{top10} by field, and may quickly
see that advances typically transcend any one field with broader impacts to science
and technology as a whole.
Computing is more than using an overgrown calculator. It is a cross cutting communication medium.
Research into programming languages therefore breaks us out of our research boundaries.
The first decade of the 21st century saw a boost in such
research with the High Productivity Computing Systems DARPA funded
projects into the languages Chapel \cite{chapelref}, Fortress \cite{fortressref}
} and X10 \cite{x10}.
Also contemporaneous has been a growing acceptance of Python.
Up to around 2009 some of us were working on the Star-P interactive high performance computing system
for parallelizing primarily MATLAB but also Python, R, Excel and other high level languages
were researched.
(User Guide: \cite{starpug}, Getting Started: \cite{starpstart}, Papers \cite{starpright,starpstudy,Husbands98inter,Choy04star-p:high}).
Julia continues our research into parallel computing.
More recently Google, Apple, and Mozilla have introduced
Go \cite{go}, Swift \cite{swift}, and Rust \cite{rust} respectively as new interesting dynamic languages
which are gaining traction.
\section{A taste of Julia}
\subsection{A brief tour}
Since Julia is a young language, new users may worry that it is somehow
immature or lacking some functionality. Our experience shows that
Julia is rapidly heading towards maturity. Most new users are
surprised to learn that Julia has much of the functionality of the
traditional established languages, and a great deal of functionality
not found in the older languages.
New users also want a quick explanation as to why Julia is fast, and
whether somehow the same ``magic dust" could also be sprinkled on
their traditional scientific computing language. Why Julia is fast is
a combination of many technologies some of which are introduced in
Sections \ref{sec:select} through \ref{sec:lang} in this paper. Julia is fast because we, the
designers, developed it that way for us, the users. Performance is
fragile, like accuracy, one arithmetic error can ruin an entire
otherwise correct computation. We do not believe that a language can
be designed for the human, and easily retrofitted for the computer. Rather a
language must be designed from the start for the human and the
computer. Section \ref{sec:humancomputer} and \cite{hoareessay} explores this issue.
Before we get into the details of Julia, we provide a brief tour for
the reader to get a beginners' feel for the language. Users of other dynamic
numerical computing environments may find some of the syntax
familiar. In language design, there is always a trade-off between
making users feel comfortable in a new language vs.\ clean language
design. In Julia, we maintain superficial similarity to existing
environments to ease new users, but not at the cost of sacrificing
good language design or performance.
%For example, this familiar syntax shows how a random matrix is
%created, an identity matrix is added to it, and its inverse is
%computed:
\begin{jinput}
A = rand(3,3) + eye(3) \sh {\# Familiar Syntax} \\
inv(A)
\end{jinput}
\begin{joutput}
3x3 Array\{Float64,2\}:\\
\hspace*{.02em} 0.698106 -0.393074 -0.0480912 \\
-0.223584 \hspace*{.02em} 0.819635 -0.124946 \\
-0.344861 \hspace*{.02em} 0.134927 \hspace*{.02em} 0.601952
\end{joutput}
The output from the Julia prompt says that $A$ is a two dimensional
matrix of size $3 \times 3$, and contains double precision floating
point numbers.
Indexing of arrays is performed with brackets, and
is 1-based. It is also possible to compute an entire array
expression and then index into it, without assigning the expression to
a variable:
\begin{jinput}
x = A[1,2] \\
y = (A+2I)[3,3] \sh{ \# The [3,3] entry of A+2I}
\end{jinput}
\begin{joutput}
2.601952
\end{joutput}
In Julia, \verb+I+ is a built-in representation of the identity
matrix, which can be scaled and combined with other matrix operations,
without explicitly forming the identity matrix as is commonly done
using commands such as ``eye" which unnecessarily requires $O(n^2)$
storage and unnecessary computation in traditional languages and in
the end is a cute but ultimately wrong word for the identity
matrix. The built-in representation is fully implemented in Julia code
in the Julia base library.
Julia has symmetric tridiagonal matrices as a special type. For
example, we may define Gil Strang's favorite matrix (the second order
difference matrix) in a way that uses only $O(n)$ memory.
\begin{figure}[h]
\centering
\includegraphics[width=3in]{cupcakes1.jpg}
\caption{Gil Strang's favorite matrix is {\tt strang(n) =
SymTridiagonal(2*ones(n),-ones(n-1)) } \newline Julia only stores
the diagonal and off-diagonal. (Picture taken in Gil Strang's
classroom.) }
\end{figure}
\ja
strang(n) = SymTridiagonal(2*ones(n),-ones(n-1)) \\
strang(7)
\jb
7x7 SymTridiagonal\{Float64\}: \vspace{-.05in}
\begin{verbatim}
2.0 -1.0 0.0 0.0 0.0 0.0 0.0
-1.0 2.0 -1.0 0.0 0.0 0.0 0.0
0.0 -1.0 2.0 -1.0 0.0 0.0 0.0
0.0 0.0 -1.0 2.0 -1.0 0.0 0.0
0.0 0.0 0.0 -1.0 2.0 -1.0 0.0
0.0 0.0 0.0 0.0 -1.0 2.0 -1.0
0.0 0.0 0.0 0.0 0.0 -1.0 2.0
\end{verbatim}
\jc
Julia calls an efficient $O(n)$ tridiagonal solver:
\ja
strang(8)\textbackslash ones(8)
\jb
8-element Array\{Float64,1\}: \vspace{-.1in}
\begin{verbatim}
4.0
7.0
9.0
10.0
10.0
9.0
7.0
4.0
\end{verbatim}
\jc
Consider the sorting of complex numbers. Sometimes it is handy
to have a sort that generalizes the real sort. This can be done by
sorting first by the real part, and where there are ties, sort by the imaginary
part. Other times it is handy to use the polar representation, which sorts
by radius than angle.
If a numerical computing language ``hard-wires" its sort to be one or the other,
it misses a wonderful opportunity. A sorting algorithm need not depend on details
of what is being compared or how it is being compared. One can abstract away these
details thereby reusing a sorting algorithm for many different situations. One can specialize
later. Thus alphabetizing strings, sorting
real numbers, or sorting complex numbers in two or more ways all run with the same code.
In Julia, one can turn a complex number \verb+w+ into an ordered pair
of real numbers (a tuple of length 2) such as the Cartesian form \verb+(real(w),imag(w))+
or the polar form \verb+(abs(w),angle(w))+. Tuples are then compared lexicographically
in Julia. The sort command takes an optional ``less-than" operator, \verb+lt+, which is used to compare
elements when sorting.
\ja
\sh{\# Cartesian comparison sort of complex numbers} \\
complex\_compare1(w,z) = (real(w),imag(w)) < (real(z),imag(z)) \\
sort([-2,2,-1,im,1], lt = complex\_compare1 )
\jb
5-element Array\{Complex\{Int64\},1\}: \\
-2+0im \\
-1+0im \\
\hspace*{.02em} 0+1im \\
\hspace*{.02em} 1+0im \\
\hspace*{.02em} 2+0im
\jc
\ja
\sh{\# Polar comparison sort of complex numbers} \\
complex\_compare2(w,z) = (abs(w),angle(w)) < (abs(z),angle(z)) \\
sort([-2,2,-1,im,1], lt = complex\_compare2)
\jb
5-element Array\{Complex\{Int64\},1\}: \\
\hspace*{.02em} 1+0im \\
\hspace*{.02em} 0+1im \\
-1+0im \\
\hspace*{.02em} 2+0im \\
-2+0im
\jc
%In Julia one can define a ``less than" operator for any object, which
%can then be used for sorting. A total ordering of complex numbers is
%not well defined, and hence Julia does not define one by
%default. However, we are free to define our own ordering, and then use
%that to order a series of complex numbers. We show below a general
%approach that can work for \verb+(real,imag)+ or \verb+(abs,angle)+.
%We first define \verb+W+, a function that returns a function as its
%output. It takes a pair of functions from the complexes to the reals \verb+(f,g)+ as input and then
%converts a complex \verb+z+ into a real tuple: \verb+(f(z),g(z))+. Thus
%\verb+W(real,imag)(z)+ computes \verb+(real(z),imag(z))+. We can then
%define an \verb+lt+ function that compares any pair of functions we
%like. Julia already compares tuples lexicographically, so that does
%not need to be defined.
%\begin{jinput}
%\sh{ \# Convert z into a tuple (f(z), g(z)) e.g.\ (real(z),imag(z)) }\\
%\sh{ \# The -> syntax creates an anonymous function } \\
%\sh{\# W(f,g) creates a function that takes a Complex to the tuple}\\
%W(f,g) = z->(f(z),g(z)) \\
%\sh{\# Create a custom ``less than'' operator } \\
%\sh{\# lt(f,g) creates a function that takes a tuple to a Boolean} \\
%t(f,g) = (z,w)-> W(f,g)(z) < W(f,g)(w)\\
%\ti{ \# Sort by real part, then imaginary part} \\
%sort([2,-2,-1,im,1], lt = lt(real,imag) )
%\end{jinput}
%\begin{joutput}
%5-element Array\{Complex\{Int64\},1\}: \\
% -2+0im \\
% -1+0im \\
% \hspace*{.02em} 0+1im \\
%\hspace*{.02em} 1+0im \\
% \hspace*{.02em} 2+0im
%\end{joutput}
% \begin{jinput}[4]
%\sh{ \# Sort by absolute value, and then angle}\\
%sort([2,-2,-1,im,1], lt = lt(abs,angle) )
%\end{jinput}
%\begin{joutput}
% 5-element Array\{Complex\{Int64\},1\}: \\
%\hspace*{.02em} 1+0im \\
%\hspace*{.02em} 0+1im \\
%-1+0im \\
%\hspace*{.02em} 2+0im \\
%-2+0im
%\end{joutput}
Note the \verb+Array{ElementType,dims}+ syntax. It shows up everywhere.
In the above example, the elements are complex numbers whose parts are \verb+Int64+'s.
The \verb+1+ indicates it is a one dimensional vector.
%Extensive
%help is also available for Julia's functions:
%\begin{jinput}[2]
%help(qr)
%\end{jinput}
%\begin{joutput}
%INFO: Loading help data...
%Base.qr(A, [pivot=false,][thin=true]) -> Q, R, [p]
%Compute the (pivoted) QR factorization of "A" such that either \\
%"A = Q*R" or "A[:,p] = Q*R". Also see "qrfact". The default \\
%s to compute a thin factorization. Note that "R" is not extended \\
%with zeros when the full "Q" is requested.
%\end{joutput}
%The QR factorization in Julia yields the Q and R matrices as
%expected. The result of {\tt qr} is a tuple
%containing the two output matrices. Many Julia functions that return
%multiple output arguments return them in a tuple, from which they can
%be accessed by indexing.
%#\begin{verbatim}
%#julia> qr(A)
%#(
%#3x2 Array{Float64,2}:
%-0.406048 0.431218
%-0.235053 -0.901287
%-0.883105 0.0416203,
%2x2 Array{Float64,2}:
%-0.65531 -0.658722
% 0.0 -0.379163)
%\end{verbatim}
\begin{comment}
Julia also provides an interface to the QR factorization,
known as a
``factorization object." The result of the
QR factorization from LAPACK is more compact and efficient
than the alternative \verb+qr+ command which is also available.
\end{comment}
\begin{comment}
\begin{jinput}
X=qrfact(rand(4,2))
\end{jinput}
\begin{joutput}
QRCompactWY\{Float64\} ( 4x2 Array\{Float64,2\}: \\
\hspace*{.1em} -1.4584 \hspace*{.5em} -1.38826 \\
\> 0.414351 \hspace*{.1em} 0.813698 \\
\> 0.420327 \hspace*{.1em} 0.830722 \\
\> 0.415317 -0.0241482,2x2 Array\{Float64,2\}: \\
1.31505 \hspace*{1.5em} -1.17218 \\
6.93424e-310 1.18295)
\end{joutput}
In the example above $X$ is not a matrix, it is an efficient QR
Factorization object. We can build the components
upon request. Here is \verb+R+
\begin{jinput}
X[:R]
\end{jinput}
\begin{joutput}
2x2 Array\{Float64,2\}: \\
-1.4584 -1.38826 \\
\hspace*{.02em} 0.0 \, 0.813698
\end{joutput}
One can use the factorization object to find the least squares
solution. The backslash operator for QR factorization objects has been
overloaded in Julia:
\begin{jinput}
b=rand(4) {\sh { \# One dimensional vector} }
\end{jinput}
\begin{joutput}
4-element Array\{Float64,1\}: \\
\> 0.299695 \\
\> 0.976353 \\
\> 0.688573 \\
\> 0.653433
\end{joutput}
\begin{jinput}
X\ / b {\sh{ \# Least squares solution is efficient due to packed format} }
\end{jinput}
\begin{joutput}
2-element Array \{Float64,1\}: \\
\> 0.985617 \\
\hspace*{.12em} -0.0529438
\end{joutput}
\end{comment}
To be sure, experienced computer scientists tend to suspect there is nothing new under the sun.
The C function \verb+qsort()+ takes a \verb+compar+ function. Nothing really new there.
Python also has custom sorting with a key. MATLAB's sort is more basic.
The real contribution of Julia, as will be fleshed out further in this paper, is that
the design of Julia allows custom sorting to be much faster than other dynamic languages.
\newpage
The next example that we have chosen for the introductory taste of Julia is a quick plot of Brownian motion.
This example uses the matplotlib python package for graphics.
\ja
Pkg.add("PyPlot") \sh{\# Download the PyPlot package} \\
using PyPlot \sh {\# load the functionality into Julia} \\
\\
for i=1:5 \\
\, y=cumsum(randn(500)) \\
\, plot(y) \\
end \\ \\
\includegraphics[width=4in]{myfig2.pdf}
\end{jinput}
\vspace{.2in}
The matplotlib package is popular for users coming from Python or MATLAB.
Gadfly is another very popular package for plotting.
Gadfly was built by Daniel Jones and was influenced by the well admired Grammar of Graphics
(see \cite{gg1} and \cite{gg2}).
The syntax might be less familiar to some users of numerical computing languages,
but it is not hard to learn.\footnote{See tutorial on \code{http://gadflyjl.org}}
Many Julia users find Gadfly more flexible and
prefer the look of the output.
Gadfly has also added to the building of beautiful interactive graphics through the \code{Interact} package.
See \code{https://github.com/JuliaLang/Interact.jl/issues/36} for examples of Interact.
\newpage
\ja
Pkg.add("Gadfly") \sh{\# Download the Gadfly package} \\
using Gadfly \sh {\# load the functionality into Julia} \\
\begin{verbatim}
n = 500
p = [layer(x=1:n, y=cumsum(randn(n)), color=[i], Geom.line)
for i in ["First","Second","Third"]]
labels=(Guide.xlabel("Time"),Guide.ylabel("Value"),
Guide.Title("Brownian Motion Trials"),Guide.colorkey("Trial"))
plot(p...,labels...)
\end{verbatim}
\end{jinput}
\includegraphics[width=4in]{gadflyplot.pdf}
\vspace{.2in}
\newpage
In our last example in this
introductory tour, we illustrates graphics and histogramming: a user performs a simple
Monte-Carlo experiment: the famous semicircle law from Random Matrix Theory for a random eigenvalue of a symmetric matrix. In Section \ref{sec:easypar} we provide examples
of parallel monte carlo histograms.
\ja
n=100 \sh { \# Matrix Size} \\
t=1000 \sh {\# Number of Trials} \\
sym(A)=A+A' \sh {\# Define a function named} sym \sh{ to symmetrize a matrix} \\
\sh {\# Eigenvalues of t nxn matrices saved as vector } \\
z= [[eigvals(sym(randn(n,n))) for i=1:t]...] \\
z/=sqrt(2n) \\
\\
\sh {\# Histogram Plot} \\
{\# using PyPlot }
\vspace{-.05in}
\begin{verbatim}
plt.figure(figsize=(8,3.5))
x=-2:.01:2;
\end{verbatim}
\vspace{-.2in}
plot(x,sqrt(4.-x.\^{}2)/(2$\pi$),"r")
\vspace{-.05in}
\begin{verbatim}
plt.hist(z,bins=50,normed=true)
\end{verbatim}
\sh {\# Label and save to file}
\vspace{-.05in}
\begin{verbatim}
axis([-3,3,0,.4])
\end{verbatim}
\vspace{-.2in}
\verb&xlabel("normalized &$\lambda$\verb&")&
\vspace{-.06in}
\begin{verbatim}
ylabel("pdf")
title("Wigner Semi-Circle Law")
plt.savefig("myfig.pdf")
\end{verbatim}
\includegraphics[width=4in]{scfig.pdf}
\end{jinput}
\newpage
Julia has been in development since 2009; a public release was
announced in February of 2012. It is an active open source project
with over 250 contributors and is available under the MIT
License~\cite{mitlicense} for open source software. Nurtured at the
Massachusetts Institute of Technology, but with contributors from
around the world, Julia is increasingly seen as a high-performance
alternative to R, Matlab, Octave, Python, and SciLab, or a
high-productivity alternative to C, C++ and Fortran. It is also
recognized as being better suited to general purpose computing tasks
than traditional numerical computing systems, allowing it to be used
not only to prototype numerical algorithms, but also to deploy those
algorithms, and even serve results of numerical computations to the
rest of the world.\footnote{Sudoku as a service, by Iain Dunning,
\url{http://iaindunning.com/2013/sudoku-as-a-service.html}, is a
wonderful example where a Sudoku puzzle is solved using the
optimization capabilities of the JUMP.jl Julia package \cite{jump} and made
available as a web service.} Perhaps most significantly, a rapidly
growing ecosystem of high-quality, open source, composable numerical
packages (over 450 in number, see \url{http://pkg.julialang.org} and especially
\url{http://pkg.julialang.org/pulse.html})
written in Julia has emerged, including
libraries for linear algebra, statistics, optimization, data analysis,
machine learning and many other applications of numerical computing.
\subsection{An invaluable tool for numerical integrity}
One popular feature of Julia is that it gives the user the ability to
``kick the tires" of a numerical computation. We thank Velvel Kahan
for the sage advice\footnote{Personal communication, January 2013, in the Kahan
home, Berkeley, California} concerning the importance of this feature.
The idea is simple: a good engineer tests his or her code for numerical stability.
In Julia this can be done by changing IEEE rounding modes.
There are five modes to choose from, yet most engineers silently only choose the
\verb+RoundNearest+ mode default available in many numerical computing systems.
If a difference is detected, one can also run the computation in higher precision.
Kahan writes
\begin{quotation}
Can the effects of roundoff upon a floating-point computation be assessed without submitting it to
a mathematically rigorous and (if feasible at all) time-consuming error-analysis? In general, No.
$\ldots$
Though far from foolproof, rounding every inexact arithmetic operation (but not
constants) in the same direction for each of two or three directions besides the
default To Nearest is very likely to confirm accidentally exposed hypersensitivity
to roundoff. When feasible, this scheme offers the best {\it Benefit/Cost }ratio.
\cite{kahan:mindless}
\end{quotation}
As an example, we round a 15x15 Hilbert-like matrix, and take the [1,1] entry of the inverse
computed in various round off modes. The radically different answers dramatically indicates
the numerical sensitivity to roundoff.
We even noticed that slight changes to LAPACK give radically different answers. Very likely
you will see different numbers when you run this code due to the very high sensitivity to roundoff errors.
\begin{jinput}
h(n)=[1/(i+j+1) for i=1:n,j=1:n]
\end{jinput}
\begin{joutput}
h (generic function with 1 method)
\end{joutput}
\begin{jinput}
H=h(15); \\
with\_rounding(Float64,RoundNearest) do \\
\, inv(H)[1,1] \\
end
\end{jinput}
\begin{joutput}
154410.55589294434
\end{joutput}
\begin{jinput}
with\_rounding(Float64,RoundUp) do \\
\, inv(H)[1,1] \\
end
\end{jinput}
\begin{joutput}
-49499.606132507324
\end{joutput}
\begin{jinput}
with\_rounding(Float64,RoundDown) do \\
\, inv(H)[1,1] \\
end
\end{jinput}
\begin{joutput}
-841819.4371948242
\end{joutput}
With 300 bits of precision we obtain \\
\begin{jinput}
with\_bigfloat\_precision(300) do \\
\, inv(big(H))[1,1] \\
end
\end{jinput}
\begin{joutput}
-2.09397179250746270128280174214489516162708857703714959763232689047153\\ 50765882491054998376252e+03
\end{joutput}
Note this is the [1,1] entry of the inverse of the rounded Hilbert-like
matrix, not the inverse of the exact Hilbert-like matrix. Also, the results
are senstive to the BLAS and LAPACK, and the results may differ on
different machines with different versions of Julia.
%whose [1,1] entry would be the integer 225.
\subsection{Julia architecture and language design philosophy}
Many popular dynamic languages were not designed with the goal of high
performance. After all, if you wanted really good performance you
would use a static language, or so the popular wisdom would say. Only with the increased need in the
day-to-day life of scientific programmers for simultaneous
productivity and performance in a single system has the need for
high-performance dynamic languages become pressing. Unfortunately,
retrofitting an existing slow dynamic language for high performance is
almost impossible \textit{specifically} in numerical computing
ecosystems. This is because numerical computing requires
performance-critical numerical libraries, which invariably depend on
the details of the internal implementation of the high-level language,
thereby locking in those internal implementation details.
For example, you can run Python code much faster than the standard
CPython implementation using the PyPy just-in-time compiler; but PyPy
is currently incompatible with NumPy and the rest of SciPy.
% Ironically, while you \textit{can} retrofit dynamic languages with
% faster implementations for non-numerical computing, \textit{because}
% of the intense performance requirements numerical computing places on
% libraries you cannot significantly improve the speed of a slow
% language without losing all the high-performance libraries that make
% it appealing in the first place. While one can leverage
% high-performance libraries such as BLAS and LAPACK, it is libraries
% that are written natively in the dynamic language itself that are hard
% to extract performance from.
Another important point is that just because a program is available in C or
Fortran, it may not run
efficiently from the high level language or be easy to ``glue" it in.
For example when Steven Johnson tried to include his C erf function
in Python he
reported that Pauli Virtane
had to write glue code\footnote{\url{https://github.com/scipy/scipy/commit/ed14bf0}} to vectorize the erf function over the native
structures in Python in order to realize the performance
and Johnson had to write glue code for Matlab, Octave,
and Scilab. The Julia effort was,
by contrast, effortless.\footnote{Steven Johnson, personal communication. See \url{http://ab-initio.mit.edu/wiki/index.php/Faddeeva_Package})}
As another example, a benchmark of the normal random number generator
written in pure Julia is twice as fast as MATLAB's \verb+randn+ written
in a lower level language.
The best path to a fast, high-level system for scientific and
numerical computing is to make the system fast enough that all of its
libraries can be written in the high-level language in the first
place. The JUMP.jl library~\cite{jump} for mathematical optimization
written by Miles Lubin and Iain Dunning is a great example of the
success of this approach---the entire library is written in Julia and
uses many language features such as metaprogramming, user-defined
parametric types, and multiple dispatch extensively to provide a
seamless interface to describe various kinds of optimization problems
and solve them with any number of commercially or freely available
solvers.
{\bf The Two Language Problem:}
As long as the developers' language is harder than the users' language, numerical computing will always be hindered
This is an essential part of the design philosophy of Julia: all basic
functionality must be possible to implement in Julia---never force the
programmer to resort to using C or Fortran.
Julia solves the two language problem.
Basic
functionality must be fast: integer arithmetic, for loops, recursion,
floating-point operations, calling C functions, manipulating C-like
structs. While these are not only important for numerical programs,
without them, you certainly cannot write fast numerical code.
``Vectorization languages'' like Matlab, R, and Python+NumPy hide
their for loops and integer operations, but they are still there, inside the C
and Fortran, lurking behind the thin veneer. Julia removes this
separation entirely, allowing high-level code to ``just write a for
loop'' if that happens to be the best way to solve a problem.
We believe that the Julia programming language fulfills much of the
Fortran dream: automatic translation of formulas into efficient
executable code. It allows programmers to write clear, high-level,
generic and abstract code that closely resembles mathematical
formulas, as they have grown accustomed to in dynamic systems, yet
produces fast, low-level machine code that has traditionally only been
generated by static languages.
Julia's ability to combine these levels of performance and
productivity in a single language stems from the choice of a number of
features that work well with each other:
\begin{enumerate}
\item An expressive parametric type system, allowing optional type
annotations; (See Section \ref{sec:firstclass} for parametric types and Section \ref{sec:dispatch} for optional type annotations.)
\item Multiple dispatch using those types to select implementations (Section \ref{sec:select});
\item A dynamic dataflow type inference algorithm allowing types of
most expressions to be inferred (Section \ref{sec:inference});
\item Careful design of the language and standard library to be
amenable to type analysis (Section \ref{sec:lang});
\item Aggressive code specialization against run-time types (Section \ref{sec:reuse});
\item Metaprogramming for sophisticated code generation (Section \ref{sec:reuse});
\item Just-In-Time (JIT) compilation using the Low-level Virtual
Machine (LLVM) compiler framework~\cite{LLVM}, which is also used
by a number of other compilers such as Clang~\cite{clang} and
Apple's Swift~\cite{swift} (Section \ref{sec:reuse}).
\end{enumerate}
Although a sophisticated type system is made available to the
programmer, it remains unobtrusive in the sense that one is never
required to specify types, nor are type annotations necessary for
performance. Type information flows naturally from having actual
values (and hence types) during code generation, and from the multiple
dispatch paradigm: by expressing polymorphic behavior of functions
declaratively with multiple dispatch, the programmer provides the
compiler with extensive type information, while also enjoying
increased expressiveness.
In what follows, we describe the benefits of Julia's language design
for numerical computing, allowing programmers to more readily express
themselves and obtain performance at the same time.
% data flow type inference algorithm, as well as the various language
% features and how they amplify the effectiveness of this algorithm
% and allow We also provide examples of how Julia translates, clear,
% high-level, generic code into the kind of fast, native machine code
% that a human would write, with all the layers of abstraction
% removed, reducing the computation to its bare minimum.
%% \item{\bf A social organization for scientists and scientific ideas}\\
%% Lively high quality discussions on Google Groups, Github, and
%% StackOverflow discuss the nuances of scientific numerical computing
%% at an expert level. In the past, a university or organization was
%% lucky to have an expert on numerical analysis nearby. Now there is
%% an entire community online ready to discuss the nuances of error
%% analysis, IEEE standards, effective algorithms, and best coding
%% practices.
%% \end{enumerate}
\section{Writing programs with and without types}
\label{sec:types}
\subsection{The balance between human and the computer}
\label{sec:humancomputer}
Graydon Hoare, author of the Rust programming
language~\cite{rust}, in an essay on ``Interactive Scientific
Computing''~\cite{hoareessay} defined programming languages
succinctly:
\begin{quote}
Programming languages are mediating devices, interfaces that try to
strike a balance between human needs and computer needs. Implicit in
that is the assumption that human and computer needs are equally
important, or need mediating.
\end{quote}
Catering to the needs of both the human and the computer is a
repeating theme in this paper. A program consists of data and
operations on data. Data is not just the input file, but everything
that is held ---an array, a list, a graph, a constant---during the
life of the program. The more the computer knows about this data, the
better it is at executing operations on that data. Types are
exactly this metadata. Describing this metadata, the types, takes real
effort for the human. Statically typed languages such as C and Fortran
are at one extreme, where all types must be defined and are statically
checked during the compilation phase. The result is excellent
performance. Dynamically typed languages dispense with type
definitions, which leads to greater productivity, but lower
performance as the compiler and the runtime cannot benefit from the
type information that is essential to produce fast code. Can we strike
a balance between the human's preference to avoid types and the
computer's need to know?
\subsection{Julia's recognizable types}
Julia's design allows for the gradual learning of concepts, where users
start in a manner that is familiar to them and over time, learn to
structure programs in the ``Julian way'' (a term that captures
well-structured readable high performance Julia code). Julia users
coming from other numerical computing environments have a notion that
data may be represented as matrices that may be dense, sparse,
symmetric, triangular, or of some other kind. They may also, though
not always, know that elements in these data structures may be single
precision floating point numbers, double precision, or integers of a
specific width. In more general cases, the elements within data
structures may be other data structures. We introduce Julia's type
system using matrices and their number types:
\begin{jinput}
rand(1,2,1)
\end{jinput}
\begin{joutput}
1x2x1 Array\{Float64,3\}: \\
{}[ :, :, 1{}] = \\
\> 0.789166 0.652002
\end{joutput}
% \begin{jinput}
% A=eye(3)
% \end{jinput}
% \begin{joutput}
% 3x3 Array\{Float64,2\}: \\
% \> 1.0 0.0 0.0 \\
% \> 0.0 1.0 0.0 \\
% \> 0.0 0.0 1.0
% \end{joutput}
\begin{jinput}
{}[1 2; 3 4{}]
\end{jinput}
\begin{joutput}
2x2 Array\{Int64,2\}: \\
\> 1 2 \\
\> 3 4
\end{joutput}
\begin{jinput}
{}[true; false{}]
\end{jinput}
\begin{joutput}
2-element Array\{Bool,1\}: \\
\> true \\
\> false
\end{joutput}
We see a pattern in the examples above. \noindent
\verb+Array{T,ndims}+ is the general form of the type of a dense
array with \verb+ndims+ dimensions, whose elements themselves have
a specific type \verb+T+, which is of type double precision floating
point in the first example, a 64-bit signed integer in the second, and
a boolean in the third example.
%Array size is not part of the type, even though it is displayed with the array.
Therefore \verb+Array{T,1}+ is a 1-d vector (first class
objects in Julia) with element type \verb+T+ and \verb+Array{T,2}+ is
the type for 2-d matrices.
It is useful to think of arrays as a
generic N-d object that may contain elements of any type
\verb+T+. Thus \verb+T+ is a type parameter for an array that can take
on many different values. Similarly, the dimensionality of the array
\verb+ndims+ is also a parameter for the array type. This generality
makes it possible to create arrays of arrays. For example, Using
Julia's array comprehension syntax, we create a 2-element vector
containing $2\times2$ identity matrices.
\begin{jinput}
a = {}[eye(2) for i=1:2{}]
\end{jinput}
\begin{joutput}
2-element Array\{Array\{Float64,2\},1\}:
\end{joutput}
\noindent
Many first time users are already familiar with basic floating point
number types such as \verb+Float32+ (single precision) and
\verb+Float64+ (double precision). In addition, Julia also provides a
built-in \verb+BigFloat+ type that may interest a new Julia user.
With \verb+BigFloat+s, users can run computations in arbitrary
precision arithmetic, thereby exploring the numerical properties of
their computation.
\subsection{User's own types are first class too}
\label{sec:firstclass}
%We would be remiss if we did not illustrate how to define a new type
%in Julia.
Many dynamic languages for numerical computing have traditionally had
an asymmetry, where built-in types have much higher
performance than any user-defined types. This is not the case with
Julia, where there is no meaningful distinction between user-defined
and ``built-in'' types.
We have mentioned so far a few number types and two matrix types,
\verb+Array{T,2}+ the dense array, with element type \verb+T+ and
\verb+SymTridiagonal{T}+, the symmetric tridiagonal with element type \verb+T+. There
are also other matrix types, for other structures including
SparseMatrixCSC (Compressed Sparse Columns), Hermitian, Triangular, Bidiagonal, and Diagonal. Julia's sparse matrix type
has an added flexibility that it can go beyond storing just numbers as
nonzeros, and instead store any other Julia type as well. The indices
in SparseMatrixCSC can also be represented as integers of any width
(16-bit, 32-bit or 64-bit). All these different matrix types, although
available as built-in types to a user downloading Julia, are
implemented completely in Julia, and are in no way any more or less
special than any other types one may define in their own program.
For demonstration, we create a symmetric arrow matrix type that
contains a diagonal and the first row \verb+A[1,2:n]+.
\ja
\sh{ \# Parametric Type Example (Parameter T)} \\
\sh{\# Define a Symmetric Arrow Matrix Type with elements of type T} \\
type SymArrow\{T\} \\
\, dv::Vector\{T\} \sh{ \# diagonal }\\
\, ev::Vector\{T\} \sh{ \# 1st row{}[2:n{}] }\\
end \\ \\
\sh{ \# Create your first Symmetric Arrow Matrix} \\
S = SymArrow([1,2,3,4,5],[6,7,8,9])
\jb
\begin{verbatim}
SymArrow{Int64}([1,2,3,4,5],[6,7,8,9])
\end{verbatim}
\jc
%We will flesh out this Symmetric Arrow Matrix example further in Section \ref{sec:xdispatch}.
Our purpose here is to
introduces ``Parametric Types''.
Later in Section \ref{sec:arrow}, we develop this example much further.
The \verb+SymArrow+
matrix type contains two vectors, one each for the diagonal and the
first row, and these vector contain elements of type \verb+T+. In the type definition,
the type \verb+SymArrow+ is parametrized by the type of the
storage element \verb+T+. By doing so, we have created a generic type,
which refers to a universe of all arrow matrices containing elements
of all types. The matrix \verb+S+, is an example where \verb+T+ is \verb+Int64+.
When we write functions in Section~\ref{sec:arrow}
that operate on arrow matrices, those functions themselves will be
generic and applicable to the entire universe of arrow matrices we
have defined here.
Julia's type system allows for abstract types, concrete ``bits''
types, composite types, and immutable composite types. All of these
can be parametric and users may even write programs using unions of
these different types. We refer the reader to read all about Julia's
type system in the types chapter in the Julia manual\footnote{See the chapter
on types in the Julia manual:
\url{http://docs.julialang.org/en/latest/manual/types/}}.