#
# 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.
if __name__ == "__main__":
main()