Extending the CAS

General Discussions about ND1

Extending the CAS

Postby oliver » Sun Apr 15, 2012 4:02 pm

UPDATE: This thread has been superseded by http://forums.naivedesign.com/viewtopic.php?f=11&t=673. Please refer to this for information on how to extend the CAS. You can still get some insight from this thread from the discussion starting with the second post.

ND1's CAS works through WolframAlpha™ queries. WolframAlpha is powered by Mathematica™ and can be used to obtain math answers, and a whole lot more.

If you're familiar with using wolframalpha.com, you can easily extend ND1. You do this by writing wrapper functions like this:
Code: Select all
   ME.expr["simplify"] = function(x) { return calculator.callWA("simplify", x); };

(As explained in the JavaScript API document, ME stands for MorphEngine, ND1's and CalcPad's calculator engine, and is an alias for calculator.functions. This is a JS object which has various sub-objects, each representing a "function collection". ME.expr is the collection of functions that take expressions as arguments. This will be the most usual case for CAS functions.)

These have to be "injected" into the engine to become part of it. Once part of the engine, you can use them from any folder, on the edit-line, in expressions, in RPL, in JavaScript.

There's two ways to do the injection:
Manually: Enter code like this on the Definition | Injection page (enable JS Injections in the Settings app under ND1, to see this entry on the Definition page).
Automatically: create a Code object (JavaScript code starting with /**/) and use the inject command.

(More details about this can be found in the JavaScript API documentation. Extensions like BigInt and Constants use this and can serve as examples. If you're a beta-tester you will receive a folder prepared for this.)

The callWA() function takes up to four arguments.
1st arg: the W|A command [string]
2nd arg: the W|A arg [any object (currently recognized: Expression, Real, String)]
3rd arg: id of the output pod [string]
4th arg: an arg suffix [string]

The first two args are simply what you would type on the W|A command-line. The arg is broken out as an extra arg because it can be another type than string (it usually is an Expression, which is a string in single-quotes).
Specifically for expressions, callWA() will transform the 2nd arg from a ND1 expression (something like 'ln(x)') into a suitable W|A arg (something like log(x)). Also, on the way back from W|A, it will translate something like this b log(a), into something like this 'b*ln(a)'.

The output pod id is the name of a certain field in the result from W|A. This is a detail from the W|A query API.
It's actually quite simple.
Go to http://products.wolframalpha.com/api/explorer.html and see what the W|A API is actually returning for a given query.

Type 3+4 and submit.
Note, the result, 7, appears in a plaintext tag under a pod with id Result.

Result is the usual pod name for results, and is the implied default if you don't specify the 3rd arg in a call to callWA().

Type integrate x^2 and submit.
Now the result appears in a pod with id IndefiniteIntegral. The result in plaintext is "integral x^2 dx = x^3/3+constant". callWA() will filter this to 'x^3/3'.

The pod IDs are not documented as part of the W|A API and are a bit arbitrary. So, as a general rule: use the API explorer to figure out the pod id. In 80% of all cases, the pod id is Result and you don't have to worry about the 3rd arg.

Here's the implementation for integral (INTVX):
Code: Select all
   ME.expr["integral"] = function(x) { return calculator.callWA("integral", x, "IndefiniteIntegral"); };

(If you want a Classic name for one of your implementations, have something like that as part of your injection:
Code: Select all
   calculator.function_aliases["INTVX"] = "integral";
)
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm

A universal query command

Postby oliver » Sun Apr 15, 2012 4:09 pm

Yes, you could callWA() to just pass any kind of query.
While easy to implement
Code: Select all
   ME.string["evalWA"] = function(x) { return calculator.callWA("", x); };

this would work for some inputs, but not for others. As mentioned above, expressions need to be translated/transformed; and, sometimes, an output pod id has to specified.

For example, "D[x^2,x]" evalWA is a case where this (subtly) breaks. W|A would return 2 x. Notice the missing * and the missing single quotes. To be a valid algebraic expression in ND1, it needs to be '2*x'. Similarly, some function names don't match. ND1 uses "ln()" while W|A uses "log()" and Mathematica uses "Log[]". These are automatically handled correctly when you use the callWA() function as intended, where the 1st arg is the command name, and the second the arg.

EDIT: Thinking this through a bit more, the problems mentioned above can be solved. The callWA() can be made smarter to automatically find the most applicable pod. (This is currently not done alone for efficiency reasons. Different from the API Explorer, the query by ND1 specifies which output pod should be returned. This minimizes the volume of data transferred.) I will enhance this function with an "all" option for the 3rd parameter to permit this. This will solve the output pod problem (at the acceptable expense of more data moved).

If this completely universal query command is restricted to use W|A / Mathematica expressions (as suggested by alm) and the result is known to be a W|A algebraic expression, then the problem with translations can be solved too by calling the appropriate translation function.
The implementation becomes
Code: Select all
   ME.string["evalWA"] = function(x) { return calculator.WAexpressionToExpression(calculator.callWA("", x, "all")); };

This command will take a W|A / Mathematica expression as a string, run it through W|A, and return an expression.
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm

Using Mathematica syntax

Postby oliver » Sun Apr 15, 2012 4:21 pm

It is perfectly possible to do raw queries, operate on other types than Expression or String, and also to use Mathematica™ syntax in queries.

Examples:
Code: Select all
   ME["nCDFi_lo"] = function(x) { return parseFloat(calculator.callWA("", "InverseCDF[NormalDistribution[0, 1], " + Number(x).toPrecision(15) + "]")); };
   ME["li"] = function(x) { return parseFloat(calculator.callWA("", "Li[" + Number(x).toPrecision(15) + "]")); };


Note
    Mathematica syntax with brackets is used
    the entire query appears as 2nd arg. This is required to obtain proper percent encoding of the query string
    the functions are injected into ME and not ME.expr or ME.string, as in the previous examples. That's because these function will take a Real, and not an Expression or String
    .toPrecision() is used to feed W|A a longer Real string and thereby coax it to provide a higher-precision result
    parseFloat() is used to custom-filter the W|A string result into the intended JavaScript type

The plaintext W|A result in these examples actually looks like this 0.12~~0.12 or 3.6543.... The function parseFloat() happens to ignore the "~~" or "..." parts, and do the right thing. If you do raw queries (where you don't use the 2nd arg to callWA() to strictly pass one arg, but an entire query string), you will have to do your own transforming/filtering of the plaintext result string.

To clarify: If you extend the CAS, write one wrapper per command, and the 2nd arg is a single arg (usually an expression), you don't have to worry about transforming or filtering, and in most cases all you need to do is change the 1st arg, the command name.
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm

Re: Extending the CAS

Postby alm » Tue Apr 17, 2012 2:32 pm

So if I understand this correctly, you're parsing the W|A results to make them compatible with the ND1 syntax. Why is this parsing disabled with raw queries, is it because calculator.callWA() inspects the first argument to figure out the return type to expect, i.e. is it an algebraic/numeric expression or something entirely different like an image? Would it be possible to pass the type of output to CallWA, something like:
Code: Select all
calculator.callWA("", "D[" + calculator.parseForWA("x*ln(x)-x") + ",x]", "algebraic")

Which should return ln(x).

What does the W|A -> ND1 translation code do with functions that don't exist in ND1, for example:
Code: Select all
Integrate[cos[2t-a*sin[t]],t,0,Pi]

This should return the BesselJ function (although W|A seems to run out of time before coming up with a solution). As far as I know ND1 doesn't implement a BesselJ function (otherwise I'd just have to pick some other special function). Would it be returned as a W|A expression that could be evaluated, algebraically or numerically, at some later point using W|A? Or would it return an error because the result can not be represented by ND1? Is the purpose to implement all special functions supported by W|A at some point, so all expressions can be translated?

The reason why I consider raw queries directly from the stack an important feature is that it is necessary to exploit the full power of the Mathematica kernel used by W|A beyond a more basic CAS like xcas. If I encounter an integral that could by solved with a Bessel function, I might want to do some calculations with this function. I don't feel like writing a JS function just to use a W|A function. For someone used to Mathematica, being able to type Mathematica expressions directly into ND1 would be quite useful.

The most optimal solution in my opinion would be to have a 'W|A string' data type. You could feed an ND1 expression to callWA, it should convert it to a W|A string. The function would return a W|A string (or W|A image or whatever), which could be converted to an ND1 expression / number if possible, or left as W|A string..

Feel free to ignore this if you're just trying to achieve feature parity with the HP-50g. I'm probably not going to use ND1 CAS anyway, so it's not like you'd be losing a customer, but I still want it to be as good as possible. I don't really care about the HP-50g. I think it's a great calculator for numeric work, but I feel that Mathematica is better at symbolic work. I share your opinion from the other thread that it's probably not coming to mobile devices anytime soon, and that it may not be very suitable for this, however. At least the 'offline' version.
alm
 
Posts: 12
Joined: Sat Jul 16, 2011 12:26 pm

Re: Extending the CAS

Postby oliver » Tue Apr 17, 2012 10:11 pm

Hi alm,

callWA() will leave unknown function names alone. That is, something like a unknown special function will be preserved and you will be able to use it with another W|A function.
The return argument isn't really figured from the W|A arg, except the case of an expression will look to see if the result looks like an expression and act accordingly.

So, yes, you will be able to do what you suggest.

BesselJ isn't a built-in function but available through the Special Functions downloadable folder, by the way.

Cheers.
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm

Re: Extending the CAS

Postby oliver » Wed May 02, 2012 10:44 am

Hi alm,

Your suggestion of a W|A string type to allow raw queries, untranslated results, and re-use of results as input to the next query, is a good one. It doesn't even require a new data type. I will provide a evalWA()-type function that takes a normal ND1 string. It will be passed to W|A and returned untranslated as an ND1 string, ready to be used as input for the next query.

This is simple, and allows users who are familiar with W|A syntax to use W|A natively without having to write wrapper functions.

Thank you for suggesting this!

Oliver
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm

Re: Extending the CAS

Postby alm » Fri May 04, 2012 4:26 pm

I'm glad that my feedback was useful to you, and that you're open to suggestions.
alm
 
Posts: 12
Joined: Sat Jul 16, 2011 12:26 pm


Return to General

Who is online

Users browsing this forum: No registered users and 1 guest

cron