nsmith5/Maxima.jl

Documentation Feature Request: Add Examples of Feature Usages

Opened this issue · 13 comments

I'm very interested in using this project, because right now I have to manually transfer mathematically derived code from Maxima to my projects. That said, the closest thing to example code I can find is the runtests.jl file. The runtests.jl file has a lot of examples of sending expressions to Maxima and seeing if it returns a correct Maxima string or numeric value. What I'm not seeing is an example of this feature:

Basic translation of expressions between Maxima and Julia

Personally, I care less about translating a Julia expression into a Maxima one than the reverse. In fact, I would love to see an example of a Maxima expression transpiled into a Julia one that Julia can then compile (maybe after wrapping into a function).

Has this feature not been implemented (yet)? If it's not implemented yet, it would be a kludge but it may be easiest to use the Maxima f90 function and then transpile the Fortran. The replacement "**" -> "^" is easy, and the line continuation characters are kind of tricky (only thing I can think of would be to look for a set of lines separated by '$' and throw parentheses around them). Then there's the open question about whether to try to translation function names into Julia ones, or just assume that Julia will be able to make sense of all tokens in the context (I think that's what f90 does - it doesn't translate bessel_j to their Fortran equivalents, for example).

Regardless, a couple of tests for these features should probably be in the runtests.jl file.

That said, adding one or two examples of each major feature in a documentation section would be a great assistance in helping users adopt this package.

I'm not the maintainer of this project, but I did add some features about a month ago to increase the functionality of the translation of julia and maxima expresions as discussed in #20

What you can do is use the functions parse and unparse in src/mexpr.jl to convert julia expression objects into maxima expressions and the reverse. However, the entire maxima and julia languages are not supported. If there is a missing translation feature you need, you'd need to implement it yourself by adding the necessary control flow statements into the parse and unparse functionality.

Example:

julia> parse(m"sin(x+1)")
:(sin(x + 1))

julia> ans |> Maxima.unparse
1-element Array{String,1}:
 "sin((x + 1))"

Alternatively to Maxima.unparse, you can use MExpr to construct maxima expression objects, which actually wraps the unparse functionality.

Here is another example of what you can currently do:

julia> fun = Expr(:function,:(f(x)),:(y = x+1; y^2)) |> MExpr
 
                                                     2
                       f(x) := block([], y : x + 1, y )

julia> fun |> parse
:(function f(x)
        y = x + 1
        y ^ 2
    end)

Thank you for the examples, @chakravala. What I want to do deals with using Maxima to derive an array of rational expressions, and then turning those into an array of Julia functions, so I can probably figure out how to do what I need to do from what you've shown here.

When I implemented the quoteblock support, I made the MExpr objects encapsulate an array of strings to be able to represent multiple maxima expressions and then be able to reference them individually.

The julia to maxima translation is quite trivial to implement; however, the maxima to julia function translation implementation is more tricky and is only "a proof of concept" and not a fully thought out implementation that will work as expected in all situations. If you have nested blocks for example, the functionality may break down because I did not spend time to make that fully robust.

so it could definitely still use more improvement.

For my purposes taking the outputs of the Maxima functions "grind" or "string" and passing them through Julia's built in "parse" then "eval" would suffice. They're just rational functions.

Hi, yes this is in fact the reason that I made this package! My research has some nasty polynomial expansions and I use Maxima.jl as a sort of super powered meta-programming helper to write them out for me and then I compile them to Julia functions. Thanks for the heads up that the documentation on this process is a little sparse. I'll add some more soon! (Thesis has been hampering my contributions lately but its finally over!)

The array you're working with makes this somewhat spicier than the usual manipulations. What happens when you try the obvious parse(array_of_maxima_expressions)? If this doesn't work I think a for loop is probably the right approach (eg., parse the array elements one at a time and assign them to julia
array elements as you go)

@odysseus9672, note that the parse(::MExpr) function I was referring to is not the built-in julia function but the one from Maxima.jl that takes an MExpr object as input.

It's possible to add in a loop to deal with arrays of julia expressions, but it's not a feature as of now. Instead of an array of julia expression, I used the quoteblock feature. Julia expressions have a head and args, the args component is the array of julia expressions encapsulated in a :block.

julia> u = Expr(:block,:(y=x+1),:(y^2))
quote 
    y = x + 1
    y ^ 2
end

julia> u.args
2-element Array{Any,1}:
 :(y = x + 1)
 :(y ^ 2)    

julia> MExpr(u).str
2-element Array{String,1}:
 "y:x+1"
 "y^2"  

So the args array is what is converted into the array of maxima strings.

Also note that

julia> s = MExpr(u) |> string
"y:x+1; y^2"

julia> MExpr(s).str
1-element Array{String,1}:
 "y:x+1; y^2"

julia> split(MExpr(s)).str
2-element Array{String,1}:
 "y:x+1"
 " y^2" 

in other words, the string function converts the array of maxima expressions to a single string and the split function splits it back into an array.

Example of for loop I suggested:

# Define matrix of expressions on maxima side
mcall(m"g: matrix([x + x, sin(x)], [cos(x), x / (1 + x) ^ 2])") 

# Target matrix of functions on julia side
julia_array = Matrix{Function}(2, 2) 

for j in 1:2
    for i in 1:2
        expr = MExpr("g[$i, $j]") |> mcall |> parse
        julia_array[i, j] = eval(:(x -> $expr))
    end
end

julia_array[1, 1](2) # == 4
julia_array[1, 2](0) # == 1.0

This is a little hacky but it does the trick.

interesting!

Yeah we can override getindex for better maxima array support to make this a little prettier though.. I'll make an issue to that effect.

That for loop is exactly the sort of thing that would go well in an examples directory, like in the ArgParse prooject.