在Julia中将公式作为函数参数传递

Pass a formula as a function parameter in Julia

我正在尝试创建一个允许更改Julia中的公式和系数的函数。我80%确定我应该使用匿名函数吗?

这个使用python的SO帖子是我想要完成的一个更离散的例子(特别是chepner的基本python示例,而不是使用库)。在python中将公式作为函数参数传递

我还发现这个使用Julia的SO帖子,它使用一个类型来存储所需的参数,然后将它们传递给一个函数。如何将参数列表传递给Julia中的函数

使用这些作为基础,这是我到目前为止创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   #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
  • 我是否正确地通过使用复合类型和/或lambdas来构建它?我没有任何CS培训,在尝试学习Julia的过程中,我在弄清楚了许多概念。

  • 什么是允许用户更改公式和任何系数的函数的正确语法。对于给定的X?最终,我想象的功能如下:

    1
    2
    3
    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
  • 谢谢

    更新:
    我按照@Chris的语法开始工作。我不得不修补的一件事是使用

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

    我必须在调用之前将2.0和3.0包装在一个数组中

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


    我们的想法是建立一个可调用的类型。可调用类型是具有"调用"的任何类型。函数f是可调用类型,因为您可以调用它:例如f(x)。但是,函数并不是唯一可以像函数一样运行的东西。实际上,在Julia中,函数基本上是可调用类型<: Function

    所以让我们为你的例子构建一个。使您的类型包含您想要的数据:

    1
    2
    3
    4
    type Params
      b1::Float64
      b2::Float64
    end

    现在让我们添加对Params的调用。假设我们想要x*b1 + b2。我们可以通过以下方式拨打电话:

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

    让我们看看它是如何工作的。做一个参数:

    1
    p = Params(2.0,3.0)

    现在我们可以通过使用它的调用来计算公式:

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

    现在看到p充当使用内部数据的函数。就是这个。

    其余的都是从同一个基础建立起来的。您需要尊重Julia类型不是动态的事实。这是有充分理由的。但是,假设你不知道你想要多少b。然后你可以让一个字段是一个b的数组:

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

    现在让我们假设您希望能够修改公式。然后你可以有一个公式字段:

    1
    2
    3
    4
    type Params
      formula
      b::Vector{Float64}
    end

    并使调用将值抛出到:

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

    现在,如果用户做了:

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

    然后,和以前一样:

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

    它的行为相同,仍然使用内部数据。

    但由于p只是任何类型,我们可以修改字段。所以在这里我们可以修改:

    1
    2
    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]

    并再次调用,现在使用更新的字段:

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

    实际上,正如@LyndonWhite指出的那样,ParameterizedFunctions.jl实现了这样的东西。这样做的策略是可调用类型。

    额外的小细节

    构建(错误地)某些库需要用户传入函数。所以这里有一个p"就像一个函数",但有些库不会接受它。

    但是,有一个快速解决方案。只需将其<:Function。例:

    1
    2
    3
    4
    type Params <: Function
      b1::Float64
      b2::Float64
    end

    现在需要函数的东西将使用p,因为它是<:Function。这只是一种指出在Julia中的方法,Function是一个抽象类型,每个Function只是一个可调用的类型,其子类型为Function


    这是我目前用于处理这些"固定参数"与"更改参数"问题的类似模式。
    固定参数是在运行特定程序时经常不会改变的参数(例如b1b2b3)。更改参数是常见变量(例如x),它们在每个函数调用之间几乎不断变化。在许多情况下,使用可选参数或关键字参数足以解决问题,但如果我们想同时更改函数及其参数,则此解决方案可能不是理想的解决方案。正如本文中的答案所暗示的,更好的方法是创建一个类型并使用多个调度。但是,我们还需要手动unpack函数体中的类型。实际上,我们可以使用@generated宏编写更通用的函数wapper:

    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
    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