/usr/lib/python2.7/dist-packages/ffc/quadrature/product.py is in python-ffc 1.4.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 | "This file implements a class to represent a product."
# Copyright (C) 2009-2010 Kristian B. Oelgaard
#
# This file is part of FFC.
#
# FFC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FFC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FFC. If not, see <http://www.gnu.org/licenses/>.
#
# First added: 2009-07-12
# Last changed: 2010-03-11
# FFC modules.
from ffc.log import error
from ffc.cpp import format
# FFC quadrature modules.
from symbolics import create_float
from symbolics import create_product
from symbolics import create_sum
from symbolics import create_fraction
from expr import Expr
#class Product(object):
class Product(Expr):
__slots__ = ("vrs", "_expanded")
def __init__(self, variables):
"""Initialise a Product object, it derives from Expr and contains
the additional variables:
vrs - a list of variables
_expanded - object, an expanded object of self, e.g.,
self = x*(2+y) -> self._expanded = (2*x + x*y) (a sum), or
self = 2*x -> self._expanded = 2*x (self).
NOTE: self._prec = 2."""
# Initialise value, list of variables, class.
self.val = 1.0
self.vrs = []
self._prec = 2
# Initially set _expanded to True.
self._expanded = True
# Process variables if we have any.
if variables:
# Remove nested Products and test for expansion.
float_val = 1.0
for var in variables:
# If any value is zero the entire product is zero.
if var.val == 0.0:
self.val = 0.0
self.vrs = [create_float(0.0)]
float_val = 0.0
break
# Collect floats into one variable
if var._prec == 0: # float
float_val *= var.val
continue
# Take care of product such that we don't create nested products.
elif var._prec == 2: # prod
# if var.vrs[0]._prec == 0:
# float_val *= var.vrs[0].val
# self.vrs += var.vrs[1:]
# continue
# self.vrs += var.vrs
# continue
# If expanded product is a float, just add it.
if var._expanded and var._expanded._prec == 0:
float_val *= var._expanded.val
# If expanded product is symbol, this product is still expanded and add symbol.
elif var._expanded and var._expanded._prec == 1:
self.vrs.append(var._expanded)
# If expanded product is still a product, add the variables.
elif var._expanded and var._expanded._prec == 2:
# self.vrs.append(var)
# Add copies of the variables of other product (collect floats).
if var._expanded.vrs[0]._prec == 0:
float_val *= var._expanded.vrs[0].val
self.vrs += var._expanded.vrs[1:]
continue
self.vrs += var._expanded.vrs
# If expanded product is a sum or fraction, we must expand this product later.
elif var._expanded and var._expanded._prec in (3, 4):
self._expanded = False
self.vrs.append(var._expanded)
# Else the product is not expanded, and we must expand this one later
else:
self._expanded = False
# Add copies of the variables of other product (collect floats).
if var.vrs[0]._prec == 0:
float_val *= var.vrs[0].val
self.vrs += var.vrs[1:]
continue
self.vrs += var.vrs
continue
# If we have sums or fractions in the variables the product is not expanded.
elif var._prec in (3, 4): # sum or frac
self._expanded = False
# Just add any variable at this point to list of new vars.
self.vrs.append(var)
# If value is 1 there is no need to include it, unless it is the
# only parameter left i.e., 2*0.5 = 1.
if float_val and float_val != 1.0:
self.val = float_val
self.vrs.append(create_float(float_val))
# If we no longer have any variables add the float.
elif not self.vrs:
self.val = float_val
self.vrs = [create_float(float_val)]
# If 1.0 is the only value left, add it.
elif abs(float_val - 1.0) < format["epsilon"] and not self.vrs:
self.val = 1.0
self.vrs = [create_float(1)]
# If we don't have any variables the product is zero.
else:
self.val = 0.0
self.vrs = [create_float(0)]
# The type is equal to the lowest variable type.
self.t = min([v.t for v in self.vrs])
# Sort the variables such that comparisons work.
self.vrs.sort()
# Compute the representation now, such that we can use it directly
# in the __eq__ and __ne__ methods (improves performance a bit, but
# only when objects are cached).
self._repr = "Product([%s])" % ", ".join([v._repr for v in self.vrs])
# Use repr as hash value.
self._hash = hash(self._repr)
# Store self as expanded value, if we did not encounter any sums or fractions.
if self._expanded:
self._expanded = self
# Print functions.
def __str__(self):
"Simple string representation which will appear in the generated code."
# If we have more than one variable and the first float is -1 exlude the 1.
if len(self.vrs) > 1 and self.vrs[0]._prec == 0 and self.vrs[0].val == -1.0:
# Join string representation of members by multiplication
return format["sub"](["", format["mul"]([str(v) for v in self.vrs[1:]])])
return format["mul"]([str(v) for v in self.vrs])
# Binary operators.
def __add__(self, other):
"Addition by other objects."
# NOTE: Assuming expanded variables.
# If two products are equal, add their float values.
if other._prec == 2 and self.get_vrs() == other.get_vrs():
# Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x.
return create_product([create_float(self.val + other.val)] + list(self.get_vrs())).expand()
# if self == 2*x and other == x return 3*x.
elif other._prec == 1: # sym
if self.get_vrs() == (other,):
# Return expanded product, to get rid of -x + x -> 0, not product(0).
return create_product([create_float(self.val + 1.0), other]).expand()
# Return sum
return create_sum([self, other])
def __sub__(self, other):
"Subtract other objects."
if other._prec == 2 and self.get_vrs() == other.get_vrs():
# Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x.
return create_product([create_float(self.val - other.val)] + list(self.get_vrs())).expand()
# if self == 2*x and other == x return 3*x.
elif other._prec == 1: # sym
if self.get_vrs() == (other,):
# Return expanded product, to get rid of -x + x -> 0, not product(0).
return create_product([create_float(self.val - 1.0), other]).expand()
# Return sum
return create_sum([self, create_product([FloatValue(-1), other])])
def __mul__(self, other):
"Multiplication by other objects."
# If product will be zero.
if self.val == 0.0 or other.val == 0.0:
return create_float(0)
# If other is a Sum or Fraction let them handle it.
if other._prec in (3, 4): # sum or frac
return other.__mul__(self)
# NOTE: We expect expanded sub-expressions with no nested operators.
# Create new product adding float or symbol.
if other._prec in (0, 1): # float or sym
return create_product(self.vrs + [other])
# Create new product adding all variables from other Product.
return create_product(self.vrs + other.vrs)
def __div__(self, other):
"Division by other objects."
# If division is illegal (this should definitely not happen).
if other.val == 0.0:
error("Division by zero.")
# If fraction will be zero.
if self.val == 0.0:
return self.vrs[0]
# If other is a Sum we can only return a fraction.
# NOTE: Expect that other is expanded i.e., x + x -> 2*x which can be handled
# TODO: Fix x / (x + x*y) -> 1 / (1 + y).
# Or should this be handled when reducing a fraction?
if other._prec == 3: # sum
return create_fraction(self, other)
# Handle division by FloatValue, Symbol, Product and Fraction.
# NOTE: assuming that we get expanded variables.
# Copy numerator, and create list for denominator.
num = self.vrs[:]
denom = []
# Add floatvalue, symbol and products to the list of denominators.
if other._prec in (0, 1): # float or sym
denom = [other]
elif other._prec == 2: # prod
# Get copy.
denom = other.vrs[:]
# fraction.
else:
error("Did not expected to divide by fraction.")
# Loop entries in denominator and remove from numerator (and denominator).
for d in denom[:]:
# Add the inverse of a float to the numerator and continue.
if d._prec == 0: # float
num.append(create_float(1.0/d.val))
denom.remove(d)
continue
if d in num:
num.remove(d)
denom.remove(d)
# Create appropriate return value depending on remaining data.
if len(num) > 1:
# TODO: Make this more efficient?
# Create product and expand to reduce
# Product([5, 0.2]) == Product([1]) -> Float(1).
num = create_product(num).expand()
elif num:
num = num[0]
# If all variables in the numerator has been eliminated we need to add '1'.
else:
num = create_float(1)
if len(denom) > 1:
return create_fraction(num, create_product(denom))
elif denom:
return create_fraction(num, denom[0])
# If we no longer have a denominater, just return the numerator.
return num
# Public functions.
def expand(self):
"Expand all members of the product."
# If we just have one variable, compute the expansion of it
# (it is not a Product, so it should be safe). We need this to get
# rid of Product([Symbol]) type expressions.
if len(self.vrs) == 1:
self._expanded = self.vrs[0].expand()
return self._expanded
# If product is already expanded, simply return the expansion.
if self._expanded:
return self._expanded
# Sort variables such that we don't call the '*' operator more than we have to.
float_syms = []
sum_fracs = []
for v in self.vrs:
if v._prec in (0, 1): # float or sym
float_syms.append(v)
continue
exp = v.expand()
# If the expanded expression is a float, sym or product,
# we can add the variables.
if exp._prec in (0, 1): # float or sym
float_syms.append(exp)
elif exp._prec == 2: # prod
float_syms += exp.vrs
else:
sum_fracs.append(exp)
# If we have floats or symbols add the symbols to the rest as a single
# product (for speed).
if len(float_syms) > 1:
sum_fracs.append( create_product(float_syms) )
elif float_syms:
sum_fracs.append(float_syms[0])
# Use __mult__ to reduce list to one single variable.
# TODO: Can this be done more efficiently without creating all the
# intermediate variables?
self._expanded = reduce(lambda x,y: x*y, sum_fracs)
return self._expanded
def get_unique_vars(self, var_type):
"Get unique variables (Symbols) as a set."
# Loop all members and update the set.
var = set()
for v in self.vrs:
var.update(v.get_unique_vars(var_type))
return var
def get_var_occurrences(self):
"""Determine the number of times all variables occurs in the expression.
Returns a dictionary of variables and the number of times they occur."""
# TODO: The product should be expanded at this stage, should we check
# this?
# Create dictionary and count number of occurrences of each variable.
d = {}
for v in self.vrs:
if v in d:
d[v] += 1
continue
d[v] = 1
return d
def get_vrs(self):
"Return all 'real' variables."
# A product should only have one float value after initialisation.
# TODO: Use this knowledge directly in other classes?
if self.vrs[0]._prec == 0: # float
return tuple(self.vrs[1:])
return tuple(self.vrs)
def ops(self):
"Get the number of operations to compute product."
# It takes n-1 operations ('*') for a product of n members.
op = len(self.vrs) - 1
# Loop members and add their count.
for v in self.vrs:
op += v.ops()
# Subtract 1, if the first member is -1 i.e., -1*x*y -> x*y is only 1 op.
if self.vrs[0]._prec == 0 and self.vrs[0].val == -1.0:
op -= 1
return op
def reduce_ops(self):
"Reduce the number of operations to evaluate the product."
# It's not possible to reduce a product if it is already expanded and
# it should be at this stage.
# TODO: Is it safe to return self.expand().reduce_ops() if product is
# not expanded? And do we want to?
# if self._expanded:
# return self._expanded
# error("Product must be expanded first before we can reduce the number of operations.")
# TODO: This should crash if it goes wrong (the above is more correct but slower).
return self._expanded
def reduce_vartype(self, var_type):
"""Reduce expression with given var_type. It returns a tuple
(found, remain), where 'found' is an expression that only has variables
of type == var_type. If no variables are found, found=(). The 'remain'
part contains the leftover after division by 'found' such that:
self = found*remain."""
# Sort variables according to type.
found = []
remains = []
for v in self.vrs:
if v.t == var_type:
found.append(v)
continue
remains.append(v)
# Create appropriate object for found.
if len(found) > 1:
found = create_product(found)
elif found:
found = found.pop()
# We did not find any variables.
else:
return [((), self)]
# Create appropriate object for remains.
if len(remains) > 1:
remains = create_product(remains)
elif remains:
remains = remains.pop()
# We don't have anything left.
else:
return [(self, create_float(1))]
# Return whatever we found.
return [(found, remains)]
# FFC quadrature modules.
from floatvalue import FloatValue
from symbol import Symbol
from sumobj import Sum
from fraction import Fraction
|