Converting a multivariate function to a single variate function

I want to convert a multivariate function into a single variable function. Aka to perform partial differentiation. While my attempt works but it is such an ugly kludge. Is there a better way of doing this?

func1(x,y) = 3.0*x + 7.0*y^2.0

function create1Dfunction(func::Function,vector::Vector,varnum::Int64)
    len = length(vector)
    if 1 <= varnum <= len
        codestring = "("
        for i in 1:len
            if i == varnum
                codestring = codestring * "x"
            else
                codestring = codestring * string(vector[i])
            end
            if i < len
                codestring = codestring * ", "
            end
        end
        codestring = codestring * ")"
        # println(codestring)
        subexpr = Meta.parse(codestring)
        expr = :( x -> $func($subexpr...) )
        # println(expr)
        local OneDimensionalFunc = eval(expr)
        return OneDimensionalFunc
    else
        println("Error, parameter number $(varnum) does not exists")
    end
end

# We want to convert func1(x,y) to g(x) = x -> func1(3.1,x)
println("func1(3.1,7.8) = ",func1(3.1,7.8))
g = create1Dfunction(func1,[3.1,7.8],2)
println("g(7.8) = ",g(7.8))

Output

Starting Julia...
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _  |  |
  | | |_| | | | (_| |  |  Version 1.0.3 (2018-12-18)
 _/ |\__ _|_|_|\__ _|  |  Official https://julialang.org/ release
|__/                   |

func1(3.1,7.8) = 435.18
g(7.8) = 435.18

How about just this:

julia> func1(x, y) = 3.0*x + 7.0*y^2.0
func1 (generic function with 1 method)

julia> g(x) = func1(3.1, x)
g (generic function with 1 method)

julia> func1(3.1, 7.8)
435.18

julia> g(7.8)
435.18

What if my function have 26 variables and I want to convert it to a single variable function on variable number 21? And fill the rest with the value [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] and I don’t want to do it by hand. And I don’t know the value of the vector of numbers to replace them with at compile time but only at runtime.

Ah, I see. Here’s an option without dynamic code generation:

oned(f, args, pos) = x -> f(ntuple(i -> i == pos ? x : args[i], length(args))...)

f(x, y) = 3.0*x + 7.0*y^2

g = oned(f, [3.1, 7.8], 2)

And now:

julia> f(3.1, 7.8)
435.18

julia> g(7.8)
435.18

Thank you very much. That worked and is a lot shorter

function oned(f::Function, args::Vector, pos::Int64)
    return x -> f(ntuple(i -> i == pos ? x : args[i], length(args))...)
end
h = oned(func1,[3.1,7.8],2)
println("h(7.8) = ",h(7.8))

Output

Starting Julia...
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _  |  |
  | | |_| | | | (_| |  |  Version 1.0.3 (2018-12-18)
 _/ |\__ _|_|_|\__ _|  |  Official https://julialang.org/ release
|__/                   |

func1(3.1,7.8) = 435.18
g(7.8) = 435.18
h(7.8) = 435.18

Please check my final version of the program

func1(x,y) = 3.0 * x    + 7.0 * y    ^ 2.0
func2(X)   = 3.0 * X[1] + 7.0 * X[2] ^ 2.0

function create1Dfunction(func::Function,args::Vector,pos::Int64;FuncType::String="MultiParameters")
    if 1 <= pos <= length(args)
        if FuncType == "MultiParameters"
            return x -> func( ntuple(i -> i == pos ? x : args[i], length(args))... )
        else
            return x -> func( ntuple(i -> i == pos ? x : args[i], length(args)) )
        end
    else
        println("Error! Parameter number $(pos) does not exists")
        return x -> Nothing
    end
end

# We want to convert func(x,y) to g(x) = x -> func1(3.1,x)
println("func1(3.1,7.8) = ",func1(3.1,7.8))
g = create1Dfunction(func1,[3.1,7.8],2)
println("g(7.8) = ",g(7.8))
h = create1Dfunction(func2,[3.1,7.8],2,FuncType="VectorParameters")
println("h(7.8) = ",h(7.8))

Output

func1(3.1,7.8) = 435.18
g(7.8) = 435.18
h(7.8) = 435.18

you could also splat the vector

f(a,b,c,d,e,f,g) = a+b+c+d+e+f+g
g(a) = f(a,[1,2,3,4,5,6]...)
g(3) #24

Another option could be to add a method to the function foo using keyword arguments with the same names:

foo(a,b,c,d,e,f,g) = a + b + c + d + e + f + g

foo(;a=1.0, b=2.0, c=3.0, d=4.0, e=5.0, f=6.0, g=7.0) = foo(a,b,c,d,e,f,g)

# Examples:
foo(c=-25)      # 0.0
foo(d=0)        # 24.0

My package CallableExpressions.jl supports constructing evaluatable expression, and partial evaluation of these expressions. Several other packages offer different approaches to solving similar problems. A PR by @MilesCranmer, solving this problem, was also recently merged, to be released with Julia v1.12:

This thread is ancient, but this should be done using tuples instead:

create1arg(f, args::Tuple, pos::Integer) = x -> f(Base.setindex(args, x, pos)...) 

And then distinguish the vector-arg version like this

create1arg(f, args::Vector, pos::Integer) = x -> f(setindex!(args, x, pos)) 

Or some non-mutating version of this.

Don’t splat vectors, btw.

Our grannies too. Can we continue to visit them, please.

Okay, but at some point it’s more for one’s own sake, than theirs.

Don’t splat vectors, btw.

Could you elaborate? Are there any drawbacks or hidden problems?

The size of a Vector is not part of the type, so the compiler doesn’t know which method of f to call. In principle, I guess (but don’t know), that when you have a literal vector like above ([1,2,3,4,5,6]) the length could be known, by constant propagation or something.

But in general, the compiler only sees f(::Int, (::Vector{Int})...) and that’s no good.

Splatting a tuple or a StaticArray, on the other hand, is fine, since then compiler knows the size: f(::Int, (::NTuple{5, Int})...) is just the same as f(::Int, ::Int, ::Int, ::Int, ::Int, ::Int). (Well, I mean in pseudocode, since it’s not actually valid syntax.)