Prevent numpy from vectorizing multiplication - python

I created a set of little helper classes for working with degrees and radians. I have included some numpy ufuncs (radians, degrees, sin, cos...) so that I can have deg objects inside of numpy arrays, and perform numpy trig operations (e.g., np.cos(np.array([5*deg, 10*deg, 15*deg]))) on the numpy arrays.
However, I have discovered that when "multiplying" an ndarray by the deg class on the RHS, the numpy object's __mul__ method gets invoked on the array, rather than UnitMeta.__rmul__ leading to raising a TypeError inside of NumberMixin.__new__ as desired. It works correctly (raises an error) when deg is the LHS, e.g. deg * np.array([1]).
Only a portion of the classes is shown below for brevity.
'deg.py'
from numbers import Number
import numpy as np
class UnitMeta(type):
def __mul__(cls, other):
return cls(other)
def __rmul__(cls, other):
'''So can write things like "1 * deg"'''
return cls(other)
class NumberMixin():
def __new__(cls, v):
if not isinstance(v, Number):
raise TypeError('A valid numeric type value is required. {} is not numeric.'.format(type(v)))
return super().__new__()
def __mul__(self, other):
return self._v * other
def __rmul__(self, other):
return self.__mul__(other)
def radians(self): # NOTE: overridden in deg below
return np.radians(self._v)
def degrees(self): # NOTE: overridden in deg below
return np.degrees(self._v)
def sin(self):
return np.sin(self._v)
def cos(self):
return np.cos(self._v)
def tan(self):
return np.tan(self._v)
class deg(NumberMixin, metaclass = UnitMeta):
def __init__(self, d = 0.0):
if isinstance(d, deg):
self._v = d._v
self._deg = d._deg
else:
self._v = np.radians(d)
self._deg = d
def __mul__(self, other):
if isinstance(type(other),UnitMeta):
return NotImplemented
else:
return super().__mul__(other)
def __str__(self):
return str(self._deg) + '°'
def __repr__(self):
return str('deg({})'.format(self._deg))
def __format__(self, spec):
return self._deg.__format__(spec) + '°'
def degrees(self):
return self
def radians(self):
return self._v
if __name__ == '__main__':
try:
print('FAILURE: ', deg * np.array([1]) , ' exception not caught')
except TypeError:
print('SUCCESS: deg * np.array([1]) exception caught.')
try:
print('FAILURE: ', np.array([1]) * deg, ' exception not caught')
except TypeError:
print('SUCCESS: np.array([1]) * deg exception caught.')
Everything else works well.
I want to prevent numpy from doing this so that I can write things more like "normal math", e.g.:
5 * deg
...but have an exception come up, and not get swallowed, when something like this accidentally occurs:
a = np.array([5])
a * deg <- TypeError
Any suggestions? I haven't worked with numpy much as of yet, so apologies if there is an obvious solution.

Related

Not sure why I'm stuck in a Python stuck recursion loop

The add and mul definitions here are nonsensical because of their dependence on returning self, causing infinite loops. If they create a new distribution using the lambdas then it works fine, as in my own answer below.
I'm just playing around with classes and overriding trying to build a small statistics tool. However, when I run this code I get stuck in a recursion loop inside the __mul__ call which is being run in the n1.pdf call and I cannot figure out why. I think it has something to do with Python lazily executing the __mul__ instead of doing what I kind of 'wanted' (let's say in the language of CS) which was to create a new pointer to the old function call for pdf that is owned by the new pointer to pdf, and then to set the old pointer (the main .pdf pointer) to the new function.
I think this is quite poorly worded so edits extremely welcome if you understand what I'm asking.
import math
import random
class Distribution:
def __init__(self, pdf, cdf):
self.pdf = pdf
self.cdf = cdf
def pdf(self, x):
return self.pdf(x)
def cdf(self, x):
return self.cdf(x)
def __mul__(self, other):
if isinstance(other, float) or isinstance(other, int):
newpdf = lambda x : self.pdf(x) * other
self.pdf = newpdf
newcdf = lambda x : self.cdf(x) * other
self.cdf = newcdf
return self
else:
return NotImplemented
def __add__(self, other):
self.pdf = lambda x : self.pdf(x) + other.pdf(x)
self.cdf = lambda x : self.cdf(x) + other.cdf(x)
return Distribution(self.pdf, self.cdf)
class Normal(Distribution):
def __init__(self, mean, stdev):
self.mean = mean
self.stdev = stdev
def pdf(self, x):
return (1.0 / math.sqrt(2 * math.pi * self.stdev ** 2)) * math.exp(-0.5 * (x - self.mean) ** 2 / self.stdev ** 2)
def cdf(self, x):
return (1 + math.erf((x - self.mean) / math.sqrt(2) / self.stdev)) / 2
def sample(self):
return self.mean + self.stdev * math.sqrt(2) * math.cos(2 * math.pi * random.random())
if __name__ == "__main__":
n1 = Normal(1,2)
n1half = n1 * 0.5
x = n1.pdf(1)
print(x)
p.s. I know that it is no longer a pdf after being multiplied by 0.5, this is not an issue.
class Distribution:
...
def pdf(self, x):
return self.pdf(x)
pdf() calls itself, which calls itself, which calls itself... infinitely.
Same with cdf().
def pdf(self, x):
return self.pdf(x)
def cdf(self, x):
return self.cdf(x)
I assume your intent is to delegate to the attributes. Since they are always assigned, they will be found (assuming you do the lookup on an instance) instead of the class methods (which would straightforwardly be infinite recursion without those attributes); but this in turn means that these class methods are just useless. x.cdf(y), where cdf is a callable instance attribute, just works; there is no need to provide a method as well.
newpdf = lambda x : self.pdf(x) * other
self.pdf = newpdf
I assume your intent is to create a new function that relies upon the existing value of self.pdf. Unfortunately, it doesn't work that way. The problem is that the lambda is late binding. When it executes, that is the time at which it will look up self.pdf... and find itself.
There is a separate problem here, in that you are writing __mul__ and __add__ implementations - that is, the * and + operators, which are supposed to return a new value, and not mutate either operand. (If you wrote a = 3 and b = 4 and then c = a * b, you would be extremely surprised if the values of a or b changed, yes?)
We can solve both problems at once, by simply using the computed pdf and cdf to create a new instance (which we need anyway):
def __mul__(self, other):
if isinstance(other, float) or isinstance(other, int):
newpdf = lambda x : self.pdf(x) * other
newcdf = lambda x : self.cdf(x) * other
return Distribution(newpdf, newcdf)
else:
return NotImplemented
Similarly, __add__ should use local variables, rather than modifying self:
def __add__(self, other):
newpdf = lambda x : self.pdf(x) + other.pdf(x)
newcdf = lambda x : self.cdf(x) + other.cdf(x)
return Distribution(newpdf, newcdf)
Note that implementing these methods also gives you the augmented assignment operators *= and += (albeit by creating a new object and rebinding the name).
Let's test it:
if __name__ == "__main__":
n1 = Normal(1,2)
n1half = n1 * 0.5
print(n1.pdf(1))
print(n1half.pdf(1))
n1 += n1
print(n1.pdf(1))
I get:
>py test.py
0.19947114020071635
0.09973557010035818
0.3989422804014327
Thanks for the help #John and #Tom and #bbbbbb... The problem was trying to return self instead of creating a new distribution. If I change the def'n of mul to
def __mul__(self, other):
if isinstance(other, float) or isinstance(other, int):
def newpdf(x):
return self.pdf(x) * other
def newcdf(x):
return self.cdf(x) * other
return Distribution(newpdf, newcdf)
else:
return NotImplemented
Then this solves this problem

How to not print "Decimal" in my output for vectors?

from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector(object):
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot normalize the zero vector'
def __init__(self, coordinates):
try:
if not coordinates:
raise ValueError
self.coordinates = tuple([Decimal(x) for x in coordinates])
self.dimension = len(self.coordinates)
except ValueError:
raise ValueError('The coordinates must be nonempty')
except TypeError:
raise TypeError('The coordinates must be an iterable')
def __str__(self):
return 'Vector: {}'.format(self.coordinates)
def __eq__(self, v):
return self.coordinates == v.coordinates
def plus(self,v):
new_coordinates = [x+y for x,y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def minus(self,v):
new_coordinates = [x-y for x,y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def times_scalar(self,c):
new_coordinates = [c*x for x in self.coordinates]
return Vector(new_coordinates)
def magnitude(self):
coordinates_squared = [x**2 for x in self.coordinates]
return Decimal(sqrt(sum(coordinates_squared)))
def normalized(self):
try:
magnitude = self.magnitude()
return self.times_scalar(Decimal(1.0)/magnitude)
except ZeroDivisionError:
raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)
#normalization
v3 = Vector([5.581, -2.136])
v4 = Vector([1.996, 3.108, -4.554])
print(v3.normalized())
print(v4.normalized())
The output is:
Vector: (Decimal('0.933935214086640295130539147343'), Decimal('-0.357442325262329983594964055642'))
Vector: (Decimal('0.340401295943301353537171045562'), Decimal('0.530043701298487295255023200306'), Decimal('-0.776647044952802835008995686630'))
Why is this? How can I get it to print without the word "Decimal"?
Printing a tuple defaults to displaying the __repr__() of the contained objects, which is a technical representation for debugging and usually includes the type. What you want is the __str__() (the "pretty" representation) so you must format them yourself.
Also note that you must initialize Decimal with strings or you will capture the floating point inaccuracy you are trying to avoid.
Example (removed unnecessary code not necessary to demonstrate the issue):
from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector:
def __init__(self, coordinates):
self.coordinates = tuple([Decimal(x) for x in coordinates])
def __str__(self):
return f"Vector: ({', '.join(str(x) for x in self.coordinates)})"
v1 = Vector([5.581, -2.136]) # Initialized without strings
v2 = Vector([1.996, 3.108, -4.554])
v3 = Vector(['5.581', '-2.136'])
v4 = Vector(['1.996', '3.108', '-4.554'])
print(v1)
print(v2)
print(v3)
print(v4)
Output:
Vector: (5.58100000000000040500935938325710594654083251953125, -2.13600000000000012079226507921703159809112548828125)
Vector: (1.995999999999999996447286321199499070644378662109375, 3.108000000000000095923269327613525092601776123046875, -4.5540000000000002700062395888380706310272216796875)
Vector: (5.581, -2.136)
Vector: (1.996, 3.108, -4.554)
If you want to get a string with all the decimals, use the builtin str method:
>>> str(decimal.Decimal(2.1))
'2.100000000000000088817841970012523233890533447265625'
If you want a specific number of decimals use Python string formatting:
>>> f"{decimal.Decimal(2.1):0.2}" # Use :0.n where n is the number of decimals
'2.1'
or with .format
>>> "{:0.2}".format(decimal.Decimal(2.1))
'2.1'
Reference
EDIT
Your __str__ method would be something like this:
def __str__(self):
return 'Vector: {}'.format(str(x) for x in self.coordinates)

Typeerror: unsupported operand type(s) for /: 'Decimal' and 'float'

Here is my code: I have tried all day to solve the mistake but I still have the error:
TypeError: unsupported operand type(s) for /: 'Decimal' and 'float'.
from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector(object):
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot compute an angle with the zero vector'
def __init__(self, coordinates):
try:
if not coordinates:
raise ValueError
self.coordinates = tuple(Decimal(x) for x in coordinates)
self.dimension = len(self.coordinates)
except ValueError:
raise ValueError('The coordinates must be nonempty')
except TypeError:
raise TypeError('The coordinates must be an iterable')
#orthogonal or parallel
def is_zero(self, tolerance=1e-10):
return self.magnitude() < tolerance
def is_orthogonal_to(self, v, tolerance=1e-10):
return abs(self.dot(v)) < tolerance
def is_parallel_to(self, v):
return ( self.is_zero() or
v.is_zero() or
self.angle_with(v) == 0 or
self.angle_with(v) == pi )
#dot product &angle
def dot(self, v):
return sum([x*y for x,y in zip(self.coordinates,v.coordinates)])
def angle_with(self,v,in_degrees=False):
try:
u1 = self.normalized()
u2 = v.normalized()
angle_in_radians = acos(u1.dot(u2))
if in_degrees:
degrees_per_radian = 180./pi
return angle_in_radians * degrees_per_radian
else:
return angle_in_radians
except Exception as e:
if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:
raise Exception('Cannot compute an angle with the zero vector')
else:
raise e
#magnitude& Direction
def magnitude(self):
coordinates_squared = [x**2 for x in self.coordinates]
return sqrt(sum(coordinates_squared))
def normalized(self):
try:
magnitude = self.magnitude()
return self.times_scalar(Decimal('1.0')/magnitude)
except ZeroDivisionError:
raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)
#Plus,minus, scalar multiply
def plus(self, v):
new_coordinates = [x+y for x,y in zip(self.coordinates,v.coordinates)]
return Vector(new_coordinates)
def minus(self, v):
new_coordinates = [x-y for x,y in zip(self.coordinates,v.coordinates)]
return Vector(new_coordinates)
def times_scalar(self, c):
new_coordinates = [Decimal(c)*x for x in self.coordinates]
return Vector(new_coordinates)
def __str__(self):
return 'Vector: {}'.format(self.coordinates)
def __eq__(self, v):
return self.coordinates == v.coordinates
v = Vector(['8','-9'])
w = Vector(['-1','-1'])
print v.dot(w)
print v.angle_with(w)
print v.angle_with(w, in_degrees=True)
In general it usually does not make sense to mix Decimal and float math. Not sure why you need Decimal here, but with two small changes this code can be made to run. I would suggest considering getting rid of the Decimal unless you are sure you need it:
Change magnitude to:
# magnitude& Direction
def magnitude(self):
coordinates_squared = [x ** 2 for x in self.coordinates]
return sum(coordinates_squared) ** Decimal('0.5')
And the acos call to:
angle_in_radians = Decimal(str(acos(u1.dot(u2))))

Delayed Compute with Method Chaining in Python

Let say I have a class:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
def add(self, value):
# Add amount 'value' to every element in the results list
def minus(self, value):
# Subtract amount 'value' from every element in the results list
def compute(self):
# Perform computation
Is there a way to do something like:
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
m.compute() # This would actually run the computations in order
How do I do something like this in python?
Personally, I would have .add(), et al, push the operator and the operand onto a list and then have .compute() walk through the list, computing the answer as it goes.
Operator chaining is easily accomplished by having each operator return self as its final instruction.
For example:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
self.operations = []
def add(self, value):
# Add amount 'value' to every element in the results list
self.operations.append(('+', value))
return self
def minus(self, value):
# Subtract amount 'value' from every element in the results list
self.operations.append(('-', value))
return self
def compute(self):
results = []
for x in self.results:
for op, value in self.operations:
if op == '+':
x += value
elif op == '-':
x -= value
results.append(x)
return results
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
print(m.compute()) # This would actually run the computations in order
Wow, you guys are fast!
Here is another go also with a stack, but manipulating the results-list:
class MATH(object):
def __init__(self):
self.results = [0, 1, 2]
self.stack = []
def add(self, value):
self.stack.append(value)
return self
def minus(self, value):
self.stack.append(-value)
return self
def compute(self):
for s in self.stack:
for index, _ in enumerate(self.results):
self.results[index] += s
m = MATH()
m.add(5).minus(2).add(7) # This would be a lazy and not actually compute
m.compute() # This would actually run the computations in order
print m.results
[10, 11, 12]
As #Rob pointed out, you will need some way to store the operators so that the final compute method can be utilized correctly. This solution uses __add__ and __sub__, with a decorator to store the operators. Note, however, that it would be much more efficient to keep a running total of the values that have been pushed to the stack:
import operator as op
from collections import deque
def operator(f):
def wrapper(cls, _):
cls.operators.append(f.__name__.replace('__', ''))
return f(cls, _)
return wrapper
class Math:
def __init__(self):
self.stack = []
self.operators = deque()
#operator
def __sub__(self, _val):
self.stack.append(_val)
return self
#operator
def __add__(self, _val):
self.stack.append(_val)
return self
def compute(self):
_result = 0
while self.stack:
a, *c = self.stack
_result = getattr(op, self.operators.popleft())(_result, a)
self.stack = c
return _result
m = Math()
m1 = m + 5 - 2 + 7
print([m1.stack, m1.operators])
print(m1.compute())
Output:
[[5, 2, 7], ['add', 'sub', 'add']]
10
Here's a string-based approach which requires little brainpower.
class Math:
def __init__(self):
self.stack = '0'
#staticmethod
def wrap(expr):
return '(' + expr + ')'
def _op(self, other, op):
self.stack = ' '.join([Math.wrap(self.stack), op, str(other)])
def add(self, other):
self._op(other, '+')
return self
def mul(self, other):
self._op(other, '*')
return self
def compute(self):
return eval(self.stack)
m = Math()
print(m.add(2).mul(3).compute())

Why does the print statement at the bottom of my main method not print anything?

I'm working on the MIT open courseware for CS-600 and I can't figure out why the last print statement isn't printing anything. Here's the code I wrote:
#!/usr/bin/env python
# encoding: utf-8
# 6.00 Problem Set 9
#
# Name:
# Collaborators:
# Time:
from string import *
class Shape(object):
def area(self):
raise AttributeException("Subclasses should override this method.")
class Square(Shape):
def __init__(self, h):
"""
h: length of side of the square
"""
self.side = float(h)
def area(self):
"""
Returns area of the square
"""
return self.side**2
def __str__(self):
return 'Square with side ' + str(self.side)
def __eq__(self, other):
"""
Two squares are equal if they have the same dimension.
other: object to check for equality
"""
return type(other) == Square and self.side == other.side
class Circle(Shape):
def __init__(self, radius):
"""
radius: radius of the circle
"""
self.radius = float(radius)
def area(self):
"""
Returns approximate area of the circle
"""
return 3.14159*(self.radius**2)
def __str__(self):
return 'Circle with radius ' + str(self.radius)
def __eq__(self, other):
"""
Two circles are equal if they have the same radius.
other: object to check for equality
"""
return type(other) == Circle and self.radius == other.radius
#
# Problem 1: Create the Triangle class
#
## TO DO: Implement the `Triangle` class, which also extends `Shape`.
class Triangle(Shape):
def __init__(self, base, height):
self.base = float(base)
self.height = float(height)
def area(self):
return self.base*self.height/2
def __str__(self):
return 'Triangle with base ' + str(self.base) + 'and height ' + str(self.height)
def __eq__(self, other):
return type(other) == Triangle and self.base == other.base and self.height == other.height
#
# Problem 2: Create the ShapeSet class
#
## TO DO: Fill in the following code skeleton according to the
## specifications.
class ShapeSet(object):
def __init__(self):
"""
Initialize any needed variables
"""
self.allCircles = []
self.allSquares = []
self.allTriangles = []
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
self.place = None
def addShape(self, sh):
"""
Add shape sh to the set; no two shapes in the set may be
identical
sh: shape to be added
"""
if not isinstance(sh, Shape): raise TypeError('not a shape')
if isinstance(sh, Square):
for sq in self.allSquares:
if sh == sq:
raise ValueError('shape already in the set')
self.allSquares.append(sh)
if isinstance(sh, Triangle):
for tri in self.allTriangles:
if sh == tri:
raise ValueError('shape already in the set')
self.allTriangles.append(sh)
if isinstance(sh, Circle):
for circ in self.allCircles:
if sh == circ:
raise ValueError('shape already in the set')
self.allCircles.append(sh)
def __iter__(self):
"""
Return an iterator that allows you to iterate over the set of
shapes, one shape at a time
"""
self.place = 0
return self
def next(self):
if self.place >= len(self.allShapes):
raise StopIteration
self.place += 1
return self.allShapes[self.place - 1]
def __str__(self):
"""
Return the string representation for a set, which consists of
the string representation of each shape, categorized by type
(circles, then squares, then triangles)
"""
shapeList = ""
for item in self.allShapes:
shapeList += item.get__str__ + "br/"
return shapeList
#
# Problem 3: Find the largest shapes in a ShapeSet
#
def findLargest(shapes):
"""
Returns a tuple containing the elements of ShapeSet with the
largest area.
shapes: ShapeSet
"""
## TO DO
#
# Problem 4: Read shapes from a file into a ShapeSet
#
def readShapesFromFile(filename):
"""
Retrieves shape information from the given file.
Creates and returns a ShapeSet with the shapes found.
filename: string
"""
## TO DO
def main():
sq1 = Square(4.0)
sq2 = Square(5.0)
sq3 = Square(3.0)
circ1 = Circle(3.0)
circ2 = Circle(3.2)
tri1 = Triangle(3.0, 4.0)
tri2 = Triangle(4.0, 3.0)
tri3 = Triangle(1.0, 1.0)
thisSet = ShapeSet()
thisSet.addShape(sq1)
thisSet.addShape(sq2)
thisSet.addShape(sq3)
thisSet.addShape(circ1)
thisSet.addShape(circ2)
thisSet.addShape(tri1)
thisSet.addShape(tri2)
thisSet.addShape(tri3)
print thisSet
if __name__ == '__main__':
main()
This line:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
doesn't do what you think it does. It sets allShapes to an empty list, and then as you add shapes later, nothing updates allShapes.
Then your __str__ function just loops over allShapes, which is still empty, so your __str__ returns an empty string.
This line makes allShapes an empty list:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
If you modify allCircles, that doesn't affect allShapes. I would personally eliminate allShapes, and in the str method, add them at the last possible second:
for item in self.allCircles + self.allSquares + self.allTriangles:
The problem is here:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
When you concatenate lists like this, the result is a copy of the component lists. So when those lists are changed later, the concatenated list isn't changed. In this case, self.allCircles, etc. are all empty. So self.allShapes is an empty list too; the for loop in ShapeSet.__str__ doesn't append anything to ShapeList, and so the result is an empty string.
One simple way to fix this would be to make allShapes a method that you call, and that returns a new concatenation of self.allCircles... etc. each time it's called. That way, allShapes is always up-to-date.
If this is your actual code, then it must be because of
item.get__str__
which should raise an exception.
Edit: as others have noted, this isn't the actual problem, but I leave this here as a hint for further progress. Mind you, it's considered bad style ("unpythonic") to call x.__str__() directly, as you probably intended. Call str(x) instead, even in the implementation of __str__.
You assign allShapes to be the value of self.allCircles + self.allSquares + self.allTriangles at the start in your init method (when the other lists are empty).
It's value is then never changed, so it remains empty.
You need this in addShape:
self.allShapes.append(sh)

Categories