Rand Stats

Math::Symbolic

github:raydiak

Math::Symbolic

This is a Raku symbolic math module. It parses, manipulates, evaluates, and outputs mathematical expressions and relations. This module is PRE-ALPHA quality.

Synopsis

Either of

symbolic --m=1 --b=0 'y=m*x+b' x

on the command line, or

say Math::Symbolic.new("y=m*x+b").isolate("x").evaluate(:m(1), :b(0));

in Raku code, will print

x=y

Usage

Command line

A basic command line program named 'symbolic' is provided, and may be installed in your PATH. It takes at least one positional argument: the relation or expression to work with. If a second positional is passed, it is the name of the variable to isolate in the given relation (non-relation expressions are unsupported for isolation). If "0" is passed instead of a variable name, the relation is arranged with 0 on the right side and terms grouped by variable (when possible) on the left side.

If any named args are passed, they are substituted into the expression for the variables they name. Each named argument's value is parsed as an expression itself, so it doesn't just have to be a numeric value to give the variable.

The resulting expression will be printed after applying all requested transformations, and attempting to simplify.

For development of Math::Symbolic itself, there is also a 'symbolic' bash script in the module's root directory. This will use the module and command line script in the wrapper's directory, instead of the installed versions. It also intentionally does not include blib, since precompilation isn't ordinarily done during development.

API

At the time of this writing, most of the API is too unstable to document yet. To minimize exposure to the internal chaos and to provide a starting point for thinking about what functionality needs to exist in a more formal future API, a minimal temporary public interface is implemented as a small collection of simple methods in the main Math::Symbolic class. Note that where the actual signatures differ from what is documented here, the undocumented differences are considered "private", and may not do what is expected.

.new(Str:D $expression)

Creates a new Math::Symbolic object, initialized with the tree resulting from a parse of $expression (which may also be a relation; currently only "=" equality is supported for relations).

.clone()

Returns a clone of the object with an independent copy of the tree structure. This is important because all manipulations (below) are done in place, and cloning avoids the parsing overhead of .new().

.isolate(Str:D $var)

Arranges a relation with $var on the left and everything else on the right. Or attempts to. It supports simple operation inversion when only one instance of a variable exists, as well as attempting to combine instances via distributive property and/or factoring of polynomial expressions, if necessary. Calling .isolate on a non-relation expression is not supported.

.evaluate(*%values)

Replaces all instances of variables named by the keys of the hash, with the expressions in the values of the hash, and simplifies the result as much as possible (see .simplify below). If the resulting expression has no variables, this means it can be fully evaluated down to a single value.

Note that fully evaluating an equation with valid values would result in something mostly unhelpful like "0=0" if the simplifier is smart enough. Though in the future, when such a relation can be evaluated for truth, that will become useful.

.simplify()

Makes a childish attempt to reduce the complexity of the expression by evaluating operations on constants, removing operations on identity values (and eventually other special cases like 0, inverse identity, etc). Also already does a very small number of rearrangements of combinations of operations, like transforming a+-b into a-b.

.simplify is sometimes called after certain manipulations like .isolate and .poly which might otherwise leave messy expressions e.g. operations with identity values and awkward forms of negation and inversion. It is also called at the end of the command line tool for output of the final result.

.poly(Str $var?)

Attempts to arrange the equation/expression in polynomial form. If $var is given, terms are grouped for that specific variable. Otherwise terms are grouped separately according to the set of all variables in a term. For example "x²+x²*y" will be unchanged by .poly(), but .poly('x') will rearrange it to something like "x²*(1+y)". Unlike the formal definition of a polynomial, this function may accept and return any expression for coefficients, and allows for exponents of any constant value.

If .poly is called on a relation, it is first arranged so that the right side is equal to zero, before grouping terms on the left. An attempt is made to guess which side should be subtracted from the other to avoid ending up with an excessive amount of negation.

.expression(Str:D $var)

Creates a new Math::Symbolic object from the expression on the right-hand side of a relation after isolating $var on the left. Note that unlike the above transformations, no changes are made to the original object.

.compile($positional = False, $defaults?)

Returns a Raku subroutine which is mathematically equivalent to the Math::Symbolic expression. Not all operations are currently supported. Compiling relations is also undefined behavior at this time.

All arguments are named by default. If $positional is True, all arguments are positional instead, sorted in default Raku sort order. If $positional is itself a Positional class then only the listed variables will be taken positionally, in the specified order.

All arguments are also required by default. If $defaults is an Associative object, it is taken as a map of variable names to default values, and the listed variables will be optional. If $defaults is any other defined value, that value is taken as the default for all arguments.

.routine($positional = False, $defaults?)

Identical to .compile (above), but returns the code as a string without compiling via EVAL, for instance to embed the code into another module or script.

.count()

Returns the number of nodes in the expression's tree. This could be useful to determine if an expression has been fully evaluated, or used as a crude complexity metric.

.dump_tree()

Prints out the tree structure of the expression. This really should return the string instead, and perhaps be renamed.

.Str()

Returns the expression re-assembled into a string by the same syntactic constructs which govern parsing. As with all Raku objects, this is also the method which gets called when the object is coerced to a string by other means, e.g. interpolation, context, or the ~ prefix. The .gist() method is also handled by this routine, for easy printing of a readable result.

Passing the result of .Str() back in to .new() should always yield a mathematically equivalent structure (exact representation may vary by some auto-simplification), giving the same type of round-trip characteristics to expressions that .raku() and EVAL() provide for Raku objects. This allows a user to, for instance, isolate a variable in an equation, then plug the result in to .evaluate() for that variable in a different equation, all with the simplicity of strings; no additional classes or APIs for the user to worry about (albeit at a steep performance penalty).

.Numeric()

Returns the expression coerced first to a string (see above), then to a number. This will fail if the expression hasn't already been evaluated/simplified (see further above) to a single constant value. As with all Raku objects, this is also the method which gets called when the object is coerced to a number by other means, e.g. context or the + prefix.

Syntax and Operations

All whitespace is optional. Implicit operations, e.g. multiplication by putting two variables in a row with no infix operator, are not supported, and likely never will be. It leads to far too many consequences, compromises, complexities and non-determinisms.

The available operations and syntax in order of precedence are currently:

BUGS

Many, in all likelihood. Please report them to raydiak@cyberuniverses.com. Patches graciously accepted.