Is it possible to capture standard deviation from %%timeit -o? - jupyter-notebook

I get average time to take running function but cannot get standard deviation.
import random
def average_py(n):
s = 0
for i in range(n):
s += random.random()
return s / n
n = 10_000_000
result_py = %timeit -o average_py(n)
670 ms ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
print(result_py.average)
0.6704248709886867
I already checked ipython document.

It is accessible through result_py.stdev.
In [1]: import random
...: def average_py(n):
...: s = 0
...: for i in range(n):
...: s += random.random()
...: return s / n
...: n = 10_000_000
In [2]: result_py = %timeit -o average_py(n)
1.37 s ± 40.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [3]: result_py.stdev
Out[3]: 0.040515329046983364

Related

Recursively sum all digits until number is single digit

For example, sum all the digits of 1253, 1+2+5+3 which is 11. This is two digits, so sum those again to get 2. The final number left is a single digit.
This is what I have so far:
defmodule Kata do
def digital_root(n) do
n |> Integer.digits() |> Enum.reduce(0, &Kernel.+/2)
end
end
n = 1253
You can use a multi clause function for this: in the first one, return n unchanged if it's a single digit. Otherwise, compute the sum of digits (like you've already figured out) and recursively call the function on it again. Also, your reduce can be replaced with Enum.sum/1.
defmodule Kata do
def digital_root(n) when n < 10, do: n
def digital_root(n) do
n |> Integer.digits() |> Enum.sum() |> digital_root()
end
end
Test:
iex(1)> Kata.digital_root(0)
0
iex(2)> Kata.digital_root(1)
1
iex(3)> Kata.digital_root(9)
9
iex(4)> Kata.digital_root(91)
1
iex(5)> Kata.digital_root(912)
3
iex(6)> Kata.digital_root(9123)
6
iex(7)> Kata.digital_root(1253)
2
Just for fun, here's a version that doesn't use Integer.digits/2 or Enum, and instead uses div/2 and rem/2 to calculate the least significant digit for each iteration:
defmodule Kata do
# Function header
def digital_root(to_sum, acc \\ 0)
# Base case. All digits added, acc is a single digit
def digital_root(0, acc) when acc < 10, do: acc
# Finished a round. All digits added, acc is multiple digits. Sum again.
def digital_root(0, acc), do: digital_root(acc, 0)
# Split off the least significant digit, add it, and recurse
def digital_root(to_sum, acc) do
digit = rem(to_sum, 10)
next_to_sum = div(to_sum, 10)
digital_root(next_to_sum, acc + digit)
end
end
Output:
iex> Kata.digital_root(1523)
2
In a benchmark, this version is twice as fast as dogbert's, and three times faster than 7stud's answers.
Name ips average deviation median 99th %
adam 14.26 M 70.13 ns ±1898.51% 66.70 ns 83.30 ns
dogbert 7.08 M 141.28 ns ±14510.93% 125 ns 167 ns
sevenstud 4.83 M 206.98 ns ±15193.15% 167 ns 292 ns
Comparison:
adam 14.26 M
dogbert 7.08 M - 2.01x slower +71.14 ns
sevenstud 4.83 M - 2.95x slower +136.85 ns
Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 16 GB
Elixir 1.13.4
Erlang 24.3.4
def digit_sum(number) when number < 10, do: number
def digit_sum(number) when is_integer(number) do
digit_sum(
for digit <- Integer.digits(number), reduce: 0 do
acc -> acc + digit
end
)
end
I like Dogbert's better.

Binding multiple variables to copies in BenchmarkTools.jl setup

I am trying to use the #benchmarkable macro from BenchmarkTools.jl. In the package documentaton they explain how to pass setup expressions to #benchmark and #benchmarkable. They also explain that this can be used for in-place/mutating functions in order to bind copies of the input variables.
I am not sure, however, how to use the setup expression to copy multiple variables at the same time.
For example, imagine I want to benchmark the following function (the actual function is irrelevant):
function my_function!(x, y)
deleteat!(x, y .== 0)
deleteat!(y, y .== 0)
x .= x .* 2
end
With the following inputs:
using BenchmarkTools
a = collect(1:30)
b = rand(0:5, 30)
I would like to perform the benchmark by binding a copy of a and b to variables y and z respectively.
t = #benchmarkable my_function!(m, n) setup=(m = copy($a), n = copy($b)) evals = 30
run(t)
However, running the previous code returns the following error:
ERROR: LoadError: UndefVarError: m not defined
setup requires a block of code so:
t = #benchmarkable my_function!(m, n) setup=begin; m = copy($a); n = copy($b);end evals = 30
Now you can run it:
julia> run(t)
BenchmarkTools.Trial: 10000 samples with 30 evaluations.
Range (min … max): 173.333 ns … 131.320 μs ┊ GC (min … max): 0.00% … 99.33%
Time (median): 210.000 ns ┊ GC (median): 0.00%
Time (mean ± σ): 251.470 ns ± 1.315 μs ┊ GC (mean ± σ): 5.19% ± 0.99%
▂█ ▁
██▇█▅▇█▅▆▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▁▁▁▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂ ▃
173 ns Histogram: frequency by time 807 ns <

Compute eigenvalues of complex-hermitian sparsematrix in Julia

Im working with some roughly 100000x100000 hermitian complex sparse-matrices, with roughly 5% of entries populated, and want to calculate the eigenvalues/eigenvectors.
Sofar ive been using Arpack.jl eigs(A).
But this is not working well as soon as i crank the size to higher then 5000.
For the benchmarks ive been using the following code to generate some TestMatrices:
using Arpack
using SparseArrays
using ProgressMeter
pop = 0.05
n = 3000 # for example
A = spzeros(Complex{Float64}, n, n)
#showprogress for _ in 1:round(Int,pop * (n^2))
A[rand(1:n), rand(1:n)] = rand(Complex{Float64})
end
# make A hermite
A = A + conj(A)
t = #elapsed eigs(A,maxiter=1500) # ends up being ~ 13 seconds
For n ~ 3000 the eigs() call already takes 13 seconds on my machine, and for bigger n it doesn't finish in any 'reasonable' time or outright quits.
Is there a specialized package/method for this ?
Any help is appreciated
https://github.com/JuliaLinearAlgebra/ArnoldiMethod.jl seems to be what you want:
julia> let pop=0.05, n=3000
A = sprand(Complex{Float64},n,n, 0.05)
A = A + conj(A)
#time eigs(A; maxiter=1500)
#time decomp, history = partialschur(A, nev=10, tol=1e-6, which=LM());
end;
10.521786 seconds (50.73 k allocations: 3.458 MiB, 0.04% gc time)
2.244129 seconds (19 allocations: 1.892 MiB)
sanity check:
julia> a,(b,c) = let pop=0.05, n=300
A = sprand(Complex{Float64},n,n, 0.05)
A = A + conj(A)
eigs(A; maxiter=2500), partialschur(A, nev=6, tol=1e-6, which=LM());
end;
julia> a[1]
6-element Vector{ComplexF64}:
14.5707071003175 + 8.218901803015509e-16im
4.493079744504954 - 0.8390429567118733im
4.493079744504933 + 0.8390429567118641im
-0.3415176925293196 + 4.254184281244591im
-0.3415176925293088 - 4.25418428124452im
0.49406553681541177 - 4.229680489599233im
julia> b
PartialSchur decomposition (ComplexF64) of dimension 6
eigenvalues:
6-element Vector{ComplexF64}:
14.570707100307926 + 7.10698633463049e-12im
4.493079906269516 + 0.8390429076809746im
4.493079701528448 - 0.8390430155670777im
-0.3415174262177961 + 4.254183175902487im
-0.34151626930774975 - 4.25418321627979im
0.49406543866702 + 4.229680079205066im

Lua: How do I calculate a random number between 50 and 500, with an average result of 100?

I think this is a log-normal distribution? I'm not sure. The lua I have is here:
local min = 50
local max = 500
local avg = 100
local fFloat = ( RandomInt( 0, avg ) / 100 ) ^ 2 -- float between 0 and 1, squared
local iRange = max - min -- range of min-max
local fDistribution = ( ( fFloat * iRange ) + min ) / 100
finalRandPerc = fDistribution * RandomInt( min, max ) / 100
It is close to working, but sometimes generates numbers that are slightly too large.
This can be done in literally infinite number of ways. One other approach is to generate a number from binomial distribution, multiply with 450 and add 50. I will leave the task of finding the right parameters for the binomial distribution to you.
How do I calculate a random number between 50 and 500, with an average result of 100?
You can use Chi-squared of degree 4 with its tail removed.
It is very easy to calculate.
local function random_50_500()
-- result is from 50 to 500
-- mean is very near to 100
local x
repeat
x = math.log(math.random()) + math.log(math.random())
until x > -18
return x * -25 + 50
end

Julia: nested loops for pairwise distances is really slow

I have some code which loads a csv file of 2000 2D coordinates, then a function called collision_count counts the number of pairs of coordinates that are closer than a distance d of each other:
using BenchmarkTools
using CSV
using LinearAlgebra
function load_csv()::Array{Float64,2}
df = CSV.read("pos.csv", header=0)
return Matrix(df)'
end
function collision_count(pos::Array{Float64,2}, d::Float64)::Int64
count::Int64 = 0
N::Int64 = size(pos, 2)
for i in 1:N
for j in (i+1):N
#views dist = norm(pos[:,i] - pos[:,j])
count += dist < d
end
end
return count
end
Here are the results:
pos = load_csv()
#benchmark collision_count($pos, 2.0)
BenchmarkTools.Trial:
memory estimate: 366.03 MiB
allocs estimate: 5997000
--------------
minimum time: 152.070 ms (18.80% GC)
median time: 158.915 ms (20.60% GC)
mean time: 158.751 ms (20.61% GC)
maximum time: 181.726 ms (21.98% GC)
--------------
samples: 32
evals/sample: 1
This is about 30x slower than this Python code:
import numpy as np
import scipy.spatial.distance
pos = np.loadtxt('pos.csv',delimiter=',')
def collision_count(pos, d):
pdist = scipy.spatial.distance.pdist(pos)
return np.count_nonzero(pdist < d)
%timeit collision_count(pos, 2)
5.41 ms ± 63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Any way to make it faster? And what's up with all the allocations?
The fastest I can get trivially is the follows
using Distances
using StaticArrays
using LinearAlgebra
pos = [#SVector rand(2) for _ in 1:2000]
function collision_count(pos::Vector{<:AbstractVector}, d)
count = 0
#inbounds for i in axes(pos,2)
for j in (i+1):lastindex(pos,2)
dist = sqeuclidean(pos[i], pos[j])
count += dist < d*d
end
end
return count
end
There are a variety of changes here, some stylistic, some structural. Starting with style, you may note that I don't type anything more restrictively than I need to. This has no performance benefit, since Julia is smart enough to infer types for your code.
The biggest structural change is switching from using a matrix to a vector of StaticVectors. The reason for this change is that since points are your scalar type, it makes more sense to have a vector of elements where each element is a point. The next change I made is to use a squared norm, since sqrt operations are expensive. The results speak for themselves:
#benchmark collision_count($pos, .1)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 1.182 ms (0.00% GC)
median time: 1.214 ms (0.00% GC)
mean time: 1.218 ms (0.00% GC)
maximum time: 2.160 ms (0.00% GC)
--------------
samples: 4101
evals/sample: 1
Note that there are n log(n) algorithms that may be faster, but this should be pretty close to optimal for a naive implementation.
Here's a solution that doesn't rely on specific knowledge about the dimensionality of the points:
(Edit: I updated the function to make it more robust with respect to indexing. Some AbstractArrays have indices that do not start at 1, so now I use axes and lastindex instead of size.)
function collision_count2(pos::AbstractMatrix, d)
count = 0
#inbounds for i in axes(pos, 2)
for j in (i+1):lastindex(pos, 2)
dist2 = sum(abs2(pos[k, i] - pos[k, j]) for k in axes(pos, 1))
count += dist2 < d^2
end
end
return count
end
Benchmarks:
julia> using BenchmarkTools
julia> #btime collision_count(pos, 0.7) setup=(pos=rand(2, 2000));
533.055 ms (13991005 allocations: 488.01 MiB)
julia> #btime collision_count2(pos, 0.7) setup=(pos=rand(2, 2000));
4.700 ms (0 allocations: 0 bytes)
The speed is actually close to the SVector solution. On the upcoming Julia version 1.5, the difference compared to the OP's code should be much smaller, since views become more efficient.
BTW: drop the type annotations, like these
count::Int64 = 0
N::Int64 = size(pos, 2)
it's just adding visual noise.

Resources