Source code for httk.core.vectors.vector

#
#    The high-throughput toolkit (httk)
#    Copyright (C) 2012-2015 Rickard Armiento
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program 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 Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
import sys, random
from functools import reduce

# Retain python2 compatibility without a dependency on httk.core
if sys.version[0] == "2":
    string_types = basestring
    integer_types = (long, int)
else:
    string_types = str
    integer_types = (int,)
    

[docs]class Vector(object): """ Defines the general Vector API """
[docs] @classmethod def use(cls, old): """ Make sure variable is a FracVector, and if not, convert it. """ if isinstance(old, Vector): return old else: return cls.create(old)
[docs] @classmethod def create(cls, data, chain=False): """ Create a Vector from various types of sequenced data. Will return a suitable Vector subclass for the type of data given """ #TODO: Add suitable intelligente to select the right vector class to create from httk.core.vectors.fracvector import FracVector return FracVector.create(data, chain=chain)
# Note, these are different, and thus named different (get_ prefix), than the corresponding methods in a list, since # they do not modify the vector itself.
[docs] def get_append(self, other): return self.__class__.create([self, [other]], chain=True)
[docs] def get_extend(self, other): return self.__class__.create([self, other], chain=True)
[docs] def get_insert(self, pos, other): return self.__class__.create([self[:pos], [other], self[pos:]], chain=True)
[docs] def get_prepend(self, other): return self.__class__.create([[other], self], chain=True)
[docs] def get_prextend(self, other): return self.__class__.create([other, self], chain=True)
[docs] def get_stacked(self, other): return self.__class__.create([self, [other]])
[docs] def ged_prestacked(self, other): return self.__class__.create([[other], self])
[docs] def ged_stackedinsert(self, pos, other): return self.__class__.create([self[:pos], [other], self[pos:]], chain=True)
[docs] @classmethod def chain_vecs(cls, vecs): """ Optimized chaining of Vectors. vecs: a list (or tuple) of vectors. Returns the same thing as Vector.create(vecs, chain=True) i.e., removes outermost dimension and chain the sub-sequences. If input=[[1 2 3],[4,5,6]], then Vector.chain(input) -> [1,2,3,4,5,6] Subclasses may add requirements on the vectors to use this method over <subclass>.create """ return cls.create(vecs, chain=True)
[docs] @classmethod def stack_vecs(cls, vecs): """ Optimized stacking of FracVectors. vecs = a list (or tuple) of fracvectors. Returns the same thing as:: Vector.create(vecs) Subclasses may add requirements on the vectors to use this method over <subclass>.create """ return cls.create(vecs)
[docs] @classmethod def eye(cls, dims): """ Create a diagonal one-matrix with the given dimensions """ return cls.create(tuple_eye(dims))
[docs] @classmethod def zeros(cls, dims): """ Create a zero matrix with the given dimensions """ return cls.create(tuple_zeros(dims))
[docs] @classmethod def random(cls, dims, minval=-100, maxval=100): """ Create a zero matrix with the given dimensions """ return cls.create(tuple_random(dims, minval=minval, maxval=maxval))
# @classmethod # def from_floats(cls, l, resolution=2**32): # """ # Create a FracVector from a (nested) list or tuple of floats. You can convert a numpy array with # this method if you use A.tolist() # # resolution: the resolution used for interpreting the given floating point numbers. Default is 2^32. # """ # # eps = (1.0 / resolution) * 0.1 # # gcd = nested_reduce(lambda x, y: fractions.gcd(x, abs(int((y + eps) * resolution))), l, initializer=resolution) # noms = cls.nested_map(lambda x: int((x + eps) * resolution) // gcd, l) # denom = resolution / gcd # # return cls(noms, denom) # # @classmethod # def _create_func(cls, data, func, **args): # def apply_func(arg): # val, delta = string_to_val_and_delta(arg) # low = val-delta # high = val+delta # lowval = func(low, **args) # highval = func(high, **args) # return best_rational_in_interval(lowval, highval) # # newdata = nested_map_tuple(apply_func, data) # return cls.create(newdata) # # @classmethod # def create_cos(cls, data, degrees=False, limit=False, prec=fractions.Fraction(1, 1000000)): # """ # Creating a FracVector as the cosine of the argument data. If data are composed by strings, the standard deviation of # the numbers are taken into account, and the best possible fractional approximation to the cosines # of the data are returned within the standard deviation. # # This is not the same as FracVector.create(data).cos(), which creates the best possible fractional # approximations of data and then takes cos on that. # """ # return cls._create_func(data, frac_cos, degrees=degrees, limit=limit, prec=prec) # # @classmethod # def create_sin(cls, data, degrees=False, limit=False, prec=fractions.Fraction(1, 1000000)): # """ # Creating a FracVector as the sine of the argument data. If data are composed by strings, the standard deviation of # the numbers are taken into account, and the best possible fractional approximation to the cosines # of the data are returned within the standard deviation. # # This is not the same as FracVector.create(data).sin(), which creates the best possible fractional # approximations of data and then takes cos on that. # """ # return cls._create_func(data, frac_sin, degrees=degrees, limit=limit, prec=prec) # # @classmethod # def create_exp(cls, data, prec=fractions.Fraction(1, 1000000),limit=False): # """ # Creating a FracVector as the exponent of the argument data. If data are composed by strings, the standard deviation of # the numbers are taken into account, and the best possible fractional approximation to the cosines # of the data are returned within the standard deviation. # # This is not the same as FracVector.create(data).exp(), which creates the best possible fractional # approximations of data and then takes exp on that. # """ # return cls._create_func(data, frac_exp, limit=limit, prec=prec) # # @classmethod # def pi(cls, prec=fractions.Fraction(1, 1000000), limit=False): # """ # Create a scalar FracVector with a rational approximation of pi to precision prec. # """ # return cls.create(frac_pi(prec,limit=limit)) # # # #### Properties # # @property # def dim(self): # """ # This property returns a tuple with the dimensionality of each dimension of the FracVector # (the noms are assumed to be a nested list of rectangular shape). # """ # if self._dim is None: # dimchk = self.noms # self._dim = () # while True: # try: # d = len(dimchk) # except TypeError: # break # if d > 0: # self._dim += (d,) # dimchk = dimchk[0] # else: # break # return self._dim # # @property # def nom(self): # """ # Returns the integer nominator of a scalar FracVector. # """ # if self.dim != (): # raise Exception("FracVector.nom: attempt to access scalar nominator on non-scalar FracVector:"+str(self)) # return self.noms # # #### Methods # # def validate(self): # # TODO: check all dimensions and make sure noms is a square tensor of only tuples # return True # # def to_tuple(self): # """ # Return a FracVector on tuple representation: (denom, ...noms...). # """ # return (self.denom, self.noms) # # def to_floats(self): # """ # Converts the ExactVector to a list of floats. # """ # #denom = float(self.denom) # #return nested_map_list(lambda x: float(x) / denom, self.noms) # return nested_map_list(lambda x: float(fractions.Fraction(x, self.denom)), self.noms) # # def to_float(self): # """ # Converts a scalar ExactVector to a single float. # """ # #try: # # return float(self.nom) / float(self.denom) # #except OverflowError: # return float(fractions.Fraction(self.nom, self.denom)) # # def to_fractions(self): # """ # Converts the FracVector to a list of fractions. # """ # return nested_map_list(lambda x: fractions.Fraction(x, self.denom), self.noms) # # def to_ints(self): # """ # Converts the FracVector to a list of integers, rounded off as best possible. # """ # return nested_map_list(lambda x: round(fractions.Fraction(x, self.denom)), self.noms) # # def to_fraction(self): # """ # Converts scalar FracVector to a fraction. # """ # return fractions.Fraction(self.nom, self.denom) # # def to_int(self): # """ # Converts scalar FracVector to an integer (truncating as necessary). # """ # #return int(round(fractions.Fraction(self.nom, self.denom))+0.1) # return int(self) # # def flatten(self): # """ # Returns a FracVector that has been flattened out to a single rowvector # """ # noms = nested_reduce(lambda x, y: x + [y], self.noms, initializer=[]) # return self.__class__(self._dup_noms(noms), self.denom) # # @classmethod # def set_common_denom(cls, A, B): # """ # Used internally to combine two different FracVectors. # # Returns a tuple (A2,B2,denom) where A2 is numerically equal to A, and B2 is numerically equal to B, but A2 and B2 are both # set on the same shared denominator 'denom' which is the *product* of the denominator of A and B. # """ # # if not isinstance(A, FracVector): # A = cls(A, 1) # # if not isinstance(B, FracVector): # B = cls(B, 1) # # denom = A.denom * B.denom # mA = B.denom # mB = A.denom # # Anoms = A._map_over_noms(lambda x: x * mA) # Bnoms = B._map_over_noms(lambda x: x * mB) # # return cls(Anoms, denom), cls(Bnoms, denom), denom # # def sign(self): # """ # Returns the sign of the scalar FracVector: -1, 0 or 1. # """ # if self.dim != (): # raise Exception("FracVector.nom: attempt to access scalar nominator on non-scalar FracVector.") # if self.noms < 0: # return -1 # elif self.noms > 0: # return 1 # else: # return 0 # # def T(self): # """ # Returns the transpose, A^T. # """ # dim = self.dim # if len(dim) == 0: # return self.__class__(self.noms, self.denom) # elif len(dim) == 1: # noms = self._dup_noms((self.noms[col],) for col in range(dim[0])) # return self.__class__(noms, self.denom) # elif len(dim) == 2: # noms = self._dup_noms(self._dup_noms(self.noms[col][row] for col in range(dim[0])) for row in range(dim[1])) # return self.__class__(noms, self.denom) # raise Exception("FracVector.T(): on non 1 or 2 dimensional object not implemented") # # def det(self): # """ # Returns the determinant of the FracVector as a scalar FracVector. # """ # dim = self.dim # if dim == (3, 3): # A = self.noms # noms = A[0][0] * A[1][1] * A[2][2] + A[0][1] * A[1][2] * A[2][0] + A[0][2] * A[1][0] * A[2][1] - A[0][2] * A[1][1] * A[2][0] - A[0][1] * A[1][0] * A[2][2] - A[0][0] * A[1][2] * A[2][1] # return self.__class__(noms, self.denom ** 3) # elif dim == (4, 4): # A = self.noms # noms = \ # A[0][0] * A[1][1] * A[2][2] * A[3][3] + A[0][0] * A[2][1] * A[3][2] * A[1][3] + A[0][0] * A[3][1] * A[1][2] * A[2][3] \ # + A[1][0] * A[0][1] * A[3][2] * A[2][3] + A[1][0] * A[2][1] * A[0][2] * A[3][3] + A[1][0] * A[3][1] * A[2][2] * A[0][3] \ # + A[2][0] * A[0][1] * A[1][2] * A[3][3] + A[2][0] * A[1][1] * A[3][2] * A[0][3] + A[2][0] * A[3][1] * A[0][2] * A[1][3] \ # + A[3][0] * A[0][1] * A[2][2] * A[1][3] + A[3][0] * A[1][1] * A[0][2] * A[2][3] + A[3][0] * A[2][1] * A[1][2] * A[0][3] \ # - A[0][0] * A[1][1] * A[3][2] * A[2][3] - A[0][0] * A[2][1] * A[1][2] * A[3][3] - A[0][0] * A[3][1] * A[2][2] * A[1][3] \ # - A[1][0] * A[0][1] * A[2][2] * A[3][3] - A[1][0] * A[2][1] * A[3][2] * A[0][3] - A[1][0] * A[3][1] * A[0][2] * A[2][3] \ # - A[2][0] * A[0][1] * A[3][2] * A[1][3] - A[2][0] * A[1][1] * A[0][2] * A[3][3] - A[2][0] * A[3][1] * A[1][2] * A[0][3] \ # - A[3][0] * A[0][1] * A[1][2] * A[2][3] - A[3][0] * A[1][1] * A[2][2] * A[0][3] - A[3][0] * A[2][1] * A[0][2] * A[1][3] # return self.__class__(noms, self.denom ** 4) # # raise Exception("FracVector.det: on non 3x3 or 4x4 matrix not implemented. Matrix was:"+str(dim)) # # def inv(self): # """ # Returns the matrix inverse, A^-1 # """ # dim = self.dim # if dim == (): # # For a FracScalar, just swap denominator and nominator # return self.__class__(self.denom, self.nom) # # if dim != (3, 3): # raise Exception("FracVector.inv: only scalar and 3x3 matrix implemented") # # # We are dividing with a determinant giving self.denom**3 in nominator, and # # from the matrix 1/self.denom**2 falls out -> one factor of self.denom in nominator # # det = self.det() # det_nom = det.nom # # if det_nom == 0: # raise Exception("ExactVector.inverse: cannot take inverse of singular matrix.") # # if det_nom < 0: # denom = -det_nom # m = -self.denom # else: # denom = det_nom # m = self.denom # # A = self.noms # noms = self._dup_noms(( # self._dup_noms((m * (A[1][1] * A[2][2] - A[1][2] * A[2][1]), m * (A[0][2] * A[2][1] - A[0][1] * A[2][2]), m * (A[0][1] * A[1][2] - A[0][2] * A[1][1])),), # self._dup_noms((m * (A[1][2] * A[2][0] - A[1][0] * A[2][2]), m * (A[0][0] * A[2][2] - A[0][2] * A[2][0]), m * (A[0][2] * A[1][0] - A[0][0] * A[1][2])),), # self._dup_noms((m * (A[1][0] * A[2][1] - A[1][1] * A[2][0]), m * (A[0][1] * A[2][0] - A[0][0] * A[2][1]), m * (A[0][0] * A[1][1] - A[0][1] * A[1][0])),) # )) # # return self.__class__(noms, denom) # # def simplify(self): # """ # Returns a reduced FracVector. I.e., each element has the same numerical value # but the new FracVector represents them using the smallest possible shared denominator. # """ # noms = self.noms # denom = self.denom # # if self.denom != 1: # gcd = self._reduce_over_noms(lambda x, y: fractions.gcd(x, abs(y)), initializer=self.denom) # if gcd != 1: # denom = denom / gcd # noms = self._map_over_noms(lambda x: int(x / gcd)) # # return self.__class__(noms, denom) # # def set_denominator(self, set_denom=1000000000): # """ # Returns a FracVector of reduced resolution where every element is the closest numerical approximation using this denominator. # """ # denom = self.denom # # def limit_resolution_one(x): # low = (x * set_denom) // denom # if x * set_denom * 2 > (low * 2 + 1) * denom: # return low + 1 # else: # return low # # noms = self._map_over_noms(limit_resolution_one) # return self.__class__(noms, set_denom) # # def limit_denominator(self, max_denom=1000000000): # """ # Returns a FracVector of reduced resolution. # # resolution: each element in the returned FracVector is the closest numerical approximation that can is allowed by # a fraction with maximally this denominator. Note: since all elements must be put on a common denominator, the result # may have a larger denominator than max_denom # """ # denom = self.denom # newvalues = self._map_over_noms(lambda x: fractions.Fraction(x, denom).limit_denominator(max_denom)) # return self.__class__.create(newvalues) # # def floor(self): # """ # Returns the integer that is equal to or just below the value stored in a scalar FracVector. # """ # if self.dim != (): # raise Exception("FracVector.floor: Needs scalar FracVector") # # Python integer division really does floor, even for negative numbers # return self.nom // self.denom # # def ceil(self): # """ # Returns the integer that is equal to or just below the value stored in a scalar FracVector. # """ # if self.dim != (): # raise Exception("FracVector.ceil: Needs scalar FracVector") # if self.nom % self.denom == 0: # return self.nom // self.denom # else: # return self.nom // self.denom + 1 # # def normalize(self): # """ # Add/remove an integer +/-N to each element to place it in the range [0,1) # """ # noms = self._map_over_noms(lambda x: x - self.denom * (x // self.denom)) # return self.__class__(noms, self.denom) # # def normalize_half(self): # """ # Add/remove an integer +/-N to each element to place it in the range [-1/2,1/2) # # This is useful to find the shortest vector C between two points A, B in a space with periodic boundary conditions [0,1): # C = (A-B).normalize_half() # """ # noms = self._map_over_noms(lambda x: 2 * x - (2 * self.denom) * ((((2 * x) // self.denom) + 1) // 2)) # return self.__class__(noms, 2 * self.denom) # # def mul(self, other): # """ # Returns the result of multiplying the vector with 'other' using matrix multiplication. # # Note that for two 1D FracVectors, A.dot(B) is *not* the same as A.mul(B), but rather: A.mul(B.T()). # """ # # Handle other being another object # if not isinstance(other, FracVector): # other = self.__class__.create(other) # # Adim = self.dim # Bdim = other.dim # A = self.noms # B = other.noms # denom = self.denom * other.denom # # # Other is scalar # if Bdim == (): # m = other.nom # noms = self._map_over_noms(lambda x: x * m) # # # Self is scalar # elif Adim == (): # m = self.nom # noms = other._map_over_noms(lambda x: x * m) # # # Vector * Vector # elif len(Adim) == 1 and len(Bdim) == 1: # if Adim[0] != Bdim[0]: # raise Exception("ExactVector.dot: vector multiplication dimension mismatch," + str(Adim) + " and " + str(Bdim)) # noms = self._dup_noms(A[i] * B[i] for i in range(Adim[0])) # # # Matrix * vector # elif len(Adim) == 2 and len(Bdim) == 1: # if Adim[1] != Bdim[0]: # raise Exception("ExactVector.dot: matrix multiplication dimension mismatch," + str(Adim) + " and " + str(Bdim)) # noms = self._dup_noms(sum([A[row][i] * B[i] for i in range(Adim[1])]) for row in range(Adim[0])) # # # vector * Matrix # elif len(Adim) == 1 and len(Bdim) == 2: # if Adim[0] != Bdim[0]: # raise Exception("ExactVector.dot: matrix multiplication dimension mismatch," + str(Adim) + " and " + str(Bdim)) # noms = self._dup_noms(sum([A[i] * B[i][col] for i in range(Adim[0])]) for col in range(Bdim[1])) # # # Matrix * Matrix # elif len(Adim) == 2 and len(Bdim) == 2: # if Adim[1] != Bdim[0]: # raise Exception("ExactVector.dot: matrix multiplication dimension mismatch," + str(Adim) + " and " + str(Bdim)) # noms = self._dup_noms(self._dup_noms(sum([A[row][i] * B[i][col] for i in range(Adim[1])]) for col in range(Bdim[1])) # for row in range(Adim[0])) # # else: # raise Exception("ExactVector.dot: cannot handle tensors of order > 2, dimensions:" + str(Adim) + " and " + str(Bdim)) # # return self.__class__(noms, denom) # # def dot(self, other): # """ # Returns the vector dot product of the 1D vector with the 1D vector 'other', i.e., A . B or A \cdot B. The same as A * B.T(). # """ # Adim = self.dim # Bdim = other.dim # A = self.noms # B = other.noms # denom = self.denom * other.denom # # if len(Adim) == 1 and len(Bdim) == 1: # if Adim[0] != Bdim[0]: # raise Exception("ExactVector.dot: vector multiplication dimension mismatch," + str(Adim) + " and " + str(Bdim)) # noms = sum(A[i] * B[i] for i in range(Adim[0])) # else: # raise Exception("ExactVector.dot: dot multiplication dimensions not = 1," + str(Adim) + " and " + str(Bdim)) # return self.__class__(noms, denom) # # def lengthsqr(self): # """ # Returns the square of the length of the vector. The same as A * A.T() # """ # # Other is scalar # dim = self.dim # # if dim == (): # noms = self.noms ** 2 # elif len(self.dim) == 1: # noms = sum(self.noms[i] ** 2 for i in range(self.dim[0])) # else: # raise Exception("ExactVector.lengthsqr: vector must be scalar or dimension must be = 1, is " + str(self.dim)) # return self.__class__(noms, self.denom ** 2) # # def cross(self, other): # """ # Returns the vector cross product of the 3-element 1D vector with the 3-element 1D vector 'other', i.e., A x B. # """ # # Note: multiplication is an especially simple case, there is no need to bring the two vectors into a # # common denom with common_denom, since a/b * c/d = a*c/(b*d) # Adim = self.dim # A = self.noms # Bdim = other.dim # B = other.noms # denom = self.denom * other.denom # if Adim != (3,) or Bdim != (3,): # raise Exception("FracVector.cross: can only do cross products of 3-element 1D vectors. The dimensions are:" + str(Adim) + " and " + str(Bdim)) # # noms = ((A[1] * B[2] - A[2] * B[1]), (A[2] * B[0] - A[0] * B[2]), (A[0] * B[1] - A[1] * B[0])) # # return self.__class__(noms, denom) # # def reciprocal(self): # dim = self.dim # if dim != (3, 3): # raise Exception("FracVector.reciprocal: can only calculate reciprocal matrix for a 3,3 matrix. The dimension are:" + str(dim)) # noms = self.noms # # def det_noms(A): # return A[0][0] * A[1][1] * A[2][2] + A[0][1] * A[1][2] * A[2][0] + A[0][2] * A[1][0] * A[2][1] - A[0][2] * A[1][1] * A[2][0] - A[0][1] * A[1][0] * A[2][2] - A[0][0] * A[1][2] * A[2][1] # # def cross_noms(A, B): # return ((A[1] * B[2] - A[2] * B[1]), (A[2] * B[0] - A[0] * B[2]), (A[0] * B[1] - A[1] * B[0])) # # detnom = det_noms(noms) # denom = self.denom # # v1, v2, v3 = noms[0], noms[1], noms[2] # noms = (cross_noms(v2, v3), cross_noms(v1, v3), cross_noms(v1, v2)) # noms = self.nested_map(lambda x: x*denom, noms) # return self.__class__(noms, detnom) # # def metric_product(self, vecA, vecB): # """ # Returns the result of the metric product using the present square FracVector as the metric matrix. The same as # vecA*self*vecB.T(). # """ # # dimM = self.dim # dimA = vecA.dim # dimB = vecB.dim # # M = self.noms # A = vecA.noms # B = vecB.noms # # denom = vecA.denom * vecB.denom * self.denom # # l = dimM[0] # # if dimA != dimB or dimM != (l, l) or ((len(dimA) != 1 or len(dimB) != 1) and (dimA[1] != l or dimB[1] != l)): # raise Exception("ExactVector.metric_product: vectors not in right dimensions.") # # if len(dimA) == 1: # noms = sum([A[row] * M[row][col] * B[col] for row in range(l) for col in range(l)]) # else: # # Matrix * Matrix # noms = [sum([A[i][row] * M[row][col] * B[i][col] for row in range(l) for col in range(l)]) for i in range(dimA[0])] # # return self.__class__(noms, denom) # # def cos(self, prec=fractions.Fraction(1, 1000000), degrees=False, limit=False): # """Return a FracVector where every element is the cosine of the element in the source FracVector. # # prec = precision (should be set as a fraction) # limit = True requires the denominator to be smaller or equal to precision # """ # fracs = self._map_over_noms(lambda nom: frac_cos(fractions.Fraction(nom, self.denom), prec=prec, limit=limit, degrees=degrees)) # return self.create(fracs) # # def sin(self, prec=fractions.Fraction(1, 1000000), degrees=False, limit=False): # """Return a FracVector where every element is the sine of the element in the source FracVector. # # prec = precision (should be set as a fraction) # limit = True requires the denominator to be smaller or equal to precision # """ # fracs = self._map_over_noms(lambda nom: frac_sin(fractions.Fraction(nom, self.denom), prec=prec, limit=limit, degrees=degrees)) # return self.create(fracs) # # def exp(self, prec=fractions.Fraction(1, 1000000), limit=False): # """Return a FracVector where every element is the exponent of the element in the source FracVector. # # prec = precision (should be set as a fraction) # limit = True requires the denominator to be smaller or equal to precision # """ # fracs = self._map_over_noms(lambda nom: frac_exp(fractions.Fraction(nom, self.denom), prec=prec, limit=limit)) # return self.create(fracs) # # # #### Python special overloading # # def __getitem__(self, key): # if not isinstance(key, tuple): # key = (key,) # noms = tuple_slice(self.noms, key) # return self.__class__(noms, self.denom) # # def __setitem__(self, key, values): # raise Exception("FracVector is immutable, use MutableFracVector instead.") # # def __len__(self): # return len(self.noms) # # def __iter__(self): # try: # if self.dim != (): # for i in range(len(self.noms)): # yield self.__class__(self.noms[i], self.denom) # else: # yield self # except GeneratorExit: # pass # # def __mul__(self, other): # return self.mul(other) # # def __rmul__(self, other): # other = FracVector.create(other) # return other.mul(self) # # def __pow__(self, exp): # if exp == -1: # return self.inv() # if self.dim == (): # if exp == 0: # return self.__class__(1) # if exp > 0: # return self.__class__(self.nom**exp, self.denom**exp) # if exp < 0: # return self.__class__(self.denom**(-exp), self.nom**(-exp)) # if isinstance(exp, integer_types: # if exp == 0: # return self.eye(self.dim) # if exp > 0: # a = self # for _ in range(exp-1): # a = a.mul(self) # return a # if exp < 0: # a = self.inv() # for _ in range(-exp-1): # a = a.mul(self) # return a # else: # raise Exception("FracVector.__pow__: I do not know how to exponate a FracVector with "+str(exp)) # # def __div__(self, other): # if not isinstance(other, FracVector): # other = self.__class__.create(other) # frac = self.__class__(other.denom, other.nom) # return self.mul(frac) # # def __truediv__(self, other): # if not isinstance(other, FracVector): # other = self.__class__.create(other) # frac = self.__class__(other.denom, other.nom) # return self.mul(frac) # # def __add__(self, other): # noms, denom = self._map_binary_op_over_noms(operator.add, other) # return self.__class__(noms, denom) # # def __radd__(self, other): # noms, denom = self._map_binary_op_over_noms(operator.add, other) # return self.__class__(noms, denom) # # def __sub__(self, other): # noms, denom = self._map_binary_op_over_noms(operator.sub, other) # return self.__class__(noms, denom) # # def __rsub__(self, other): # minusself = -self # noms, denom = minusself._map_binary_op_over_noms(operator.sub, -other) # return self.__class__(noms, denom) # # def __repr__(self): # return self.__class__.__name__+"(" + repr(self.noms) + "," + repr(self.denom) + ")" # # def __str__(self): # return "(1/" + str(self.denom) + ")*" + str(self.noms) # # def __hash__(self): # return (self.denom, self.noms).__hash__() # # def __neg__(self): # return self.__class__(self._map_over_noms(operator.neg), self.denom) # # def __abs__(self): # return self.__class__(self._map_over_noms(operator.abs), self.denom) # # def __eq__(self, other): # """ # Important: the == operator between FracVectors tests for numerical equality. (I.e., numerically equal FracVectors # with different denoms are still equal.) # """ # # Note: somewhat optimized for speed # try: # if self.denom == other.denom: # return (self.noms == other.noms) # else: # (A, B, _) = self.set_common_denom(self, other) # return (A.noms == B.noms) # except AttributeError: # if other is None: # return False # # if not isinstance(other, FracVector): # other = self.__class__.create(other) # # if other.dim != self.dim: # return False # # if self.denom == other.denom: # return (self.noms == other.noms) # else: # (A, B, _) = self.set_common_denom(self, other) # return (A.noms == B.noms) # # def __ne__(self, other): # return not self.__eq__(other) # # def __lt__(self, other): # try: # return self.nom * other.denom < other.nom * self.denom # except AttributeError: # return self.nom < other * self.denom # # def __gt__(self, other): # try: # return self.nom * other.denom > other.nom * self.denom # except AttributeError: # return self.nom > other * self.denom # # def __le__(self, other): # return not self.__gt__(other) # # def __ge__(self, other): # return not self.__lt__(other) # # def __float__(self): # # This way of converting avoids many possible overflow errors # return float(fractions.Fraction(self.nom, self.denom)) # # def __int__(self): # #return self.nom // self.denom # return int(fractions.Fraction(self.nom, self.denom)) # # def __long__(self): # return long(fractions.Fraction(self.nom, self.denom)) # #return self.nom // self.denom # # def __index__(self): # v = self.simplify() # if v.denom != 1: # raise Exception("FracVector.__index__: cannot index with non-integer value.") # return v.nom # # def __complex__(self): # return complex(self.__float__()) # # def max(self): # """ # Return the maximum element across all dimensions in the FracVector. max(fracvector) works for a 1D vector. # """ # #largest_nom = nested_reduce(lambda x,y:x if x>y else y,self.noms) # #return self.__class__(largest_nom,self.denom) # return max(self.flatten()) # # def nargmax(self): # """ # Return a list of indices of all maximum elements across all dimensions in the FracVector. # """ # # idt = tuple_index(self.dim) # maxval = self.max() # indices = nested_reduce_levels(lambda x, y: x+[y] if self[y] == maxval else x, idt, len(self.dim), []) # return indices # # def argmax(self): # """ # Return the index of the maximum element across all dimensions in the FracVector. # """ # idt = tuple_index(self.dim) # flat_idt = nested_reduce_levels(lambda x, y: x + [y], idt, len(self.dim), initializer=[]) # return max(flat_idt, key=lambda i: self[i]) # # def min(self): # """ # Return the minimum element across all dimensions in the FracVector. max(fracvector) works for a 1D vector. # """ # #smallest_nom = nested_reduce(lambda x,y:x if x<y else y,self.noms) # #return self.__class__(smallest_nom,self.denom) # # return min(self.flatten()) # # def nargmin(self): # """ # Return a list of indices for all minimum elements across all dimensions in the FracVector. # """ # idt = tuple_index(self.dim) # minval = self.min() # indices = nested_reduce_levels(lambda x, y: x+[y] if self[y] == minval else x, idt, len(self.dim), []) # return indices # # def argmin(self): # """ # Return the index of the minimum element across all dimensions in the FracVector. # """ # idt = tuple_index(self.dim) # flat_idt = nested_reduce_levels(lambda x, y: x + [y], idt, len(self.dim), initializer=[]) # return min(flat_idt, key=lambda i: self[i]) # # #### Private methods # # def _map_over_noms(self, op, *others): # """ # Map an operation over all nominators # """ # othernoms = [x.noms for x in others] # if isinstance(self.noms, (tuple, list)): # return self.nested_map(op, self.noms, *othernoms) # else: # return op(self.noms, *othernoms) # # def _map_binary_op_over_noms(self, op, other): # """ # Put self and other on common denominator form, and then map a binary operator # over pairs of nominators, handling the cases where either of the operands is # a scalar (thus pairing it with every nominator) # """ # # A, B, denom = self.set_common_denom(self, other) # # Adim = A.dim # Bdim = B.dim # # if len(Adim) == 0: # if len(Bdim) == 0: # # scalar [op] scalar # result = op(A.nom, B.nom) # else: # # scalar [op] (Matrix or Vector) # result = B._map_over_noms(lambda x: op(A.nom, x)) # elif len(Bdim) == 0: # # [Matrix or Vector] op scalar # result = A._map_over_noms(lambda x: op(B.nom, x)) # else: # # Matrix op Matrix # result = A._map_over_noms(lambda x, y: op(x, y), B) # # return (result, denom) # # def _reduce_over_noms(self, op, initializer=None): # """ # Run a nested reduce operation over all nominators # """ # return nested_reduce(op, self.noms, initializer=initializer)
[docs]class Scalar(Vector): """ Baseclass for scalars """
# Utility functions
[docs]def nested_map_list(op, *ls): """ Map an operator over a nested list. (i.e., the same as the built-in map(), but works recursively on a nested list) """ if isinstance(ls[0], (tuple, list)): if len(ls[0]) == 0 or not isinstance(ls[0][0], (tuple, list)): return list(map(op, *ls)) return list(map(lambda *items: nested_map_list(op, *items), *ls)) return op(*ls)
[docs]def nested_map_fractions_list(op, *ls): """ Map an operator over a nested list, but checks every element for a method to_fractions() and uses this to further convert objects into lists of Fraction. """ if hasattr(ls[0], 'to_fractions'): ls = list(ls) ls[0] = ls[0].to_fractions() if not isinstance(ls[0], string_types): try: dummy = iter(ls[0]) return list(map(lambda *items: nested_map_fractions_list(op, *items), *ls)) except TypeError: pass return op(*ls)
[docs]def nested_reduce(op, l, initializer=None): """ Same as built-in reduce, but operates on a nested tuple/list/sequence. """ if isinstance(l, (tuple, list)): return reduce(lambda x, y: nested_reduce(op, y, initializer=x), l, initializer) else: return op(initializer, l)
[docs]def nested_reduce_levels(op, l, level=1, initializer=None): """ Same as built-in reduce, but operates on a nested tuple/list/sequence. """ if level == 1: return reduce(op, l, initializer) if isinstance(l, (tuple, list)): return reduce(lambda x, y: nested_reduce_levels(op, y, level-1, initializer=x), l, initializer) else: return op(initializer, l)
[docs]def nested_reduce_fractions(op, l, initializer=None): """ Same as built-in reduce, but operates on a nested tuple/list/sequence. Also checks every element for a method to_fractions() and uses this to further convert such elements to lists of fractions. """ if hasattr(l, 'to_fractions'): l = l.to_fractions() if not isinstance(l, string_types): try: dummy = iter(l) return reduce(lambda x, y: nested_reduce_fractions(op, y, initializer=x), l, initializer) except TypeError: pass return op(initializer, l)
[docs]def tuple_slice(l, key): """ Given a python slice (i.e., what you get to __getitem__ when you write A[3:2]), cut out the relevant nested tuple. """ if isinstance(key[0], (integer_types + (slice,))): slicedlist = l[key[0]] else: slicedlist = tuple([l[i] for i in key[0]]) cdr = key[1:] if len(cdr) > 0: if isinstance(key[0], slice): return tuple(tuple_slice(slicedlist[i], cdr) for i in range(len(slicedlist))) else: return tuple_slice(slicedlist, cdr) return slicedlist
[docs]def tuple_index(dims, uppidx=()): """ Create a nested tuple where every element is a tuple indicating the position of that tuple """ if dims == (): if len(uppidx) == 1: return uppidx[0] else: return uppidx else: neweye = [] lastdim = dims[0] lowerdims = dims[1:] for i in range(lastdim): neweye += [tuple_index(lowerdims, uppidx + (i,))] return neweye
[docs]def tuple_zeros(dims): """ Create a netsted tuple with the given dimensions filled with zeroes """ if dims == (): return 0 else: neweye = [] lastdim = dims[0] lowerdims = dims[1:] for _ in range(lastdim): neweye += [tuple_zeros(lowerdims)] return neweye
[docs]def tuple_random(dims, minval, maxval): """ Create a nested tuple with the given dimensions filled with random numbers between minval and maxval """ if dims == (): return random.randint(minval, maxval) else: neweye = [] lastdim = dims[0] lowerdims = dims[1:] for _ in range(lastdim): neweye += [tuple_random(lowerdims, minval, maxval)] return neweye
[docs]def tuple_eye(dims, onepos=0): """ Create a matrix with the given dimensions and 1 on the diagonal """ if dims == (): return 1 if len(dims) == 1: neweye = [0]*dims[0] neweye[onepos] = 1 else: neweye = [] lastdim = dims[-1] nextdim = dims[-2] lowerdims = dims[:-1] for i in range(lastdim): neweye += [tuple_eye(lowerdims, onepos=(i*nextdim//lastdim))] return neweye
[docs]class MutableVector(object): pass
# The following copyright notice applies to frac_log, frac_tan, frac_asin, frac_acos, frac_atan2, sinh, cosh, tanh # #Copyright (c) 2006 Brian Beck <exogen@gmail.com>, # Christopher Hesse <christopher.hesse@gmail.com> # #Permission is hereby granted, free of charge, to any person obtaining a copy of #this software and associated documentation files (the "Software"), to deal in #the Software without restriction, including without limitation the rights to #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies #of the Software, and to permit persons to whom the Software is furnished to do #so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE.
[docs]def main(): pass
if __name__ == "__main__": main()