Using Serialization to store then load a lambdified function?

I have a lengthy symbolic expression that I find and lambdify in one function once. I hope to use this lambdified function repeatedly in a different function. It takes about a minute for lambdify to execute, so I do not want to have to solve for it repeatedly.

I am trying to lambdify and serialize the lambdified expression in one function (no issue there) and then deserialize it in another function. The function errors at the deserialize step: UndefVarError: ###274 not defined.

I have made a simple test setup for illustrative purposes.

Lambdify and serialize:

using Serialization
using SymPy

@syms a b c 
func = a^2 + b^3-c/a 
funky = lambdify(func,(a,b,c))
sol1 = funky(1,2,3)

fname = string("testSerial",".txt")
serialize(fname,funky)

Then, in a new instance of Julia

using Serialization
using SymPy
fname = "testSerial.txt"  
tfunc = deserialize(fname)

sol2 = tfunc(1,2,3)

which errors at the deserialize step.

Does anyone have any insights into this issue? Or a proposed better way to accomplish my goal?

Thank you.

The best solution could be to use Symbolics.jl instead.
The build_function is designed to make saving of generated functions easy. (And you could even generate C or Matlab source code…)

  • First step: Creating the expression and saving it as a .jl file:
using Symbolics
@variables a b c
func = a^2 + b^3-c/a

# create the function expression
f_expr = build_function(func, [a,b,c])

# Note: To use the function, one needs to apply eval:
f = eval(f_expr)
y = f([1,2,3])

# to save the function, use the expression!
write("func.jl", string(f_expr))
  • To load the function in a new session, you can simply include the Julia file
f = include("func.jl")
y = f([1,2,3])

A nice side effect is also that you don’t need any package to load the function
and you can even inspect the definition of the generated function in the file.

2 Likes

@SteffenPL Thank you! That is very helpful. However, do you happen to know if there is a way to convert SymPy variables/expressions to expressions accepted by Symbolics?

I have another function that builds up a SymPy expression using some of SymPy’s trig functions that I don’t think have an equivalent in Symbolics.jl. This expression is being ported into the (now) Symbolics function I have alluded to in this question. The build_function keeps SymPy variables as SymPy variables rather than converting them to Symbolics Num type.

I have done a search for both these functionalities and have not found anything.

Edit: Also, Symbolics seems to lack SymPy’s ability to convert an expression to a polynomial and see the terms (p.terms()) of that polynomial.

I’m not sure if there is a direct way.

One thing you could just try is to use:

using SymPy, Symbolics

@syms a b c 
func = a^2 + b^3-c/a 
funky = lambdify(func,(a,b,c))

@variables a b c 
func_symbolics = funky(a,b,c)

# ... and then proceed as posed with Symbolics...

(But it depends a bit on your particular example.)

I wouldn’t be surprised if there are ways do it with Symbolics and some related packages,
for example it should definitely be possible to make a polynomial.
Anyway, that’s in the end your decision. But feel free to post examples and we can see if it works.


By the way, feel free to uncheck my post as solution if you are looking for another one :wink:

1 Like

You can definitely make a polynomial. But SymPy let’s you append the handle for the polynomial with .terms() which yields a tuple containing, for each term, the coefficient and powers of each variable in that term.

I will try out that suggestion though!

Yes, I’m also a big fan of both Symbolics and SymPy :wink:

Just in case, there is this function:

using Symbolics
@variables x y
polynomial_coeffs(x+y+2x*y, [x,y]) 
# = (Dict{Any, Any}(y => 1, x => 1, x*y => 2), 0)
1 Like

Maybe convert to an Expr and save that. It can be evaled into a function after desrialization.

I know the topic has been marked as solved but I have found myself with a similar problem and I would like to comment my workaround.

The solution proposed herein does not work in my case since I want to include the symbolic computation inside a function (I know it is technically possible to make a include local with some macros, etc; but I think my solution is neater).

Actually, the computational cost is in the symbolic computation, so it is possible to serialize the symbolic function, and then later, deserialize and lambdify. In this way, you don’t need Symbolics package, just SymPy and Serialization.

Following, you can find the code to do so. You may adapt it as you wish, of course.

using SymPy
using Serialization
using Random

struct myVars
    x
end

function myVars(dim::Int)
    x = [SymPy.Sym(string("x",d)) for d in 1:dim]
    return myVars(x)
end

function doComputationsToExport()
    A = myVars(3)
    x = A.x
    func = x[1]^2 + x[2]^3-x[3]/x[1]
    funky = lambdify(func,[x...])
    myExport(func)
    return funky
end

function myExport(func)
    Serialization.serialize("testSerializeSymPy", func)
end

function doImports()
    A = myVars(3)
    x = A.x
    g = Serialization.deserialize("testSerializeSymPy")
    funky = lambdify(g,[x...])
    return funky
end

function main()
    Random.seed!(1234)
    rrr = [rand(), rand(), rand()]
    try
        funky = doImports()
        y = funky(rrr...)
        println("g(rrr) = ", y)
    catch
        funky = doComputationsToExport()
        y = funky(rrr...)
        println("f(rrr) = ", y)
    end
end

main()