Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a formula as a function parameter in Julia

I am trying to create a function that would allow for changing a formula and coefficients in Julia. I am 80% sure I should be using anonymous functions for this?

This SO post using python is a more discrete example of what I am trying to accomplish (in particular the base python example by chepner, rather than using a library). Pass a formula as a function parameter in python

I also found this SO post using Julia that uses a type to store needed parameters and then pass them to a function. How to pass parameter list to a function in Julia

Using these as a base, this is what I have created so far:

   #Create composite type
   type Params
   formula
   b1::Float64
   b2::Float64
   end

   #create instance of type and load
    foo=Params((b1,b2,X)-> X^b1+X+b2,0.004,0.005)

   #create function
   function DoMath(X,p::Params)
   p.formula(X,varargs...) #??
   end 
  1. Am I on the right track as to how to structure this by using composite types and/or lambdas? I don't have any CS training and I am muddling my way through many concepts while trying to learn Julia.

  2. What is the correct syntax for a function that can allow a user to change the formula and any coeffs. for a given X? Ultimately, I am imagining something with functionality like:

    DoMath(4) #some default formula with changing X
    DoMath(4, X*b1 +X*b2) #change X and change formula 
    DoMath(4, (X,b1,b2,b3)->X*b1+X*b2+x*b3) # change x, change formula to   a 3 parameter function
    

Thank you

Update: I got it working by following @Chris's syntax. One thing I had to tinker with is using

   (p::Params)(x) = p.formula(x,p.b) #p.b, not just b otherwise error

and I had to wrap the 2.0 and 3.0 in an array before calling

   p = Params((x,b)->x*b[1]+b[2],[2.0,3.0])
like image 431
Casey Avatar asked Mar 25 '17 03:03

Casey


2 Answers

The idea is to build a callable type. A callable type is any type which has a "call". A function f is a callable type because you can call on it: f(x) for example. However, functions are not the only things that can act like functions. Indeed, in Julia, functions are basically callable types which <: Function.

So let's build one for your example. Make your type contain the data you want:

type Params
  b1::Float64
  b2::Float64
end

Now let's add a call to a Params. Let's say we want to do x*b1 + b2. We can make a call that does this by:

(p::Params)(x) = x*p.b1 + p.b2

Let's see how this works. Make a Params:

p = Params(2.0,3.0)

now we can calculate the formula by using its call:

p(4) # 4*2+3 = 11

Now see that p acts as a function which uses the internal data. This is it.

The rest is all building up from this same foundation. You need to respect the fact that Julia types are not dynamic. This is for good reasons. However, let's say you don't know how many b's you want. Then you can just have a field be an array of b's:

type Params
  b::Vector{Float64}
end
(p::Params)(x) = x*b[1] + b[2]

Now let's say you wanted to be able to modify the formula. Then you can have a formula field:

type Params
  formula
  b::Vector{Float64}
end

and make the call throw the values into that:

(p::Params)(x) = p.formula(x,b)

Now if a user did:

p = Params((x,b)->x*b[1]+b[2],2.0,3.0)

then, as before:

p(4) # 4*2+3 = 11

Yay it acts the same, and still uses internal data.

But since p is just any ol' type, we can modify the fields. So here we can modify:

p.formula = (x,b)-> x*b[1]+x*b[2]+b[3]
push!(p.b,2.0) # p.b == [2.0,3.0,2.0]

and call again, now using the updated fields:

p(4) # 4*2 + 4*3 + 2 = 22

Indeed, as @LyndonWhite pointed out, ParameterizedFunctions.jl implements something like this. The strategy for doing so is callable types.

Extra little detail

Some libraries were built (incorrectly) requiring that the user pass in a function. So here we have a p that "acts like a function", but some libraries won't take it.

However, there is a quick fix. Just make it <:Function. Example:

type Params <: Function
  b1::Float64
  b2::Float64
end

Now things which require a function will take your p as it is a <:Function. This is just one way to point out that in Julia, Function is an abstract type, and each function is just a callable type which subtypes Function.

like image 81
Chris Rackauckas Avatar answered Oct 21 '22 18:10

Chris Rackauckas


Here is a similar pattern that I currently use for dealing with these "fixed parameters" vs "changing parameters" problems. Fixed parameters are those do not often change when running a particular program(e.g. b1,b2,b3). Changing parameters are common variables(e.g. x) which almost keep changing between every function call. In many cases, using optional arguments or keyword arguments is enough to deal with the problem, but if we would like to change both function and its parameters simultaneously, this solution maybe not an ideal one. As the answer in this post suggested, a better way would be to create a type and use multiple dispatch. However, we also need to manually unpack the type in the function body. In fact, we can write a more generic function wapper using @generated macro:

abstract FormulaModel

immutable Foo{F<:Function} <: FormulaModel
    formula2fixedparams::F
    fixedParam1::Float64
    fixedParam2::Float64
end
Foo(f, b1=5., b2=10.) = Foo(f, b1, b2)
Foo() = Foo((x,b1,b2)->x^b1+x+b2)  # default formula

immutable Bar{F<:Function} <: FormulaModel
    formula3fixedparams::F
    fixed1::Float64
    fixed2::Float64
    fixed3::Float64
end
Bar(b1,b2,b3) = Bar((x,b1,b2,b3)->b1*x+b2*x+b3*x, b1, b2, b3)
Bar() = Bar(1.,2.,3.)

@generated function DoMath(model::FormulaModel, changingParams...)
    fixedParams = [:(getfield(model, $i)) for i = 2:nfields(model)]
    func = :(getfield(model, 1))
    # prepare some stuff
    quote
        # do something
        $func(changingParams..., $(fixedParams...))
        # do something else
    end
end

julia> DoMath(Foo(), 4)
1038.0

julia> DoMath(Foo((x,y,b1,b2)->(b1*x+b2*y)), 4, 10)
120.0

julia> DoMath(Bar(), 4)
24.0
like image 37
Gnimuc Avatar answered Oct 21 '22 20:10

Gnimuc