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

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

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

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.

2 Likes

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

1 Like

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

1 Like

Donā€™t splat vectors, btw.

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

1 Like

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

1 Like