Julia has a built-in function to round to n significant digits. signif(0.0229, 2) will round to two significant digits and give 0.023.
How can I chop or truncate to n significant digits so that I would get 0.022 instead?
Well, not very imaginative. Used #edit signif(0.229,2) to find the source and replace round with floor (and added a Base. for correct Module referencing). Here is the result:
function mysignif(x::Real, digits::Integer, base::Integer=10)
digits < 1 && throw(DomainError(digits, "`digits` cannot be less than 1."))
x = float(x)
(x == 0 || !isfinite(x)) && return x
og, e = Base._signif_og(x, digits, base)
if e >= 0 # for numeric stability
r = trunc(x/og)*og
else
r = trunc(x*og)/og
end
!isfinite(r) ? x : r
end
Giving:
julia> mysignif(0.0229,2)
0.022
I found a version in Maple and ported to Julia:
function signifChop(num, digits)
if num == 0.0 then
return num
else
e = ceil(log10(abs(num)))
scale = 10^(digits - e)
return trunc(num * scale) / scale
end
end
# Test cases for signifChop
println("$(signifChop(124.031, 5))")
println("$(signifChop(124.036, 5))")
println("$(signifChop(-124.031, 5))")
println("$(signifChop(-124.036, 5))")
println("$(signifChop(0.00653, 2))")
println("$(signifChop(0.00656, 2))")
println("$(signifChop(-0.00653, 2))")
println("$(signifChop(-0.00656, 2))")
Note that signif was removed as of Julia 1.0.
However, now Base.round accepts the sigdigits keyword:
julia> round(pi, digits=3)
3.142
julia> round(pi, sigdigits=3)
3.14
The same works for trunc, ceil, and floor.
Source: mforets on Github and #DNF in the comments.
Related
Is it possible to use vectorized variables with user-defined objective functions in JuMP for Julia? Like so,
model = Model(GLPK.Optimizer)
A = [
1 1 9 5
3 5 0 8
2 0 6 13
]
b = [7; 3; 5]
c = [1; 3; 5; 2]
#variable(model, x[1:4] >= 0)
#constraint(model, A * x .== b)
# dummy functions, could be nonlinear hypothetically
identity(x) = x
C(x, c) = c' * x
register(model, :identity, 1, identity; autodiff = true)
register(model, :C, 2, C; autodiff = true)
#NLobjective(model, Min, C(identity(x), c))
This throws the error,
ERROR: Unexpected array VariableRef[x[1], x[2], x[3], x[4]] in nonlinear expression. Nonlinear expressions may contain only scalar expression.
Which sounds like no. Is there a workaround to this? I believe scipy.optimize.minimize is capable of optimizing user-defined objectives with vectorized variables?
No, you cannot pass vector arguments to user-defined functions.
Documentation: https://jump.dev/JuMP.jl/stable/manual/nlp/#User-defined-functions-with-vector-inputs
Issue you opened: https://github.com/jump-dev/JuMP.jl/issues/2854
The following is preferable to Prezemyslaw's answer. His suggestion to wrap things in an #expression won't work if the functions are more complicated.
using JuMP, Ipopt
model = Model(Ipopt.Optimizer)
A = [
1 1 9 5
3 5 0 8
2 0 6 13
]
b = [7; 3; 5]
c = [1; 3; 5; 2]
#variable(model, x[1:4] >= 0)
#constraint(model, A * x .== b)
# dummy functions, could be nonlinear hypothetically
identity(x) = x
C(x, c) = c' * x
my_objective(x...) = C(identitiy(collect(x)), c)
register(model, :my_objective, length(x), my_objective; autodiff = true)
#NLobjective(model, Min, my_objective(x...))
Firstly, use optimizer that supports nonlinear models. GLPK does not. Try Ipopt:
using Ipopt
model = Model(Ipopt.Optimizer)
Secondly, JuMP documentation reads (see https://jump.dev/JuMP.jl/stable/manual/nlp/#Syntax-notes):
The syntax accepted in nonlinear macros is more restricted than the syntax for linear and quadratic macros. (...) all expressions must be simple scalar operations. You cannot use dot, matrix-vector products, vector slices, etc.
you need wrap the goal function
#expression(model, expr, C(identity(x), c))
Now you can do:
#NLobjective(model, Min, expr)
To show that it works I solve the model:
julia> optimize!(model)
This is Ipopt version 3.14.4, running with linear solver MUMPS 5.4.1.
...
Total seconds in IPOPT = 0.165
EXIT: Optimal Solution Found.
julia> value.(x)
4-element Vector{Float64}:
0.42307697548737005
0.3461538282496562
0.6923076931757742
-8.46379887234798e-9
I'm playing around with the Julia language, and noticed that the small program I wrote was quite slow.
Suspecting that it was somehow related to the for loops, I rewrote it to use while, and got about 15x faster.
I'm sure there's something I'm doing wrong with the ranges etc., but I can't figure out what.
function primes_for()
num_primes = 0
for a = 2:3000000
prime = true
sa = floor(sqrt(a))
for c in 2:sa
if a % c == 0
prime = false
break
end
end
if prime
num_primes += 1
end
end
println("Number of primes is $num_primes")
end
function primes()
num_primes = 0
a = 2
while a < 3000000
prime = true
c = 2
while c * c <= a
if a % c == 0
prime = false
break
end
c += 1
end
if prime
num_primes += 1
end
a += 1
end
println("Number of primes is $num_primes")
end
#time primes_for()
#time primes()
As explained in the comments by #Vincent Yu and #Kelly Bundy, this is because sa = floor(sqrt(a)) creates a float. Then c becomes a float, and a % c is slow.
You can replace floor(sqrt(a)) with floor(Int, sqrt(a)), or preferably, I think, with isqrt(a), which returns
Integer square root: the largest integer m such that m*m <= n.
This avoids the (unlikely) event that floor(Int, sqrt(a)) may round down too far, which could happen if sqrt(x^2) = x - ε due to floating point errors.
Edit: Here's a benchmark to demonstrate (note the use of isqrt):
function primes_for2()
num_primes = 0
for a = 2:3000000
prime = true
# sa = floor(sqrt(a))
sa = isqrt(a)
for c in 2:sa
if a % c == 0
prime = false
break
end
end
if prime
num_primes += 1
end
end
println("Number of primes is $num_primes")
end
1.7.0> #time primes_for()
Number of primes is 216816
6.705099 seconds (15 allocations: 480 bytes)
1.7.0> #time primes_for2()
Number of primes is 216816
0.691304 seconds (15 allocations: 480 bytes)
1.7.0> #time primes()
Number of primes is 216816
0.671784 seconds (15 allocations: 480 bytes)
I can note that each call to isqrt on my computer takes approximately 8ns, and that 3000000 times 8ns is 0.024 seconds. A call to regular sqrt is approximately 1ns.
It's not the for/while that makes the speed difference, it's the sqrt. It doesn't help that sqrt returns float, which promotes all the rest of the code around the sqrt output from integers.
Note that #time is not measuring the while and for loops, but also the code outside those loops.
If you are benchmarking code, the rest of your code needs to be the same, and removing the sqrt is one of the prime optimizations in this algorithm. It's also possible to remove the c * c in the test, but this is trickier.
Engineering notation differs from scientific notation in that:
The exponent is always a multiple of 3, and
The digits to the left of the decimal point are scaled to range from 1 to 999.
My use case calls for specifying 0 to 13 digits to the right of the decimal point. The default is 4.
Here are desired examples:
const Avogadro = 6.022140857e23
str = eng_notation(Avogadro, digits=0)
# str = "602E+21"
str = eng_notation(Avogadro, digits=1)
# str = "602.2E+21"
# Default 4 digits to right of decimal point.
str = eng_notation(Avogadro)
# str = "602.2141E+21"
str = eng_notation(Avogadro, digits=10)
# str = "602.2140857000E+21"
# Negative and fractional numbers should also work.
str = eng_notation(-0.01234567, digits=7)
# str = "-12.4567000E-03"
Any suggestions?
Edit: I updated the requirements to 0 to 13 digits to the right of the decimal point (from 0 to 15 previously).
Use the NumericIO.jl package
julia> using NumericIO
julia> const Avogadro = 6.022140857e23;
julia> formatted(Avogadro, :ENG, ndigits=4, charset=:ASCII)
"602.2E21"
julia> formatted(Avogadro, :ENG, ndigits=4)
"602.2×10²¹"
The updated eng_notation() function below appears to solve the problem.
The number of digits to the right of the decimal is now limited to 0 to 13 digits instead of 0 to 15 digits.
Here are some examples:
julia> const Avogadro = 6.022140857e23
6.022140857e23
julia> eng_notation(Avogadro, digits=0)
"602E+21"
julia> eng_notation(Avogadro, digits=1)
"602.2E+21"
julia> eng_notation(Avogadro)
"602.2141E+21"
julia> eng_notation(Avogadro, digits=10)
"602.2140857000E+21"
julia> eng_notation(-0.01234567, digits=7)
"-12.3456700E-03"
julia> eng_notation(Avogadro, digits=13, plus_sign=true)
"+602.2140857000000E+21"
julia> eng_notation(floatmax(Float64), digits=13)
"179.7693134862316E+306"
julia> eng_notation(floatmin(Float64), digits=13)
"22.2507385850720E-309"
Here is the updated code:
"""
eng_notation(num, digits=4, spec="E", plus_sign=false)
Return `num` in engineering notation where the exponent is a multiple of 3 and the
number before the decimal point ranges from 1 to 999.
# Arguments
- `num`: any subtype of `Number`. `Complex` subtypes are passed through unchanged.
Numbers greater than (in absolute value) `floatmax(Float64)`=1.7976931348623157e308
are passed through unchanged.
Numbers less than (in absolute value) `floatmin(Float64)`=2.2250738585072014e-308 and > 0.0
are passed through unchanged.
- `digits`: the number of digits to the right of the decimal point. `digits` is clipped from 0 to 13.
- `spec`: "E", 'E', "e", or 'e' sets case of the the exponent letter.
- `plus_sign`: when `true` includes a plus sign, "+", in front of numbers that are >= 0.0.
# Examples
```julia_repl
julia> const Avogadro = 6.022140857e23
6.022140857e23
julia> eng_notation(Avogadro, digits=0)
"602E+21"
julia> eng_notation(Avogadro, digits=1)
"602.2E+21"
julia> eng_notation(Avogadro)
"602.2141E+21"
julia> eng_notation(Avogadro, digits=10)
"602.2140857000E+21"
julia> eng_notation(-0.01234567, spec="e", digits=7)
"-12.3456700e-03"
julia> eng_notation(Avogadro, digits=13, plus_sign=true)
"+602.2140857000000E+21"
julia> eng_notation(floatmax(Float64), digits=13)
"179.7693134862316E+306"
julia> eng_notation(floatmin(Float64), digits=13)
"22.2507385850720E-309"
```
"""
function eng_notation(num::Number; digits=4, spec="E", plus_sign=false)
# Complex subtypes are just passed through unchanged.
if typeof(num) <: Complex; return num; end
# Values larger/smaller that Float64 limits just pass through unchanged.
if abs(num) > floatmax(Float64); return num; end # max=1.7976931348623157e308
if abs(num) < floatmin(Float64) && num != 0; return num; end # min=2.2250738585072014e-308
# Min of 0 and max of 13 digits after the decimal point (dp).
digits = digits < 0 ? 0 : digits
digits = digits > 13 ? 13 : digits
# Don't add a dp when 0 digits after dp.
dec_pt = digits == 0 ? "" : "."
spec_char = spec[1] == 'E' ? 'E' : 'e'
sign = ifelse(num < 0, "-", ifelse(plus_sign, "+", ""))
# This Julia code is modified from Java code at:
# http://www.labbookpages.co.uk/software/java/engNotation.html
# If the value is zero, then simply return 0 with the correct number of digits.
if num == 0; return string(sign, 0, dec_pt, "0"^digits, spec_char, "+00"); end
# If the value is negative, make it positive so the log10 works
pos_num = num < 0 ? -num : num
log10_num = log10(pos_num);
# Determine how many orders of 3 magnitudes the value is.
count = floor(log10_num/3);
# Scale num into the range 1 <= num < 1000.
val = num/10.0^(3count)
if digits == 0
val_int = Int(round(val, digits=0))
else
val_int = Int(trunc(val))
end
n_val_digits = length(string(val_int))
n_val_digits = ifelse(val_int < 0, n_val_digits-1, n_val_digits) # Account for - sign
# Determine fractional digits to requested number of digits.
# Use 15 below because 1 + 15 = 16, and 16 sigdigits is around the limit of Float64.
num_str = #sprintf "%+.15e" num
# Remove sign and decimal pt.
digits_str = replace(num_str[2:end], "." => "")
e_index = findlast("e", digits_str).start
# Remove exponent.
digits_str = digits_str[1:e_index-1]
# Jump over leading digits to get digits to right of dec pt.
frac_digits = digits_str[n_val_digits+1:end]
if digits == 0
frac_digits = ""
else
frac_digits = string(Int(round(parse(Int, frac_digits), sigdigits=digits)))
# Round may not give us digits zeros, so we just pad to the right.
frac_digits = rpad(frac_digits, digits, "0")
frac_digits = frac_digits[1:digits]
end
# Determine the scaled exponent and pad with zeros for small exponents.
exp = Int(3count)
exp_sign = exp >= 0 ? "+" : "-"
exp_digits = lpad(abs(exp), 2, "0")
return string(sign, abs(val_int), dec_pt, frac_digits, spec_char, exp_sign, exp_digits)
end # eng_notation()
Here are a few tests:
function test_eng_notation()
#testset "Test eng_notation() function" begin
Avogadro = 6.022140857e23
#test eng_notation(Avogadro, digits=0) == "602E+21"
#test eng_notation(Avogadro, digits=1) == "602.2E+21"
#test eng_notation(Avogadro) == "602.2141E+21"
#test eng_notation(Avogadro, digits=10) == "602.2140857000E+21"
#test eng_notation(-0.01234567, spec="e", digits=7) == "-12.3456700e-03"
#test eng_notation(Avogadro, digits=13, plus_sign=true) == "+602.2140857000000E+21"
#test eng_notation(floatmax(Float64), digits=13) == "179.7693134862316E+306"
#test eng_notation(floatmin(Float64), digits=13) == "22.2507385850720E-309"
end
return nothing
end
I was curious how quick and accurate, algorithm from Rosseta code ( https://rosettacode.org/wiki/Ackermann_function ) for (4,2) parameters, could be. But got StackOverflowError.
julia> using Memoize
#memoize ack3(m, n) =
m == 0 ? n + 1 :
n == 0 ? ack3(m-1, 1) :
ack3(m-1, ack3(m, n-1))
# WARNING! Next line has to calculate and print number with 19729 digits!
julia> ack3(4,2) # -> StackOverflowError
# has to be -> 2003529930406846464979072351560255750447825475569751419265016973710894059556311
# ...
# 4717124577965048175856395072895337539755822087777506072339445587895905719156733
EDIT:
Oscar Smith is right that trying ack3(4,2) is unrealistic. This is version translated from Rosseta's C++:
module Ackermann
function ackermann(m::UInt, n::UInt)
function ack(m::UInt, n::BigInt)
if m == 0
return n + 1
elseif m == 1
return n + 2
elseif m == 2
return 3 + 2 * n;
elseif m == 3
return 5 + 8 * (BigInt(2) ^ n - 1)
else
if n == 0
return ack(m - 1, BigInt(1))
else
return ack(m - 1, ack(m, n - 1))
end
end
end
return ack(m, BigInt(n))
end
end
julia> import Ackermann;Ackermann.ackermann(UInt(1),UInt(1));#time(a4_2 = Ackermann.ackermann(UInt(4),UInt(2)));t = "$a4_2"; println("len = $(length(t)) first_digits=$(t[1:20]) last digits=$(t[end-20:end])")
0.000041 seconds (57 allocations: 33.344 KiB)
len = 19729 first_digits=20035299304068464649 last digits=445587895905719156733
Julia itself does not have an internal limit to the stack size, but your operating system does. The exact limits here (and how to change them) will be system dependent. On my Mac (and I assume other POSIX-y systems), I can check and change the stack size of programs that get called by my shell with ulimit:
$ ulimit -s
8192
$ julia -q
julia> f(x) = x > 0 ? f(x-1) : 0 # a simpler recursive function
f (generic function with 1 method)
julia> f(523918)
0
julia> f(523919)
ERROR: StackOverflowError:
Stacktrace:
[1] f(::Int64) at ./REPL[1]:1 (repeats 80000 times)
$ ulimit -s 16384
$ julia -q
julia> f(x) = x > 0 ? f(x-1) : 0
f (generic function with 1 method)
julia> f(1048206)
0
julia> f(1048207)
ERROR: StackOverflowError:
Stacktrace:
[1] f(::Int64) at ./REPL[1]:1 (repeats 80000 times)
I believe the exact number of recursive calls that will fit on your stack will depend upon both your system and the complexity of the function itself (that is, how much each recursive call needs to store on the stack). This is the bare minimum. I have no idea how big you'd need to make the stack limit in order to compute that Ackermann function.
Note that I doubled the stack size and it more than doubled the number of recursive calls — this is because of a constant overhead:
julia> log2(523918)
18.998981503278365
julia> 2^19 - 523918
370
julia> log2(1048206)
19.99949084151746
julia> 2^20 - 1048206
370
Just fyi, even if you change the max recursion depth, you won't get the right answer as Julia uses 64 bit integers, so integer overflow with make stuff not work. To get the right answer, you will have to use big ints to have any hope. The next problem is that you probably don't want to memoize, as almost all of the computations are not repeated, and you will be computing the function more than 10^19729 different inputs, which you really do not want to store.
Is there a generic way to zero out small values in an array?
By "small" I mean elements whose absolute value is less than some threshold like 10.0^-5.
Edit: For now, I loop with eachindex.
function sparsify(a, eps)
for i in eachindex(a)
if abs(a[i]) < eps
a[i] = 0
end
end
end
Why not just apply a mask and an element-wise less than operator?
>>> x = rand(Float32, 100)
>>> eps = 0.5
>>> x[abs(x) .< eps] = 0
or as a function (note the function modifies the vector x inplace):
>>> sparsify!(x, eps) = x[abs(x) .< eps] = 0;
You could also replace 0 with zero(eltype(x)) to ensure it has the same type as x.
The temporary boolean mask created by x .< eps will compare every element of x to eps. Then, every element that satisfies that condition will be set to 0.
To complete answer of Imanol Luengo, and extend it to multiple dimensions,
x[abs.(x) .< eps(eltype(x))] .= zero(eltype(x))
I ended up with a vectorized method, which is much shorter.
sparsify(x, eps) = abs(x) < eps ? 0.0 : x
#vectorize_2arg Float64 sparsify
Disclaimer (2019): The below answer is badly out of date, and refers to an old version of Julia (<0.7). In version 1.x you should instead use x .= 0 or fill!(x, 0).
What approach to choose depends on what you need. If you just need a simple one-liner, then the vectorized version is fine. But if you want optimal performance, a loop will serve you better.
Here are a few alternatives compared by performance. Do keep in mind that map is slow on version 0.4. The timings here are done with version 0.5.
function zerofy!(x, vmin)
for (i, val) in enumerate(x)
if abs(val) < vmin
x[i] = zero(eltype(x))
end
end
end
zerofy2!(x, vmin) = ( x[abs(x) .< vmin] = zero(eltype(x)) )
zerofy3(x, eps) = abs(x) < eps ? 0.0 : x
#vectorize_2arg Float64 zerofy3!
zerofy4(y, vmin) = map(x -> abs(x)<vmin ? zero(x) : x, y)
zerofy4!(y, vmin) = map!(x -> abs(x)<vmin ? zero(x) : x, y)
function time_zerofy(n, vmin)
x1 = rand(n)
x2, x3, x4, x5 = copy(x1), copy(x1), copy(x1), copy(x1)
#time zerofy!(x1, vmin)
#time zerofy2!(x2, vmin)
#time zerofy3(x3, vmin)
#time zerofy4(x4, vmin)
#time zerofy4!(x5, vmin)
return nothing
end
julia> time_sparse(10^8, 0.1)
0.122510 seconds
1.078589 seconds (73.25 k allocations: 778.590 MB, 5.42% gc time)
0.558914 seconds (2 allocations: 762.940 MB)
0.688640 seconds (5 allocations: 762.940 MB)
0.243921 seconds
There's a pretty big difference between the loop (fastest) and the naively vectorized one.
Edit: zerofy3! => zerofy3 since it's not in-place.