Metaprogramming | String to Symbolics Num; eval(Meta.parse(. . .))

Hello,

Context. I am using SymPy.solveset(. . .) within a function to solve an equation. I first convert the Symbolics Num equation to a string, pass it to the function, and within the function use
SymPy.sympy.parse_expr(eqtn_str) to convert it a SymPy expression and finally solve it.

The test solution is retuned as
sltn = Set(Sym[-l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ))])

Extracted from the Set using first(sltn) the result is

(typeof(sltn_pyobject), sltn_pyobject) = (Sym{PyCall.PyObject}, -l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ)))

which is then converted to a string

(typeof(sltn_str), sltn_str) = (String, "-l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ))")

and returned from the function.

Issue. I would like to convert the returned string back to a Symbolics expression. Based on the discussion in

I attempted use eval(Meta.parse(sltn_str)), but encounter a metaprogramming error that I don’t understand.

ERROR: LoadError: UndefVarError: lnot defined inMain`

I suspect that it have something to do with scope, but not having used metaprogramming before, I’m at a bit of a loss on how to fix the issue.

Alternatively, if there is a better method to convert a SymPy PyObject expression to a Symbolics Num expression, I’d appreciate learning how to do so.

Below is a MNWE that reproduces the issue:

# test_eval_Meta_parse.jl

using Symbolics

function eval_String_to_Num(
            θ::Num,
            p::Num,
            t::Num,
            g::Num,
            l::Num,
            m::Num
        )
    # Specifying the variable here also does not work.
    # @variables θ, p, t
    # @variables g, l, m

    expr_str = "-l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ))"

    # Ref. https://discourse.julialang.org/t/how-to-convert-a-string-to-a-symbolic-expression/104527

    expr_parsed = Meta.parse(expr_str)
    @show expr_parsed
    println("")
    
    expr = eval(expr_parsed)

    @show expr
    println("")
end

function main()
    @variables θ, p, t
    @variables g, l, m

    eval_String_to_Num(
        θ,
        p,
        t,
        g,
        l,
        m
    )
end

begin
    main()
end

The full error message is as follows:

ERROR: LoadError: UndefVarError: `l` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ none:1
 [2] eval
   @ ./boot.jl:430 [inlined]
 [3] eval
   @ ./sysimg.jl:48 [inlined]
 [4] eval_String_to_Num()
   @ Main ~/projects/Hamiltonian/test/test_eval_Meta_parse.jl:26
 [5] main()
   @ Main ~/projects/Hamiltonian/test/test_eval_Meta_parse.jl:36
 [6] top-level scope
   @ ~/projects/Hamiltonian/test/test_eval_Meta_parse.jl:49
 [7] include(fname::String)
   @ Main ./sysimg.jl:38
 [8] top-level scope
   @ REPL[1]:1
in expression starting at /home/audrius/projects/Hamiltonian/test/test_eval_Meta_parse.jl:48

I’m not sure if it works, but this unexported feature is supposed to do this for simple cases:

1 Like

Thank you for the link to the SymPy ↔ Symbolics conversion code. Much appreciated.

The following MWE works

# test_SymPy_to_Symbolics_v2.jl

using SymPy
using Symbolics
using SymbolicUtils

global const T_smblc = SymbolicUtils.BasicSymbolic

using SymPyCore: exchange, _issymbol, _value, _makesymbol

function parse_String_to_SymPy()

    SymPy.@syms θ, p, g, l, m

    expr_str = "-l*(g*l*m*t*cos(θ) - l**2*m/t)/(g*t**2*sin(θ))"
    expr_smp = SymPy.sympy.parse_expr(expr_str)

    return expr_smp
end

function expr_SymPy_to_Symbolics()

    expr_smp = parse_String_to_SymPy()
    @show typeof(expr_smp), expr_smp
    println("")

    _issymbol(x::T_smblc) = SymbolicUtils.issym(x)
    _value(x::T_smblc) = x
    _makesymbol(::Type{<:T_smblc}, x::Symbol) = SymbolicUtils.Sym{Number}(x)

    expr_smblc_util = exchange(T_smblc, expr_smp)

    @show typeof(expr_smblc_util), expr_smblc_util
    println("")

    expr_smblc = Num(Symbolics.wrap(expr_smblc_util))

    @show typeof(expr_smblc), expr_smblc
    println("")
end

function main()
    expr_SymPy_to_Symbolics()
end

begin
    main()
end

returning

julia> include("test_SymPy_to_Symbolics_v2.jl")
(typeof(expr_smp), expr_smp) = (Sym{PyCall.PyObject}, -l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ)))

(typeof(expr_smblc_util), expr_smblc_util) = (SymbolicUtils.BasicSymbolic{Number}, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))

(typeof(expr_smblc), expr_smblc) = (Num, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))

Given that the Julia Metaprogramming documentation explicitly states that

eval and defining new macros should be typically used as a last resort. It is almost never a good idea to use Meta.parse or convert an arbitrary string into Julia code.

your link is the superior solution.

For completeness, a MWE that converts from Symbolics Num to SymPy Sym{PyCall.PyObject} and back to Symbolics Num:

# test_SymPy_to_Symbolics_v3.jl

# References 
# 1. https://github.com/jverzani/SymPyCore.jl/blob/main/src/lambdify.jl#L139

using Symbolics
using SymbolicUtils
using SymPy

using SymPyCore: exchange, _issymbol, _value, _makesymbol

global const T_smp = SymPy.Sym
global const T_smblc = SymbolicUtils.BasicSymbolic

function expr_Symbolics_to_SymPy_to_Symbolics(expr_num::Num)::Num

    expr_smblc_utls = Symbolics.unwrap(expr_num)

    @show typeof(expr_smblc_utls), expr_smblc_utls
    println("")

    SymPy.@syms θ, p, t, g, l, m
    expr_smp = exchange(T_smp, expr_smblc_utls)

    @show typeof(expr_smp), expr_smp
    println("")

    _issymbol(x::T_smblc) = SymbolicUtils.issym(x)
    _value(x::T_smblc) = x
    _makesymbol(::Type{<:T_smblc}, x::Symbol) = SymbolicUtils.Sym{Number}(x)

    expr_smblc_util = exchange(T_smblc, expr_smp)

    @show typeof(expr_smblc_util), expr_smblc_util
    println("")

    expr_smblc = Num(Symbolics.wrap(expr_smblc_util))

    return expr_smblc
end

function main()

    @variables θ, p, t, g, l, m

    expr_num = -l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ))

    @show typeof(expr_num), expr_num
    println("")

    expr_smblc = expr_Symbolics_to_SymPy_to_Symbolics(expr_num)

    @show typeof(expr_smblc), expr_smblc
    println("")
end

begin
    main()
end

returning

julia> include("test_SymPy_to_Symbolics_v3.jl")
(typeof(expr_num), expr_num) = (Num, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))

(typeof(expr_smblc_utls), expr_smblc_utls) = (SymbolicUtils.BasicSymbolic{Real}, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))

(typeof(expr_smp), expr_smp) = (Sym{PyCall.PyObject}, -l*(g*l*m*t*cos(θ) - l^2*m/t)/(g*t^2*sin(θ)))

(typeof(expr_smblc_util), expr_smblc_util) = (SymbolicUtils.BasicSymbolic{Number}, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))

(typeof(expr_smblc), expr_smblc) = (Num, (-((-(l^2)*m) / t + g*l*m*t*cos(θ))*l) / (g*(t^2)*sin(θ)))