1
"This file implements a class to represent a symbol."
3
__author__ = "Kristian B. Oelgaard (k.b.oelgaard@tudelft.nl)"
4
__date__ = "2009-07-12 -- 2009-08-08"
5
__copyright__ = "Copyright (C) 2009 Kristian B. Oelgaard"
6
__license__ = "GNU GPL version 3 or any later version"
9
from ffc.common.log import error
11
from symbolics import type_to_string, create_float, create_product, create_fraction
14
# TODO: This function is needed to avoid passing around the 'format', but could
15
# it be done differently?
16
def set_format(_format):
20
EPS = format["epsilon"]
23
__slots__ = ("v", "base_expr", "base_op")
24
def __init__(self, variable, symbol_type, base_expr=None, base_op=0):
25
"""Initialise a Symbols object, it derives from Expr and contains
26
the additional variables:
28
v - string, variable name
29
base_expr - Other expression type like 'x*y + z'
30
base_op - number of operations for the symbol itself if it's a math
31
operation like std::cos(.) -> base_op = 1.
32
NOTE: self._prec = 1."""
34
# Dummy value, a symbol is always one.
37
# Initialise variable, type and class.
42
# Needed for symbols like std::cos(x*y + z),
43
# where base_expr = x*y + z.
44
# ops = base_expr.ops() + base_ops = 2 + 1 = 3
45
self.base_expr = base_expr
46
self.base_op = base_op
48
# If type of the base_expr is lower than the given symbol_type change type.
49
# TODO: Should we raise an error here? Or simply require that one
50
# initalise the symbol by Symbol('std::cos(x*y)', (x*y).t, x*y, 1).
51
if base_expr and base_expr.t < self.t:
54
# Compute the representation now, such that we can use it directly
55
# in the __eq__ and __ne__ methods (improves performance a bit, but
56
# only when objects are cached).
58
self._repr = "Symbol('%s', %s, %s, %d)" % (self.v, type_to_string[self.t], self.base_expr._repr, self.base_op)
60
self._repr = "Symbol('%s', %s)" % (self.v, type_to_string[self.t])
62
# Use repr as hash value.
63
self._hash = hash(self._repr)
67
"Simple string representation which will appear in the generated code."
71
def __add__(self, other):
72
"Addition by other objects."
73
# NOTE: We expect expanded objects and we only expect to add equal
74
# symbols, if other is a product, try to let product handle the addition.
75
# TODO: Should we also support addition by other objects for generality?
76
# Returns x + x -> 2*x, x + 2*x -> 3*x.
77
if self._repr == other._repr:
78
return create_product([create_float(2), self])
79
elif other._prec == 2: # prod
80
return other.__add__(self)
81
error("Not implemented.")
83
def __mul__(self, other):
84
"Multiplication by other objects."
85
# NOTE: We assume expanded objects.
86
# If product will be zero.
87
if self.val == 0.0 or other.val == 0.0:
88
return create_float(0)
90
# If other is Sum or Fraction let them handle the multiply.
91
if other._prec in (3, 4): # sum or frac
92
return other.__mul__(self)
94
# If other is a float or symbol, create simple product.
95
if other._prec in (0, 1): # float or sym
96
return create_product([self, other])
98
# Else add variables from product.
99
return create_product([self] + other.vrs)
101
def __div__(self, other):
102
"Division by other objects."
103
# NOTE: We assume expanded objects.
104
# If division is illegal (this should definitely not happen).
106
error("Division by zero.")
108
# Return 1 if the two symbols are equal.
109
if self._repr == other._repr:
110
return create_float(1)
112
# If other is a Sum we can only return a fraction.
113
# TODO: Refine this later such that x / (x + x*y) -> 1 / (1 + y)?
114
if other._prec == 3: # sum
115
return create_fraction(self, other)
117
# Handle division by FloatValue, Symbol, Product and Fraction.
118
# Create numerator and list for denominator.
122
# Add floatvalue, symbol and products to the list of denominators.
123
if other._prec in (0, 1): # float or sym
125
elif other._prec == 2: # prod
126
# Need copies, so can't just do denom = other.vrs.
130
# TODO: Should we also support division by fraction for generality?
131
# It should not be needed by this module.
132
error("Did not expected to divide by fraction.")
134
# Remove one instance of self in numerator and denominator if
135
# present in denominator i.e., x/(x*y) --> 1/y.
140
# Loop entries in denominator and move float value to numerator.
142
# Add the inverse of a float to the numerator, remove it from
143
# the denominator and continue.
144
if d._prec == 0: # float
145
num.append(create_float(1.0/other.val))
149
# Create appropriate return value depending on remaining data.
150
# Can only be for x / (2*y*z) -> 0.5*x / (y*z).
152
num = create_product(num)
153
# x / (y*z) -> x/(y*z),
156
# else x / (x*y) -> 1/y.
158
num = create_float(1)
160
# If we have a long denominator, create product and fraction.
162
return create_fraction(num, create_product(denom))
163
# If we do have a denominator, but only one variable don't create a
164
# product, just return a fraction using the variable as denominator.
166
return create_fraction(num, denom[0])
167
# If we don't have any donominator left, return the numerator.
172
def get_unique_vars(self, var_type):
173
"Get unique variables (Symbols) as a set."
174
# Return self if type matches, also return base expression variables.
176
if self.t == var_type:
179
s.update(self.base_expr.get_unique_vars(var_type))
182
def get_var_occurrences(self):
183
"""Determine the number of times all variables occurs in the expression.
184
Returns a dictionary of variables and the number of times they occur."""
185
# There is only one symbol.
189
"Returning the number of floating point operation for symbol."
190
# Get base ops, typically 1 for sin() and then add the operations
191
# for the base (sin(2*x + 1)) --> 2 + 1.
193
return self.base_op + self.base_expr.ops()
196
from floatvalue import FloatValue
197
from product import Product
198
from sum_obj import Sum
199
from fraction import Fraction