Symbolic - symbolic manipulation package for Python
Author: Pearu Peterson <pearu.peterson@gmail.com>
Created: January 2007
Symbolic is a pure Python package that provides tools for performing symbolic manipulations with Symbolic objects such as symbols, rational numbers, functions, differentiation and integration operators, algebraic operations etc. The package provides a parser tool to create symbolic objects from a string with Python language like syntax.
Availability
Symbolic is currently available through contacting the author directly.
Getting started
Here is how to create a simple polynomial using the parser from Python session:
>>> from symbolic.api import *
>>> poly = Symbolic('2/3 + 3 * a + 1/4 * a ** 2')
>>> poly
Symbolic('2/3 + 3 * a + 1/4 * a ** 2')
Though the repr output of
poly looks like input, the object
poly is actually an
Add object containing
Number,
Symbol,
Mul, and
Power objects. To display the internal object hierarhy of a symbolic object one can either use
.torepr() method or set
Symbolic.interactive to
False value. For instance
>>> Symbolic.interactive = False
>>> poly
Add(Rational(2, 3), Mul(Integer(3), Symbol('a')), Mul(Rational(1, 4), Power(Symbol('a'), Integer(2))))
>>> Symbolic.interactive = True # restore default behavior of repr.
One can construct symbolic objects by performing operations with Python objects. But one must take into account that Python does not have rational numbers and integer division may not produce desired result when the division is not exact. The issue of creating rational number objects can be resolved by using
Rational class or Symbolic parser or by proper operations with symbolic objects. For example
>>> a = Symbol('a')
>>> poly2 = Rational(2,3) + 3 * a + a ** 2 / 4
>>> poly2
Symbolic('2/3 + 3 * a + 1/4 * a ** 2')
The poly and poly2 objects are equal indeed:
>>> poly-poly2
Symbolic('0')
Here follows some examples what manipulations can be done with symbolic objects:
>>> poly.diff('a') # differentation
Symbolic('3 + 1/2 * a')
>>> poly.integrate('a') # antiderivative
Symbolic('1/12 * a ** 3 + 2/3 * a + 3/2 * a ** 2')
>>> poly.integrate(Range('a',0,1)) # definite integration
Symbolic('9/4')
>>> poly + 3 * poly # arithmetic operations
Symbolic('8/3 + a ** 2 + 12 * a')
>>> (poly ** 2).expand() # expansion
Symbolic('4/9 + 4 * a + 1/16 * a ** 4 + 3/2 * a ** 3 + 28/3 * a ** 2')
>>> poly.substitute('a','(b+1)/3') # substitution
Symbolic('5/3 + b + 1/4 * (1/3 + 1/3 * b) ** 2')
>>> exp(poly) # elementary functions exp, ln, log, sin, cos etc.
Symbolic('exp(2/3 + 3 * a + 1/4 * a ** 2)')
>>> lhs = Symbolic('(a and b).implies(c)') # propositional calculus
>>> rhs = Symbolic('a.implies(b.implies(c))')
>>> lhs == rhs
Symbolic('(c | ~(a & b)) == (c | ~a | ~b)')
>>> lhs.equiv(rhs).expand()
Symbolic('TRUE')
Symbolic manipulation model
In doing symbolic manipulations there are two basic types of objects, tables summarize the corresponding Symbolic classes:
- objects that represent field values:
| Field, F | Class name | Subclasses | Singleton subclasses |
booleans, B | Boolean, Symbol | | TRUE, FALSE |
set of numbers, N | Number, Symbol | Decimal, Rational | |
set of rational numbers, Q | Rational, Symbol | Integer | Half, Infinity, NegativeInfinity, NaN |
set of integers, Z | Integer, Symbol | | Zero, One, NegativeOne |
set of real numbers, R | Decimal, Symbol | | Pi, Exp1 |
set of complex numbers, C | | | ImaginaryUnit, NegativeImaginaryUnit |
set of functions, NN | SymbolicFunction, ElementaryFunction, Symbol | | Ln, Log, Exp, Sin, Cos, Sqrt |
set of operators, FF ^ FF | SymbolicOperator, Symbol | SymbolicFunction, ElementaryFunction | Differential, Integral, SymbolicFunctionGenerator |
Notes:
- To model complex numbers a singleton ImaginaryUnit is introduced, for example, Symbolic('2/3 + 4 * I') is an object representing 3/4+4j.
- Singletons Zero, One, NegativeOne, Half are defined for efficiency.
- Singletons Infinity, NegativeInfinity, and NaN are represented as rational numbers 1/0, -1/0, 0/0, respectively.
- To model quaternions, one should introduce additional singletons ImaginaryUnit2 and ImarinaryUnit3.
TODO: define set of matrices, vectors, polynomials.
- objects that represent operations defining mapping between values of possibly different fields:
Operation | Class name | From field | To field |
arithmetic operations | Add, Mul, Power | N x N x .., N x N x .., N x N | N |
boolean operations | Not, And, XOr, Or | B, B x B x ..., B x B x .., B x B x ... | B |
relational operations | Equal, NotEqual, Less, Greater, LessEqual, GreaterEqual | N x N | B |
elementary functions | Exp, Sin, Cos, Sqrt | N | N |
operators | Differential, Integral | NN | NN |
applying functions | Apply | FF x F x F x ... | F |
applying operators
| Apply
| FF ^ FF x F |
|
Notes:
- all operations may return unevaluated mappings.
Symbolic parser
Symbolic provides a parser for symbolic expressions to ease creating symbolic objects. The syntax of symbolic expressions is borrowed from Python syntax rules with some extensions like parsing rational numbers. The syntax rules are the following:
<expr> = <or-test>
<or-test> = [ <or-test> or ] <xor-test>
<xor-test> = [ <xor-test> xor ] <and-test>
<and_test> = [ <and-test> and ] <not-test>
<not-test> = [ not ] <relational>
<relational> = [ <arith> <rel-op> ] <arith>
<rel-op> = == | <> | != | < | <= | > | >= | in | not in
<arith> = [ <arith> <add-op> ] <term>
| <factor>
<factor> = [ <add-op> ] <term>
<add-op> = + | -
<term> = [ <term> <mult-op> ] <power>
<mult-op> = * | /
<power> = <primary> [ <power-op> <power> ]
<power-op> = **
<primary> = <atom>
| <attr-ref>
| <slicing>
| <call>
<atom> = <identifier> | <literal> | <parenth>
<identifier> = <letter> [ <alphanumeric_character> ]...
<literal> = <int-literal>
| <float-literal>
| <logical-literal>
<logical-literal> = True | False
<parenth> = ( <expr-list> )
<attr-ref> = <primary> . <identifier>
<slicing> = <primary> [ <subscript-list> ]
<subscript> = <expr> | <slice>
<slice> = [ <expr> ] : [ <expr> ] [ : <expr> ]
<call> = <primary> ( [ <argument-list> ] )
<argument> = [ <identifier> = ] <expr>
For each rule the
symbolic.parser module provides a class to parse string containg an expression satisfying the particular syntax rule. For example, the most general parser class is
Expr:
>>> from symbolic.parser import *
>>> Expr('a+1')
Arith('a + 1')
>>> Expr('a+1').torepr()
"Arith(Identifier('a'), '+', Int_Literal('1'))"
>>> Expr('4/5').torepr()
"Term(Int_Literal('4'), '/', Int_Literal('5'))"
Other parser classes are:
Or_Test, XOr_Test, And_Test, Not_Test, Relational, Arith, Factor, Term, Power, Primary, Parenth, Identifier, Int_Literal, Float_Literal, Logical_Literal, Attr_Ref, Slicing, Subscript, Slice, Call, Argument. To translate parsed syntax tree to a symbolic object, use
.tosymbolic() method:
>>> import symbolic.api
>>> Expr('a+1').tosymbolic()
Symbolic('1 + a')
Symbolic classes and the Symbolic class
Symbolic objects are instances of Symbolic classes which all have a common base class
Symbolic:
symbolic_object = Symbolic('<string>') # parse '<string>' and construct the corresponding symbolic object
Symbolic objects are strictly ordered and hashable. The
Symbolic class defines default methods for addition, multiplication, power, relational, boolean, and functional operations:
Operations | Results |
+a, -a | a, Mul(-1, a) |
a ** b | Power(a, b) |
a * b, a / b | Mul(a, b), Mul(a, b ** -1) |
a + b, a - b | Add(a, b), Add(a, -b) |
a.ncmul(b), a.ncdiv(b) | NcMul(a, b), NcMul(a, b ** -1) |
a == b | Equal(a, b) |
a <> b, a != b | NotEqual(a, b) |
a < b, a > b | Less(a, b), Less(b, a) |
a <= b, b >= a | LessEqual(a, b), LessEqual(b, a) |
a(b, c, ...) | Apply(a, b, c, ...) |
~a, a & b, a | b | Not(a), And(a, b), Or(a, b) |
not a | bool(a.is_false()) |
cmp(a, b) | a.compare(b) |
In addition, the following methods are defined:
Method calls | Results | Comments |
a.is_equal(b) | True, False | formal equality, the correctness of the False value may depend on the contents |
a.is_positive(), a.is_negative(), a.is_true(), a.is_false() | True, False, None | value properties, None is returned when the result is undefined |
a.todecimal() | symbolic object | transform all number values in a to Decimal objects; use Symbolic.set_precision(prec) to set the precision to prec, default is 28. |
a.expand() | symbolic object | open parenthesis, expand integer powers, etc |
a.substitute(b,c) | symbolic object | replace all occurances of b with c in a |
a.symbols() | set object | find all Symbol objects in a |
a.free_symbols() | set object | find all Symbol objects in a that are "free", i.e. can be considered as parameters. For example, the integration variable in a definite integral is not free. |
a.diff(b) | Differential(b)(a) | apply differential operator with respect to b to a |
a.integrate(b) | Integral(b)(a) | apply indefinite integral operator with respect to b to a |
a.integrate(Range(b, c, d)) | Integral(b, c, d)(a) | apply definte integral operator with respect to b over the interval [c, d] to a |
Implementation notes - All Symbolic subclasses call Symbolic.__new__() method to initialize a symbolic object. The Symbolic.__new__() method carries out two tasks: First, it calls the .init() method of a Symbolic subclass with the same arguments used in constructing the symbolic object. The .init() method initializes symbolic object internal state. Second, the .flags attribute is set to hold an AttributeHolder object. The AttributeHolder object is used to save the results of .calc_*() methods that may take too long time to compute and/or when these results are often needed.
- A Symbolic subclass must define .astuple() method that returns a tuple with the first item equal to the name of a class followed by the arguments used in constructing the symbolic object.
- To make Symbolic subclasses available to all usage cases and avoiding various importing issues then Symbolic subclasses are set as the attrributes of the Symbolic class. For example, doing from symbolic.api import Symbolic is sufficient to have access to all Symbolic subclasses as follows: Symbolic.Symbol, Symbolic.Number, etc.
The Symbol class and the Symbolic_namespace dictionary
Symbol object represents an arbitrary symbol with a label:
symbol_obj = Symbol(label = None)
If
label is not specified then an unique label will be generated:
>>> a = Symbol('a')
>>> a
Symbolic('a')
>>> b = Symbol()
>>> b
Symbolic('Symbol_object_3')
Symbol objects are created within a namespace of symbols that is a dictionary named as
Symbolic_namespace. If there exists no
Symbolic_namespace variable in
locals() then it will be automatically created. Only those Symbol objects are saved in
Symbolic_namespace that have constructed with specified
label. For example,
>>> from symbolic.api import *
>>> Symbolic_namespace # assume that no symbolic objects has been created yet
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'Symbolic_namespace' is not defined
>>> a = Symbol('a')
>>> Symbolic_namespace
{'a': Symbolic('a')}
The idea of saving Symbol objects to
Symbolic_namespace is that when a symbolic expression contains symbols with the same label then in a created symbolic object these symbols refer to the same Symbol object:
>>> expr = Symbolic('1+a')
>>> expr.seq[0] is a
True
To perform symbolic manipulations isolated from global scope one must define
Symbolic_namespace object within the current scope explicitly. For example,
>>> def foo():
... b = Symbol('b')
... return Symbol('a')
...
>>> a = Symbol('a') # this will create a Symbolic_namespace dictonary
>>> Symbolic_namespace
{'a': Symbolic('a')}
>>> foo() is a
True
>>> Symbolic_namespace # note that the Symbol object b in foo() is saved in the parent Symbolic_namespace dictonary
{'a': Symbolic('a'), 'b': Symbolic('b')}
>>> def bar():
... Symbolic_namespace = {}
... c = Symbol('c')
... return Symbol('a')
...
>>> bar() is a
False
>>> Symbolic_namespace # note that the Symbol object c in bar() is saved in the local Symbolic_namespace dictonary
{'a': Symbolic('a'), 'b': Symbolic('b')}
>>> bar() == a # though the Symbol objects are different, they are considered equal if their labels are equal
True
Here function
foo() uses global
Symbolic_namespace ("global" in the sense that it is the first
Symbolic_namespace object found in parent scope of calling
foo()) while function
bar() defines isolated
Symbolic_namespace for holding local symbol objects.
The Number class and its subclasses
Number object represents an arbitrary number with the given value information:
number_obj = Number(numer, denom) # construct rational number, equivalent to Rational(numer, denom); numer, denom must by Python int or long
number_obj = Number(numer) # construct integer, equivalent to Integer(numer); numer must be Python int or long
number_obj = Number(num) # construct arbitrary precision decimal floating point number, equivalent to Decimal(num); num must be Python float, decimal.Decimal, or str
In addition to finite number values, concepts like infinity, not-a-number, imaginary-unit and few irrational number constants are defined:
infinity = Infinity() # represents Rational(1,0) == oo, available as infinity in symbolic.api namespace
neg_infinity = NegativeInfinity() # represents Rational(-1,0) == -oo
nan = NaN() # represents Rational(0, 0), available as NaN in symbolic.api namespace
I = ImaginaryUnit() # represents sqrt(-1), available as I in symbolic.api namespace
neg_I = NegativeImaginaryUnit() # represents -sqrt(-1)
E = Exp1() # represents exp(1), avaliable as E in symbolic.api namespace
pi = Pi() # represents pi, available as Pi in symbolic.api namespace
The number objects have additional methods:
Method call | Comment |
int(n) | return integer part of n |
float(n) | return Python float value of n |
abs(n) | return absolute value of n |
Examples
>>> n=Number(3,4)
>>> n
Symbolic('3/4')
>>> n + 1
Symbolic('7/4')
>>> n / 0
Symbolic('Inf')
>>> Number('1.2') + 1
Symbolic('2.2')
The default precision, 28, for
Decimal number operations is determined by the
decimal module context. The value of the precision can be obtained and reset with the S
ymbolic.set_precision(prec=None) function:
>>> Symbolic.set_precision() # the current precision
28
>>> 1/Decimal(3)
Symbolic('0.3333333333333333333333333333')
>>> prev_precision = Symbolic.set_precision(8) # decrease the precision
>>> 1/Decimal(3)
Symbolic('0.33333333')
>>> Symbolic.set_precision(prev_precision) # restore the original precision
8
Implementation notes - Numbers 0, 1, +1, 1/2 are represented by singleton number subclasses Zero, One, NegativeOne, Half, respectively.
- ImaginaryUnit, NegativeImaginaryUnit, Exp1, Pi are not subclasses of Number.
- The nominator and denominator of a rational number is automatically normalized.
- decimal.Decimal is used to represent decimal floating point numbers. For integers and rational numbers Python int is used. The internal values of Decimal and Rational numbers are available via attributes .num and .numer, .denum, respectively. Integer class is a subclass of Rational class.
- TODO: when gmpy module is avaliable then use its integer, rational, floating point numbers for efficiency.
Propositional classes
Symbolic has basic support for propositional calculus. The
truth and
false boolean values are represented with singleton constants
TRUE and
FALSE, respectively. The following table summaries the use of propositional operators and methods:
Operations | Result | Comments |
~a | Not(a) | Negation |
a & b | And(a, b) | Conjuction |
a ^ b | XOr(a, b) | Exlusive disjunction |
a | b | Or(a, b) | Disjunction |
a.implies(b) | Or(Not(a), b) | Implication |
a.equiv(b) | Not(XOr(a, b)) | Equivalence |
bool(TRUE), bool(FALSE) | True, False | conversion of boolean values to Python boolean values |
When using Symbolic parser then instead of symbols
~, |, ^, & one can use keywords
not, or, xor, and, respectively. However, these keywords cannot be used in a Python expression in which case they are interpreted differently.
Example: The Truth Table >>> st = lambda s: s.tostr()[0]
>>> expr_list = ['p', 'q', '~p', 'p & q', 'p ^ q', 'p | q', 'p.implies(q)', 'p.equiv(q)']
>>> table = [[s.center(min(max(len(s)+2,8),14)) for s in expr_list]]
>>> for p, q in zip([TRUE, TRUE, FALSE, FALSE],[TRUE, FALSE, TRUE, FALSE]):
... table.append([expr.substitute('p',p).substitute('q',q).tostr().center(len(label))
... for expr,label in zip(map(Symbolic, expr_list),table[0])])
>>> print 'n'.join([''.join(row) for row in table])
p q ~p p & q p ^ q p | q p.implies(q) p.equiv(q)
TRUE TRUE FALSE TRUE FALSE TRUE TRUE TRUE
TRUE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
FALSE TRUE TRUE FALSE TRUE TRUE TRUE FALSE
FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE
aadad