A powerful graphing computer algebra system that is capable of performing algebraic simplifications, manipulations, and some calculus. (A Grapher is also included)
JAS is a multivariate computer algebra system built using Java. JAS handles the following senarios in both fraction and decimal mode:
- Commutative arithmetic & algebra
- Nested unary operation simplification
- Algebraic domain validation
- Trigonometric simplification
- Logarithmic exponential simplification
- Basic irrational/rational number arithmetic
- (NEW!) Some Calculus (I am currently working on it...)
- First derivative
- Logarithmic differentiation
- Implicit differentiation
- nth derivative
- Integration
**Note: the following section demonstrates the power of JAS, since not a lot of people are aware of its existence... for information about how to use it (or how it works), please scroll down and read from the "Simplification" section.
With Java Algebra System, you can easily compile algebraic expressions and perform operations on them programmatically.
Node o = Compiler.compile("(x+4)(3-x)*cos(a)+sin(a)(ln(x)^2+c)");
o.expand();
// 3*x*cos(a)+(-1)*x*x*cos(a)+3*4*cos(a)+(-1)*x*4*cos(a)+ln(x)^2*sin(a)+c*sin(a)
System.out.println(o.simplify());
// prints (-1)*x^2*cos(a)+12*cos(a)+cos(a)*x*(-1)+ln(x)^2*sin(a)+c*sin(a)
o.beautify();
// 12*cos(a)-cos(a)*x^2-x*cos(a)+ln(x)^2*sin(a)+c*sin(a)
Variable x = new Variable("x");
o.firstDerivative(x);
// ((1+0)*(3-x)+(0-1)*(x+4))*cos(a)+0*(-1)*sin(a)*(x+4)*(3-x)+0*cos(a)*(ln(x)^2+c)+(2*ln(x)^(2-1)*1*(1/x)+0)*sin(a)
o.firstDerivative(x).simplify();
// (3+(-1)*(4+2*x))*cos(a)+2*ln(x)*x^(-1)*sin(a)
o.firstDerivative(x).expand().simplify().beautify();
// x*cos(a)*(-2)+cos(a)*(-1)+2*ln(x)*sin(a)/x
Or, here comes the exciting part... use it like a calculator with CAS capabilities in a commandline environment. Here are some example inputs and outputs (to do so, run CasCmdline
):
Define a function with a single argument
INPUT: define('f',{x}, x^2/cos(x))
OUTPUT: 'Done.'
Call a function
INPUT: f(x+3)
OUTPUT: (x+3)^2*cos((x+3))^(-1)
INPUT: f(3)
OUTPUT: 9*cos(3)^(-1)
Convert exact value to decimal
INPUT: val(f(3))
OUTPUT: -9.090977993171945
INPUT: val(f(x))
OUTPUT: undef
Expand an expression
INPUT: expand((x+3)^2*cos((x+3))^(-1))
OUTPUT: 9*cos(3+x)^(-1)+cos(3+x)^(-1)*x^2+6*cos(3+x)^(-1)*x
INPUT: expand((x+3)(a^2+4/e+5)*log(x))
OUTPUT: 4*e^(-1)*x*log(x)+5*x*log(x)+12*log(x)*x^2+12*e^(-1)*log(x)+15*log(x)+4*log(x)*x^3
Evaluate an expression
INPUT: eval(9*cos(3+x)^(-1)+cos(3+x)^(-1)*x^2+6*cos(3+x)^(-1)*x, 3)
OUTPUT: 37.49334935742388
Define a variable
INPUT: store('a', 2x)
OUTPUT: 2*x
Delete a variable
INPUT: del_var('a')
OUTPUT: '[a:3]'
Define a constant
INPUT: define('b',2pi)
OUTPUT: b
Evaluate a constant
INPUT: val(b)
OUTPUT: 6.283185307179586
Define a function with multiple arguments
INPUT: define('g',{x,a,b},x + a + b^2)
OUTPUT: 'Done.'
Differentiate with respect to a variable
INPUT: derivative(log(ln(x)^2)*c^2+b,x)
OUTPUT: c^2*x^(-1)*2*ln(x)^(-1)*log(e)
INPUT: derivative(cos(2x^2),x)
OUTPUT: sin(2*x^2)*x*(-4)
Take the nth derivative
INPUT: derivative(sin(2*x^2)*x*(-4),x,3)
OUTPUT: (-48)*cos(2*x^2)+256*cos(2*x^2)*x^4+384*sin(2*x^2)*x^2
Define a list
INPUT: {3,4}
OUTPUT: {3,4}
Perform an operation on a list
INPUT: {3,4,x}+7
OUTPUT: {10,11,x+7}
INPUT: {3,4,5}+{1,4,5}
OUTPUT: {4,8,10}
INPUT: {3,4,5}+{2,3}
OUTPUT: list dimension mismatch
List as argument
INPUT: f({3,5,7})
OUTPUT: {9*cos(3)^(-1),25*cos(5)^(-1),49*cos(7)^(-1)}
Summation
INPUT: sum(x,a,{2,3},log(e))
OUTPUT: {x*3+2+log(e),x*3+3+log(e)}
Fraction/irrational number simplification
INPUT: 3/4*(453/645)
OUTPUT: (453/860)
INPUT: 3*2x^(2/3)/x^(3/4)*3^(1/3)*3^(1/3)
OUTPUT: 6*x^((-1/12))*3^(2/3)
For detailed documentation of the simplifiable expressions, please refer to simplifiable forms under the Simplification section.
The simplification algorithm is based on a composite binary tree, an original data structure.
e.g. BinOp would represent the expression (ln(3^x)*(3/4) + (5/e+2.5))
/ \ / \
BinOp BinOp ln(3^x) * (3/4) 5/e + 2.5
/ \ / \ / \ / \
UOp Frac BinOp Raw ln(3^x) (3/4) 5/e 2.5
| / \ | / \
BinOp Const Var 3^x 5 e
/ \ / \
Raw Var 3 x
An emnormous advantage that this data structure offers is that it simplifies the process of designing recursive algorithms. Takes the binary operation a*b+c
for example: the top level binary operation in this case would be the +
. The following code segment demonstrates how the recursive simplification works.
Binary mult = new Binary(new Variable("a"), "*", new Variable("a"));
Binary exp = new Binary(new Variable("a"), "^", new RawValue(2));
Binary add = new Binary(mult, "+", exp); // "a*a+a^2"
Call to add.simplify()
will subsequently invoke mult.simplify()
, which takes a*a
and returns a^2
which then produces the expression a^2+a^2
, which is then simplified to 2*a^2
by invocation of this.simplify()
.
add.copy().simplify(); // produces "2*a^2"
Fortunately, you don't have to create mathematical expressions using JAS by creating algebraic operations one at a time. That would be extremely painful, slow, and buggy. The JAS library does all the hard parts for you! The expression a*a+a^2
from the example above could also be created by using the compile(String exp)
of the Compiler
class.
Node op = Compiler.compile("a*a+a^2"); // constructs the binary operation tree.
op.copy().simplify(); // "2*a^2"
You can also set Mode.DEBUG = true
to see how it actually performs the interpretation. Compiler.compile("5+7-log<(11)>+e^2")
with debug on produces the following:
formatted input: 5+7-log(11)+e^2
exp: 11
func: 5+7-log<&0>+e^2
exp: 5+7-log<&0>+e^2
exp: &0
unary: 5+7-log<&0>+e^2
-> 5+7-#0+#1
-> #2-#0+#1
-> #3+#1
-> #4
output: 5+7-log(11)+e^2
To reduce the complexity of the simplification process, the algorithm will first attempt fundamental operations (arithmetic calculations of rational/irrational numbers, special cases like x^0
and 0/x
). Then, it converts the mathematic expression to additional only exponential form. For example:
Binary binOp = (Binary) Compiler.compile("x*(a-b)/(c+d)^3"); // "x*(a-b)/(c+d)^3"
binOp.toAdditionOnly(); // converts the expression to additional only form, "x*(a+(-1)*b)/(c+d)^3"
binOp.toExponentialForm(); // converts the expression to exponential form
System.out.println(binOp); // prints "x*(a+(-1)*b)*(c+d)^(-3)"
The expression x*(a+(-1)*b)*(c+d)^(-3)
doesn't look much better than x*(a-b)/(c+d)^3
, however, although it is considered "much simpler" by the computer as now it only needs to worry about handling +,^,*
instead of +,-,*,/,^
. However, to make it more readable to human eyes, it needs to be "beautified." You can do this by invoking Node::beautify
, which works by reversing the doings of negative exponentiation and addition. Consider the expression a/3*2.5/n*b^2.5*a/4
:
Node op = Compiler.compile("a/3*2.5/n*b^2.5*a/4");
System.out.println(op.copy().simplify()); // prints "a^2*n^(-1)*b^2.5*(5/24)"
System.out.println(op.copy().simplify().beautify()); // prints "a^2*b^2.5*5/(n*24)"
Rational/Irrational Numbers
3^(-3) -> (1/27)
(2/3)^(-1/3) -> (1/2)*3^(1/3)*2^(2/3)
3^(-1/3) -> (1/3)*3^(2/3)
(3/2)^(-2/3) -> (1/9)*4^(1/3)*9^(2/3)
7/8*2 -> (7/4)
2/5+3/7 -> (29/35)
2*5/7 -> (10/7)
3/4*(5/7) -> (15/28)
3.5/4.7^2 -> (350/2209)
Binary Operations
Original Exp | -> | Simplified | Original Exp | -> | Simplified | Original Exp | -> | Simplified | ||
---|---|---|---|---|---|---|---|---|---|---|
(a*b)^# | -> | a^#*b^# | a^b*a^c | -> | a^(b+c) | x*x | -> | x^x | ||
a*a^b | -> | a^(b+1) | x-x | -> | 0 | x/0 | -> | undef | ||
0^0 | -> | undef | x+x | -> | 2*x | 0/x | -> | 0 | ||
0*x | -> | 0 | x^0 | -> | 1 | 0^(-1) | -> | undef | ||
x*x^2 | -> | x^3 | a^b^c | -> | a^(b*c) | x/1 | -> | x | ||
0^x | -> | 0 | 1^x | -> | 1 | x^1 | -> | x | ||
(e^a)^ln(b) | -> | b^a | (10^n)^log(b) | -> | b^n | -> |
Unary Operations
Original Exp | -> | Simplified | Original Exp | -> | Simplified | Original Exp | -> | Simplified | ||
---|---|---|---|---|---|---|---|---|---|---|
ln(#^n) | -> | n*ln(#) | log(#^n) | -> | n*log(#) | ln(e^n) | -> | n | ||
sec(pi/2+pi*n) | -> | undef | cot(pi*n) | -> | undef | csc(pi*n) | -> | undef | ||
cos(acos(x)) | -> | x | sin(asin(x)) | -> | x | tan(atan(x)) | -> | x | ||
tan(pi/2+pi*n) | -> | undef | log(-#) | -> | undef | ln(-#) | -> | undef |
The JAS framework is by no means limited to standard mathematical operations. It is built to be extensible. I made it fairly easy to implement customized binary/unary operations. Just be aware that introducing custom operations would compromise CAS capabilities. (However it is possible to subclass Binary
and implement your own simplfication mechanism.) The following section demonstrates how to incorporate user-defined operations into the powerful JAS.
Binary has a private nested class BinaryOperator
that is invisible outside of the jas.core
package. It conforms to interface BinEvaluable
, which specifies only one method double eval(double a, double b)
. This enables it to take advantage of the lambda expression. If you are not already familiar with lambda, take a look here. To define a custom binary operation:
Binary.define("%", 2, (a, b) -> a % b); // defines the modular binary operation (which is nonstandard)
The first argument is the symbolic representation of binary operation. It can be any String
that contains a single symbol. The second argument is the priority of the operation. The priority defines the order of binary operations - it can either be either 1
,2
, or 3
with 3 being the highest. Addition and subtraction are of priority 1 (lowest), while multiplification and division are of priority 2 and exponentiation having priority 3 (highest). In the code segment above, the %
is defined to be having the same priority as *
and /
. The third argument is of type BinEvaluable
. You can do any operation with the left/right operand as long as a double is returned.
Compiler.compile("x % 3").eval(5); // 5 % 3 = 2, produces "2.0"
This operation type is what truly grants Java Algebra System's flexibility. A composite operation is an operation that takes in multiple operands/arguments. For example, sum(x*ln(a),x,7*3*5)
would be a valid composite operation. Similar to unary operation, binary operation, and constants, it is extensible. For more information about the extensibility of composite operation, please refer to Extensibility section. As another example, the first derivative could also be expressed as a composite operation in JAS -- derivative(cos(x),x)
-- taking the first derivative of cos(x)
with respect to x:
Compiler.compile("derivative(derivative(cos(x),x),x)") //returns an node defined as -cos(x), which is the second derivative of cos(x)
Similar to Binary
, Unary
has a private nested class Definitions
. Refer to Binary for how it works. Here's how to use it:
Unary.define("digits", x -> Integer.toString((int) x).length()); // an unary operation that calculates the numer of digits in the integer part of a number.
The first argument is the name of the unary operation, like log
, cos
, etc. The name could only contain letters [a-Z]
and must has more than 2 characters. Single letters are reserved for variable names. The second argument is of type Evaluable
, which must takes in a double and return a double.
Compiler.compile("digits(x)^2").eval(1234) // returns 16 since there are 4 digits in 1234 and 4^2 = 16.
Aside from declaring custom binary/unary operations, you can also define constants. Constants in JAS behave differently from what you would expect, however, and here's how it works. All of the constants are managed under the Constants
class, which contains 2 subclasses, Constant
and ComputedConst
with Constant
being a nested class that is a subclass of Variable
and ComputedConst
being a nested interface. The ComputedConst
interface declares a single method double compute()
and is utilized by Constant
to compute a value. Here is how it works in practice:
Constants.define("π", () -> Math.PI); // a constant having the value π. ("pi" is the default name for π in JAS)
Constants.define("seed", Math::random); // a "dynamic constant" that returns a random value between 0 and 1 when evaluated
System.out.println(Constants.valueOf("π")) // prints 3.14159265357659...
System.out.println(Compiler.compile("seed*2-1").val()) // prints a random number between -1 and 1.
Epic update to JGrapher - introducing multi-variable graphing capability! Type in x^a*cos(b*x)
and see what happens along the way! Play around with the sliders. Press [TAB]
to hide/unhide function input. Significant improvements to the CAS, though not as powerful, it is no longer considered experimental with bugs now gone and inheritance optimized. Completely original & intuitive way of performing algebra manipulations by an ORIGINAL composite tree structure that proved to be immensely powerful and ingenious.