Vectorization of Julia expressions involving both variables and arrays - julia

I have the following code that I'm using as part of an attempt to implement the most accurate equation of state for water in Julia 0.6.
struct parameterizedeos
Tc::Float64
ρc::Float64
R::Float64
#parameters for ideal gas portion
n₀::Vector{Float64}
γ₀::Vector{Float64}
end
h2o_n₀ = [-8.3204464837497, 6.6832105275932, 3.00632, 0.012436, 0.97315, 1.27950,
0.96956, 0.24873]
h2o_γ₀ = [1.28728967, 3.53734222, 7.74073708, 9.24437796, 27.5075105]
function Σ(expr)
return sum(eval(#. expr))
end
function ig(eos, δ, τ)
end_ = Σ(eos.n₀[4:8]*log(1-exp(-eos.γ₀)*τ))
return log(δ) + eos.n₀[1] + eos.n₀[2]*τ + eos.n₀[3]*log(τ) + end_
end
Tc = 647.096
ρc = 322
R = 0.46151805
eos = parameterizedeos(Tc,ρc,R,h2o_n₀,h2o_γ₀)
δ₁ = 838.025/ρc
τ₁ = Tc/500
print(ig(eos,δ₁,τ₁))
Σ is supposed to be a simple form of the corresponding operator from math, while δ and τ use the nomenclature from the linked reference (dimensionless density and temperature respectively). I get LoadError: DimensionMismatch("Cannot multiply two vectors").
I've played around with all sorts of subexamples in the Julia REPL and they all seem to work just as I'd expect. Σ(log(1-exp(-h2o_γ₀)*τ)) vectorizes and sums the elements as expected. Heck, eval(#. h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ) happily returns a 5-element vector. But calling Σ(h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ)) breaks.
I'm a noob at Julia and at arcane things like macros, so anyone could help me figure out what's going on here, that would be great.

Try this (using .* and log. and exp. instead)
function ig(eos, δ, τ)
end_ = Σ(eos.n₀[4:8] .* log.(1-exp.(-eos.γ₀)*τ))
return log(δ) + eos.n₀[1] + eos.n₀[2]*τ + eos.n₀[3]*log(τ) + end_
end

The error you get: DimensionMismatch("Cannot multiply two vectors") happens inside the function ig when eos.n₀[4:8]*log(1-exp(-eos.γ₀)*τ) is evaluated, before the result is supposed to be passed to Σ.
Furthermore: #. is expanded at parse time in global scope, it adds dots to function calls and operators. expr is just a variable, not an operator, not a function call, so therefore #. does nothing. eval also does nothing, so sum(eval(#. expr)) is identical to just plain sum(expr). But since the program fails before that point, this is not your problem.
(Edit: Actually eval does more than nothing. It does some work; the results are identical, but the extra work is completely wasted.)
Solution: Delete the function Σ, you don't need it. Re-write the function ig as follows:
function ig(eos, δ, τ)
end_ = sum(#. eos.n₀[4:8] * log(1 - exp(-eos.γ₀) * τ))
return log(δ) + eos.n₀[1] + eos.n₀[2]*τ + eos.n₀[3]*log(τ) + end_
end
Edit:
I've played around with all sorts of subexamples in the Julia REPL and
they all seem to work just as I'd expect. Σ(log(1-exp(-h2o_γ₀)*τ))
vectorizes and sums the elements as expected. Heck, eval(#. h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ) happily returns a 5-element vector.
But calling Σ(h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ)) breaks.
Everything here works as expected. log and exp vectorize automatically, for now, but that behaviour is deprecated. In version 1.0 calling log or exp (or sin or sqrt, etc.) on a vector will be an error.
eval(#. h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ) vectorizes this expression as expected. (But remove the eval, you're just calling it on a value, which it makes no sense to eval.)
And Σ(h2o_n₀[4:8]*log(1-exp(-h2o_γ₀)*τ)) fails because you're trying to multiply two vectors, something that has never worked (you can take the dot-product (using e.g. '* or dot), or do element-wise multiplication, though.) The fact that you later try to pass the result of this into Σis irrelevant.

Related

Expression manipulation for calculus in julia

I am currently trying some functionalities of Julia regarding symbolic expressions. Coming from Matlab I searched the documentation for symbolic something with little success until I found some info about the expr = :(<content>) notation.
I started with the declaration of my first function : fun1 = :(1-x) which works fine. However, I need to reuse my expression or manipulations of it afterwards.
After searching a bit, I still did not find a way to say e.g fun2 = -fun1. How does one manipulate expressions once they are declared?
EDIT My example statement being a bit restrictive, an additional case would be the construction of a array of expression using pre-declared expresions as in exprarray = [fun1 0 -2*fun2+3]
you can interpolate expressions with $:
julia> fun1 = :(1-x)
:(1 - x)
julia> fun2 = :(-$fun1)
:(-((1 - x)))
EDIT
The same works for the array :
julia> exprarray = :([$fun1 0 -2*$fun2+3])
:([1 - x 0 -2 * -((1 - x)) + 3])

Cumulative Integration Options With Julia

I have two 1-D arrays in which I would like to calculate the approximate cumulative integral of 1 array with respect to the scalar spacing specified by the 2nd array. MATLAB has a function called cumtrapz that handles this scenario. Is there something similar that I can try within Julia to accomplish the same thing?
The expected result is another 1-D array with the integral calculated for each element.
There is a numerical integration package for Julia (see the link) that defines cumul_integrate(X, Y) and uses the trapezoidal rule by default.
If this package didn't exist, though, you could easily write the function yourself and have a very efficient implementation out of the box because the loop does not come with a performance penalty.
Edit: Added an #assert to check matching vector dimensions and fixed a typo.
function cumtrapz(X::T, Y::T) where {T <: AbstractVector}
# Check matching vector length
#assert length(X) == length(Y)
# Initialize Output
out = similar(X)
out[1] = 0
# Iterate over arrays
for i in 2:length(X)
out[i] = out[i-1] + 0.5*(X[i] - X[i-1])*(Y[i] + Y[i-1])
end
# Return output
out
end

Accessing values in expression using a macro

I'm wondering whether it's possible to define a macro that can modify the values of an expression only if the values are of a specific type?
Here's a minimal example:
type Special
x::Int
end
f1(s, n::Special) = println("f1", s, n)
f2(s, n::Special) = println("f2", s, n)
x1 = Special(3)
x2 = Special(5)
expr = :(
f1("this is f1", x1),
f2("this is f2", x2)
)
Now a macro might be able to examine the values of the arguments to the functions, determine that x1 and x2 are of type Special, run some function to modify their values, say by changing 3 to 4 and 5 to 2 (it might involve comparing two values), then pass the expression back to the caller. The final result would be equivalent to calling:
f1("this is f1", 4)
f2("this is f2", 2)
I found that it's possible to access the values in a macro via:
eval(eval(filter(x -> typeof(eval(x)) == Special, expr.args[1].args))[1]).x
=> 3
but although this works it looks wrong, and I'm might either be doing it wrong or trying to do something too way out...
No, you should never try to check types or values inside macros. Using eval to figure out the type or value of something in a macro may work in very limited situations, but it'll break in almost every real use. Instead, just have the macro insert a call to a generic function — that's where Julia excels at picking apart types (as method dispatch) and values (within the method):
munge_special(x::Special) = Special(x.x + 42)
munge_special(x) = x
macro do_something_special(x)
return :(munge_special($(esc(x))))
end
julia> #do_something_special Special(2)
Special(44)
julia> #do_something_special 3
3

How can I interpret user input as a function in Julia?

I've been using the following function to take in user input for something I'm writing in Julia:
function input(prompt::AbstractString = "")
println(prompt * " ")
chomp(readline())
end
In my particular case, the input that I'm taking in is in the form of equations such as "y = x^2". After the input() function passes it to me as an ASCIIString, I then use the parse() function to convert it to an Expression:
:(y = x^2)
As an Expression, I can use the .args attribute to do things like counting the number of variables and returning the unique variables, all of which has worked fine. Now, I need to be able to evaluate the right side of the expression as the Function f(x) = x^2. To do so, I began writing the following function (which has some pretty major flaws):
function evalExpression()
L = [1,2,3,4]
equation = parse(input("Enter an equation"))
f = equation.args[2].args[2]
for i in L
x = i
value = eval(f)
println(value)
end
end
This function has two problems that I haven't been able to resolve. The first is that it gives me an UndefVarError for x when I try to run it right now; that's more or less expected. The second is that unless I knew that the user would input a function of only x, I would have no way of figuring out what the variables I needed to assign were. I wrote a recursive function that can take in an expression and return all its variables in the form of [:x, :y, etc.], but I cannot assign :x to a number to evaluate the function--I need to assign it just to x, and I cannot figure out how to access that. Is there anything that I can use to access the variables I need? Or a different approach I could take?
Thank you!
When I run the following:
function evalExpression()
L = [1,2,3,4]
equation = parse(input("Enter an equation"))
global x
for i in L
x = i
f = equation.args[2].args[2]
value = eval(f)
println(value)
end
end
and then putting y = x*x I get
evalExpression()
Enter an equation
y = x*x
1
2
3
4
What is missing, at least for x as a variable, is declaring it globally. When you eval parsed statements, these parsed statements only access global variables
So what you probably need to do after you've invented your recursive function to correctly fetch variables, is to create them globally. Maybe
eval(parse("$variable = 0"))
will do

Define Piecewise Functions in Julia

I have an application in which I need to define a piecewise function, IE, f(x) = g(x) for [x in some range], f(x)=h(x) for [x in some other range], ... etc.
Is there a nice way to do this in Julia? I'd rather not use if-else because it seems that I'd have to check every range for large values of x. The way that I was thinking was to construct an array of functions and an array of bounds/ranges, then when f(x) is called, do a binary search on the ranges to find the appropriate index and use the corresponding function (IE, h(x), g(x), etc.
It seems as though such a mathematically friendly language might have some functionality for this, but the documentation doesn't mention piecewise in this manner. Hopefully someone else has given this some thought, thanks!
with a Heaviside function you can do a interval function:
function heaviside(t)
0.5 * (sign(t) + 1)
end
and
function interval(t, a, b)
heaviside(t-a) - heaviside(t-b)
end
function piecewise(t)
sinc(t) .* interval(t,-3,3) + cos(t) .* interval(t, 4,7)
end
and I think it could also implement a subtype Interval, it would be much more elegant
I tried to implement a piecewise function for Julia, and this is the result:
function piecewise(x::Symbol,c::Expr,f::Expr)
n=length(f.args)
#assert n==length(c.args)
#assert c.head==:vect
#assert f.head==:vect
vf=Vector{Function}(n)
for i in 1:n
vf[i]=#eval $x->$(f.args[i])
end
return #eval ($x)->($(vf)[findfirst($c)])($x)
end
pf=piecewise(:x,:([x>0, x==0, x<0]),:([2*x,-1,-x]))
pf(1) # => 2
pf(-2) # => 2
pf(0) # => -1
Why not something like this?
function piecewise(x::Float64, breakpts::Vector{Float64}, f::Vector{Function})
#assert(issorted(breakpts))
#assert(length(breakpts) == length(f)+1)
b = searchsortedfirst(breakpts, x)
return f[b](x)
end
piecewise(X::Vector{Float64}, bpts, f) = [ piecewise(x,bpts,f) for x in X ]
Here you have a list of (sorted) breakpoints, and you can use the optimized searchsortedfirst to find the first breakpoint b greater than x. The edge case when no breakpoint is greater than x is also handled appropriately since length(breakpts)+1 is returned, so b is the correct index into the vector of functions f.

Resources