Documentation
¶
Overview ¶
Because Go does not support operator overloading, most numerical libraries are messy to use. M-AST is an attempt to write a domain-specific language for math, so that code can be written in a more simple way.
If it helps, think regular expressions, but for algebra.
// with Mast mast.Eval("x = A' * b + c", &x, &A, &b, &c) // without Mast Transpose(&aT, &A) Times(&Atb, &At, &b) Plus(&x, &Atb, &c)
Parser ¶
Mast mostly exists to create parsers for math-like languages. To use it, first create a *mast.Parser object, configuring the various operations and their precidence.
// A Parser configures the given operators in terms of precedence. type Parser struct { // Define which operators appear in this language. These operators are // given in order of loosest to tightest (so addition/subtraction should // probably come before multiplication/division). Operators []Prec // Define which matching grouping operators are used. Groups []Group // If true, then "sin x" is legal and parses as "sin(x)" would. If false, // that is a syntax error. AdjacentIsApplication bool }
Then you invoke the (p *mast.Parser).Parse(string) (*mast.Equation, error) function.
// Parses a single equation in the given source. On success, e is a // parsed *Equation; iff not, error is non-nil and of type Unexpected{}. func (p Parser) Parse(source string) (e *Equation, error) {
Parser Example ¶
Suppose we want to make a basic calculator parser. First, define which operations and grouping operators we want to support:
integers := &mast.Parser{ // allow parentheses Groups: []Group{ {"(", ")"}, }, // add plus, minux, multiply, divide, and modulo Operators: []Prec{ {[]string{"+", "-"}, InfixLeft}, {[]string{"*", "/", "%"}, InfixLeft}, {[]string{"-", "+"}, Prefix}, }, }
To parse a string using this language, invoke (p *mast.Parser) Parse(string) (*mast.Equation, error) with the source code to evaluate. For example, if we run:
tree, err := integers.Parse("y = (-a) * b + c") fmt.Printf("tree: %v\n", tree)
then err will be nil and tree will be as follows:
tree := &Equation{ Left: &Var{"y"}, Right: &Binary{ Op: "+", Left: &Binary{ Op: "*", Left: &Unary{ Op: "-", Elem: &Var{"a"}, }, Right: &Var{"b"}, } Right: &Var{"c"} } }
By iterating over the tree, your DSL can evaluate the mathematical expression while maintaining type integrity.
Evaluator ¶
Mast includes a toy evaluator that handles matrices as [][]float64. To use it, invoke the Eval(code string, args ...interface{}) error function, passing pointers to the respective arguments.
// Evaluate the given expression with the given variables. Variables are // assigned left to right based on first usage. func Eval(code string, args ...interface{}) error {
Think %-arguments to fmt.Printf. To make setting up variables easier, arguments can be specified in three ways:
- a [][]float64 for an n x m matrix;
- a []float64 for an 1 x n column vector; or
- a float64, for a 1 x 1 scalar.
All other types panic.
Evaluator Example ¶
Suppose we want to compute a linear transform (multiplying a vector by a matrix, and adding a vector). First, we set up the variables to compute:
A := [][]float64{ []float64{1, 2}, } x := []float64{3, 4} // same as := [][]float64{[]float64{5}, []float64{6}} b := 5.0 // same as := [][]float64{[]float64{5.0}} y := 0
Once those are set up, the computation is fairly easy.
if err := mast.Eval("y = A * x + b", &y, &A, &x, &b); err != nil { handleError(err) return }
The result is then available in y.
Example (Evaluate) ¶
package main import ( "fmt" "github.com/fatlotus/mast" ) func main() { res := [][]float64{[]float64{0, 0}, []float64{0, 0}} b := [][]float64{[]float64{1, 2}, []float64{3, 4}} mast.MustEval("res = B * B'", &res, &b) fmt.Printf("res = %v\n", res) }
Output: res = [[5 11] [11 25]]
Example (Parse) ¶
package main import ( "fmt" "github.com/fatlotus/mast" ) func main() { tree, err := mast.PEMDAS.Parse("res = inv(B * B')") if err != nil { panic(err) } fmt.Printf("%s\n", tree) }
Output: res = (inv (B * (' B)))
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Eval ¶
Evaluate the given expression with the given variables. Variables are assigned left to right based on first usage.
Example ¶
Evaluate a simple linear equation, handling the error.
y := []float64{0, 0} A := [][]float64{[]float64{1, 2}, []float64{3, 4}} x := []float64{5, 6} b := []float64{7, 8} if err := Eval("y = A * x + b", &y, &A, &x, &b); err != nil { handleError(err) return } fmt.Printf("y = [%.2f %.2f]^T", y[0], y[1])
Output: y = [24.00 47.00]^T
func MustEval ¶
func MustEval(code string, args ...interface{})
Example ¶
Evaluate a simple linear equation, but panic if something goes wrong.
y := []float64{0, 0} A := [][]float64{[]float64{1, 2}, []float64{3, 4}} x := []float64{5, 6} b := []float64{7, 8} MustEval("y = A * x + b", &y, &A, &x, &b) fmt.Printf("y = [%.2f %.2f]^T", y[0], y[1])
Output: y = [24.00 47.00]^T
Types ¶
type Apply ¶
Represents function application, where an expression is invoked as an operator. Examples:
sin x == Apply{Var{"sin"}, Var{"x"}} inv(A + B) == Apply{Var{"inv"}, Binary{"+", Var{"A"}, Var{"B"}}}
type Binary ¶
A Binary operator is one with two operands. Examples:
a + b == Binary{"+", Var{"a"}, Var{"b"}} a * b + c == Binary{"+", Binary{"*", Var{"a"}, Var{"b"}}, Var{"c"}}
type Equation ¶
An equation is an assignment of one side to the other. The engine provided can only evaluate an equation with a single variable on the left, but more advanced algebra systems could go further. Example:
x = A\b == Equation{Var{"x"}, Binary{"\\", Var{"A"}, Var{"b"}}}
type Expr ¶
type Expr interface {
String() string
}
The Syntax tree returned by .Parse() is composed of Expr elements. Exprs are always of one of the following types:
Apply sin(t) Var x Unary -w Binary a + b
type OpType ¶
type OpType int
An OpType represents the direction the operator associates and where it goes relative to the operands.
type Parser ¶
type Parser struct { // Define which operators appear in this language. These operators are // given in order of loosest to tightest (so addition/subtraction should // probably come before multiplication/division). Operators []Prec // Define the invisible grouping operators. These are not part of the tree, // but do override order of operations [as in (a + b) * c]. Parens []Group // Define structural grouping operators. These behave like parens, except // they also also empty groups, and turn into Unary and Var nodes. // // Examples: // [] = Var{"[]"} // [x] = Unary{"[]", Var{"x"}} // [a, b] = Unary{"[]", Binary{",", "a", "b"}} Brackets []Group // If true, then "sin x" is legal and parses as "sin(x)" would. If false, // that is a syntax error. AdjacentIsApplication bool }
A Parser configures the given operators in terms of precedence.
var PEMDAS Parser = Parser{ Parens: []Group{ {"(", ")"}, {"[", "]"}, }, Brackets: []Group{ {"{", "}"}, }, Operators: []Prec{ {[]string{","}, InfixLeft}, {[]string{"+", "-"}, InfixLeft}, {[]string{"*", "/", "\\"}, InfixLeft}, {[]string{"^"}, InfixRight}, {[]string{"-", "+"}, Prefix}, {[]string{"'"}, Suffix}, }, AdjacentIsApplication: true, }
PEMDAS defines a typical multiply-first math language.
type Prec ¶
A Prec is a series of operators that share the same precedence, such as plus and minus. The Type represents the associativity and position of this operator.
type Unary ¶
A Unary operator is one with one operator. Examples:
-a == Unary{"-", Var{"a"}} A' == Unary{"'", Var{"A"}}
type Unexpected ¶
A parse error; all errors returned from .Parse are of this form. These indicate which token was found, and which tokens should have been provided.