How to convert from a matrix of strings to a matrix of symbolic expressions in Julia

Viewed 73

I would like to convert from a matrix of strings to a matrix of symbolic expressions.

3×3 Matrix{String}:
 "1/a"       "1/b"            "12"
 "a"         "1/a + b*b + c"  "0"    
 "1"         "0"              "0" 

in Matlab, I would use

M=str2sym(T);
syms(symvar(M))
S = simplify(M);

but I can't find how to do it in Julia and Symbolics.jl

Please help J.

1 Answers

Unless there is some custom expression parsing specifically made for Symbolics, which I don't know about, you can use the builtins from Julia to just replicate what would happen if you were to enter those definitions "by hand" (the usual warnings about evaling arbitrary code from untrusted sources applies).

If you already have the symbolic variables lying around, you can use eval and parse directly:

julia> eval.(Meta.parse.(T))
2×2 Matrix{Real}:
 1 / a                1
    12  c + 1 / a + b^2

If you don't have to figure out the variables yourself, you can wrap the definition:

julia> function build_expr(vars, T)
           s = size(T, 2)
           exprs = Meta.parse.(T)
           return quote
               @variables $(vars...)
               Base.hvcat($s, $(exprs...))
           end
       end
build_expr (generic function with 1 method)

julia> build_expr((:a, :b, :c), T)
quote
    #= REPL[5]:5 =#
    #= REPL[5]:5 =# @variables a b c
    #= REPL[5]:6 =#
    Base.hvcat((2, 2), 1 / a, 1, 12, 1 / a + b * b + c)
end

If you do have to figure out the variables, too, it gets complicated. For some simplifying assumptions, this code could suffice:

function build_expr(T)
    s = size(T, 2)
    exprs = Meta.parse.(T)
    vars = mapreduce(get_free_vars, ∪, exprs)
    defs = Base.Generator(vars) do v
        :(@isdefined($v) ? nothing : @variables($v))
    end
    
    return quote
        $(defs...)
        Base.hvcat($s, $(exprs...))
    end
end

function get_free_vars(expr)
    vs = Set{Symbol}()
    MacroTools.postwalk(expr) do x
        if x isa Symbol
            push!(vs, s)
        end
    end

    return vs
end

It relies on figuring out all free variables (which includes known operators and functions), and testing them with @isdefined in the environment where you evaluate:

julia> build_expr(T)
quote
    #= REPL[44]:10 =#
    if #= REPL[44]:6 =# @isdefined(a)
        nothing
    else
        #= REPL[44]:6 =# @variables a
    end
    if #= REPL[44]:6 =# @isdefined(+)
        nothing
    else
        #= REPL[44]:6 =# @variables +
    end
    if #= REPL[44]:6 =# @isdefined(/)
        nothing
    else
        #= REPL[44]:6 =# @variables /
    end
    if #= REPL[44]:6 =# @isdefined(^)
        nothing
    else
        #= REPL[44]:6 =# @variables ^
    end
    if #= REPL[44]:6 =# @isdefined(b)
        nothing
    else
        #= REPL[44]:6 =# @variables b
    end
    if #= REPL[44]:6 =# @isdefined(c)
        nothing
    else
        #= REPL[44]:6 =# @variables c
    end
    #= REPL[44]:11 =#
    Base.hvcat(2, 1 / a, 12, 1, c + 1 / a + b ^ 2)
end
Related