nsmith5 / Maxima.jl

Symbolic Computations in Julia using Maxima
Other
44 stars 14 forks source link

Documentation Feature Request: Add Examples of Feature Usages #25

Open odysseus9672 opened 7 years ago

odysseus9672 commented 7 years ago

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.

chakravala commented 7 years ago

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.

chakravala commented 7 years ago

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.

chakravala commented 7 years ago

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)
odysseus9672 commented 7 years ago

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.

chakravala commented 7 years ago

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.

odysseus9672 commented 7 years ago

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.

nsmith5 commented 7 years ago

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)

chakravala commented 7 years ago

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

chakravala commented 7 years ago

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.

nsmith5 commented 7 years ago

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.

chakravala commented 7 years ago

interesting!

nsmith5 commented 7 years ago

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.

odysseus9672 commented 7 years ago

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