From 305d8487132cfe9bc6862080b8864f55236ad5d2 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Jun 2015 09:16:24 +0200 Subject: [PATCH 001/191] Some work on sidest and first steps in cluster algebra --- cluster_algebra.py | 229 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 cluster_algebra.py diff --git a/cluster_algebra.py b/cluster_algebra.py new file mode 100644 index 00000000000..24ad71fa562 --- /dev/null +++ b/cluster_algebra.py @@ -0,0 +1,229 @@ +from sage.structure.sage_object import SageObject +from sage.matrix.constructor import identity_matrix + +class ClusterAlgebra(SageObject): + + def __init__(self, data): + if isinstance(data, Matrix): + self._B0 = copy(data) + self._m = self._B0.nrows() + self._n = self._B0.ncols() + B = copy(self._B0[:self._n,:self._n]) + if not B.is_skew_symmetrizable(positive=True): + raise ValueError('data must have skew-symmetrizable principal part.') + self.current_seed = Seed(B) + self._U = PolynomialRing(QQ,['u%s'%i for i in xrange(self._n)]) + self._F = dict([ (self.current_seed.g_vector(i),self._U(1)) for i in xrange(self._n) ]) + self._R = LaurentPolynomialRing(QQ,['x%s'%i for i in xrange(self._m)]) + + def col_to_y(j): + return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._n,self._m)]) + self._y = dict([ (self._U.gen(j),col_to_y(j)) for j in xrange(self._n)]) + + def col_to_yhat(j): + return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._m)]) + self._yhat = dict([ (self._U.gen(j),col_to_yhat(j)) for j in xrange(self._n)]) + + # should keep track of the seed too ? + + # at the moment we only deal with b_matrices + else: + raise NotImplementedError('At the moment we only deal with matrices.') + + def __copy__(self): + other = ClusterAlgebra(self._B0) + other.current_seed = copy(self.current_seed) + other._F = copy(self._F) + + def mutate(self, sequence, inplace=True, mutate_F=True): + if not isinstance(mutate_F, bool): + raise ValueError('mutate_F must be boolean.') + + if not isinstance(inplace, bool): + raise ValueError('inplace must be boolean.') + if inplace: + seed = self.current_seed + else: + seed = copy(self.current_seed) + + if sequence in xrange(seed._n): + seq = iter([sequence]) + else: + seq = iter(sequence) + + for k in seq: + if k not in xrange(seed._n): + raise ValueError('Cannot mutate in direction' + str(k) + '.') + + # G-matrix + J = identity_matrix(seed._n) + if any(x > 0 for x in seed._C.column(k)): + eps = +1 + else: + eps = -1 + for j in xrange(seed._n): + J[j,k] += max(0, -eps*seed._B[j,k]) + J[k,k] = -1 + seed._G = seed._G*J + + # F-polynomials + if mutating_F: + gvect = tuple(seed._G.column(k)) + if not self._F_dict.has_key(gvect): + self._F_dict.setdefault(gvect,self._mutated_F(k)) + seed._F[k] = self._F_dict[gvect] + + # C-matrix + J = identity_matrix(seed._n) + if any(x > 0 for x in seed._C.column(k)): + eps = +1 + else: + eps = -1 + for j in xrange(seed._n): + J[k,j] += max(0, eps*seed._B[k,j]) + J[k,k] = -1 + seed._C = seed._C*J + + # B-matrix + seed._B.mutate(k) + + if not inplace: + return seed + +class Seed(SageObject): + + def __init__(self, B): + n = B.ncols() + self._B = copy(B) + self._C = identity_matrix(n) + self._G = identity_matrix(n) + self._path = [] + + def __copy__(self): + other = Seed(self._B) + other._C = copy(self._C) + other._G = copy(self._G) + other._path = copy(self._path) + return other + + def g_vectors(self): + return tuple(self._G.columns()) + + def g_vector(self, i): + return self._G.columns()[i] + + + + +####################### OLD ################### + +import time +from sage.matrix.matrix import Matrix + + + + def _mutated_F(self, k): + pos = self._U(1) + neg = self._U(1) + for j in xrange(self._n): + if self._C[j,k] > 0: + pos *= self._U.gen(j)**self._C[j,k] + else: + neg *= self._U.gen(j)**(-self._C[j,k]) + if self._B[j,k] > 0: + pos *= self._F[j]**self._B[j,k] + else: + neg *= self._F[j]**(-self._B[j,k]) + return (pos+neg)//self._F[k] + + + def find_cluster_variables(self, glist_tofind=[], num_mutations=infinity): + MCI = self.mutation_class_iter() + mutation_counter = 0 + ## the last check should be done more efficiently + while mutation_counter < num_mutations and (glist_tofind == [] or not Set(glist_tofind).issubset(Set(self._F_dict.keys()))): + try: + MCI.next() + except: + break + mutation_counter += 1 + print "Found after "+str(mutation_counter)+" mutations." + + + def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, up_to_equivalence=True, only_sink_source=False): + depth_counter = 0 + n = self._n + timer = time.time() + if return_paths: + yield (self,[]) + else: + yield self + if up_to_equivalence: + cl = Set(self._G.columns()) + else: + cl = tuple(self._G.columns()) + clusters = {} + clusters[ cl ] = [ self, range(n), [] ] + gets_bigger = True + if show_depth: + timer2 = time.time() + dc = str(depth_counter) + dc += ' ' * (5-len(dc)) + nr = str(len(clusters)) + nr += ' ' * (10-len(nr)) + print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) + while gets_bigger and depth_counter < depth: + gets_bigger = False + keys = clusters.keys() + for key in keys: + sd = clusters[key] + while sd[1]: + i = sd[1].pop() + if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): + sd2 = sd[0].mutate( i, inplace=False ) + if up_to_equivalence: + cl2 = Set(sd2._G.columns()) + else: + cl2 = tuple(sd2._G.columns()) + if cl2 in clusters: + if not up_to_equivalence and i in clusters[cl2][1]: + clusters[cl2][1].remove(i) + else: + gets_bigger = True + if only_sink_source: + orbits = range(n) + else: + orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] + + clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] + if return_paths: + yield (sd2,clusters[cl2][2]) + else: + yield sd2 + depth_counter += 1 + if show_depth and gets_bigger: + timer2 = time.time() + dc = str(depth_counter) + dc += ' ' * (5-len(dc)) + nr = str(len(clusters)) + nr += ' ' * (10-len(nr)) + print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) + + + def cluster_variable(self, k): + if k in range(self._n): + g_mon = prod([self._R.gen(i)**self._G[i,k] for i in xrange(self._n)]) + F_std = self._F[k].subs(self._yhat) + F_trop = self._F[k].subs(self._y).denominator() + return g_mon*F_std*F_trop + if isinstance(k,tuple): + if not k in self._F_dict.keys(): + self.find_cluster_variables([k]) + g_mon = prod([self._R.gen(i)**k[i] for i in xrange(self._n)]) + F_std = self._F_dict[k].subs(self._yhat) + F_trop = self._F_dict[k].subs(self._y).denominator() + return g_mon*F_std*F_trop + + + + From 8f0b02d82ca71f7700a3141068d0c9629aeb58bb Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 23 Jul 2015 20:28:31 +0200 Subject: [PATCH 002/191] First big push in the right direction --- cluster_algebra.py | 600 +++++++++++++++++++++++++++++---------------- 1 file changed, 391 insertions(+), 209 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 24ad71fa562..006c77a5a48 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -1,229 +1,411 @@ +from sage.structure.element_wrapper import ElementWrapper from sage.structure.sage_object import SageObject -from sage.matrix.constructor import identity_matrix - -class ClusterAlgebra(SageObject): - - def __init__(self, data): - if isinstance(data, Matrix): - self._B0 = copy(data) - self._m = self._B0.nrows() - self._n = self._B0.ncols() - B = copy(self._B0[:self._n,:self._n]) - if not B.is_skew_symmetrizable(positive=True): - raise ValueError('data must have skew-symmetrizable principal part.') - self.current_seed = Seed(B) - self._U = PolynomialRing(QQ,['u%s'%i for i in xrange(self._n)]) - self._F = dict([ (self.current_seed.g_vector(i),self._U(1)) for i in xrange(self._n) ]) - self._R = LaurentPolynomialRing(QQ,['x%s'%i for i in xrange(self._m)]) - - def col_to_y(j): - return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._n,self._m)]) - self._y = dict([ (self._U.gen(j),col_to_y(j)) for j in xrange(self._n)]) - - def col_to_yhat(j): - return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._m)]) - self._yhat = dict([ (self._U.gen(j),col_to_yhat(j)) for j in xrange(self._n)]) - - # should keep track of the seed too ? - - # at the moment we only deal with b_matrices - else: - raise NotImplementedError('At the moment we only deal with matrices.') - - def __copy__(self): - other = ClusterAlgebra(self._B0) - other.current_seed = copy(self.current_seed) - other._F = copy(self._F) - - def mutate(self, sequence, inplace=True, mutate_F=True): - if not isinstance(mutate_F, bool): - raise ValueError('mutate_F must be boolean.') - - if not isinstance(inplace, bool): - raise ValueError('inplace must be boolean.') - if inplace: - seed = self.current_seed - else: - seed = copy(self.current_seed) - - if sequence in xrange(seed._n): - seq = iter([sequence]) - else: - seq = iter(sequence) - - for k in seq: - if k not in xrange(seed._n): - raise ValueError('Cannot mutate in direction' + str(k) + '.') - - # G-matrix - J = identity_matrix(seed._n) - if any(x > 0 for x in seed._C.column(k)): - eps = +1 - else: - eps = -1 - for j in xrange(seed._n): - J[j,k] += max(0, -eps*seed._B[j,k]) - J[k,k] = -1 - seed._G = seed._G*J - - # F-polynomials - if mutating_F: - gvect = tuple(seed._G.column(k)) - if not self._F_dict.has_key(gvect): - self._F_dict.setdefault(gvect,self._mutated_F(k)) - seed._F[k] = self._F_dict[gvect] - - # C-matrix - J = identity_matrix(seed._n) - if any(x > 0 for x in seed._C.column(k)): - eps = +1 - else: - eps = -1 - for j in xrange(seed._n): - J[k,j] += max(0, eps*seed._B[k,j]) - J[k,k] = -1 - seed._C = seed._C*J - - # B-matrix - seed._B.mutate(k) - - if not inplace: - return seed - -class Seed(SageObject): - - def __init__(self, B): - n = B.ncols() +from sage.structure.parent import Parent +from sage.structure.parent_gens import normalize_names +from sage.rings.integer_ring import ZZ + +class ClusterAlgebraElement(ElementWrapper): + + # This function needs to be removed once AdditiveMagmas.Subobjects + # implements _add_ + def _add_(self, other): + return self.parent().retract(self.lift() + other.lift()) + +class ClusterAlgebraSeed(SageObject): + + def __init__(self, B, C, G, parent): self._B = copy(B) - self._C = identity_matrix(n) - self._G = identity_matrix(n) - self._path = [] + self._C = copy(C) + self._G = copy(G) + self._parent = parent def __copy__(self): - other = Seed(self._B) + other = type(self).__new__(type(self)) + other._B = copy(self._B) other._C = copy(self._C) other._G = copy(self._G) - other._path = copy(self._path) + other._parent = self._parent return other - def g_vectors(self): - return tuple(self._G.columns()) - - def g_vector(self, i): - return self._G.columns()[i] + def parent(self): + return self._parent + def F_polynomial(self, j): + gvect = tuple(self._G.column(j)) + return self.parent().F_polynomial(gvect) + + #maybe this alias sould be removed + F = F_polynomial + + def cluster_variable(self, j): + gvect = tuple(self._G.column(j)) + return self.parent().cluster_variable(gvect) + def g_vector(self, j): + return tuple(self._G.column(j)) + def g_matrix(self): + return copy(self._G) -####################### OLD ################### - -import time -from sage.matrix.matrix import Matrix - + def c_vector(self, j): + return tuple(self._C.column(j)) + def c_matrix(self): + return copy(self._C) + + def mutate(self, k, inplace=True, mutating_F=True): + if inplace: + seed = self + else: + seed = copy(self) + + n = self.parent().rk() + + if k not in xrange(n): + raise ValueError('Cannot mutate in direction' + str(k) + '.') + + # find sign of k-th c-vector + if any(x > 0 for x in seed._C.column(k)): + eps = +1 + else: + eps = -1 + + # store the g-vector to be mutated in case we are mutating also F-polynomials + old_gvect = tuple(seed._G.column(k)) + + # mutate G-matrix + J = identity_matrix(n) + for j in xrange(n): + J[j,k] += max(0, -eps*seed._B[j,k]) + J[k,k] = -1 + seed._G = seed._G*J + + # F-polynomials + if mutating_F: + gvect = tuple(seed._G.column(k)) + if not self.parent()._F_dict.has_key(gvect): + self.parent()._F_dict.setdefault(gvect, self._mutated_F(k, old_gvect)) + + # C-matrix + J = identity_matrix(n) + for j in xrange(n): + J[k,j] += max(0, eps*seed._B[k,j]) + J[k,k] = -1 + seed._C = seed._C*J + + # B-matrix + seed._B.mutate(k) + + if not inplace: + return seed - def _mutated_F(self, k): - pos = self._U(1) - neg = self._U(1) - for j in xrange(self._n): + def _mutated_F(self, k, old_gvect): + alg = self.parent() + pos = alg._U(1) + neg = alg._U(1) + for j in xrange(alg.rk()): if self._C[j,k] > 0: - pos *= self._U.gen(j)**self._C[j,k] + pos *= alg._U.gen(j)**self._C[j,k] else: - neg *= self._U.gen(j)**(-self._C[j,k]) + neg *= alg._U.gen(j)**(-self._C[j,k]) if self._B[j,k] > 0: - pos *= self._F[j]**self._B[j,k] - else: - neg *= self._F[j]**(-self._B[j,k]) - return (pos+neg)//self._F[k] - - - def find_cluster_variables(self, glist_tofind=[], num_mutations=infinity): - MCI = self.mutation_class_iter() - mutation_counter = 0 - ## the last check should be done more efficiently - while mutation_counter < num_mutations and (glist_tofind == [] or not Set(glist_tofind).issubset(Set(self._F_dict.keys()))): - try: - MCI.next() - except: - break - mutation_counter += 1 - print "Found after "+str(mutation_counter)+" mutations." - - - def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, up_to_equivalence=True, only_sink_source=False): - depth_counter = 0 - n = self._n - timer = time.time() - if return_paths: - yield (self,[]) - else: - yield self - if up_to_equivalence: - cl = Set(self._G.columns()) - else: - cl = tuple(self._G.columns()) - clusters = {} - clusters[ cl ] = [ self, range(n), [] ] - gets_bigger = True - if show_depth: - timer2 = time.time() - dc = str(depth_counter) - dc += ' ' * (5-len(dc)) - nr = str(len(clusters)) - nr += ' ' * (10-len(nr)) - print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) - while gets_bigger and depth_counter < depth: - gets_bigger = False - keys = clusters.keys() - for key in keys: - sd = clusters[key] - while sd[1]: - i = sd[1].pop() - if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): - sd2 = sd[0].mutate( i, inplace=False ) - if up_to_equivalence: - cl2 = Set(sd2._G.columns()) - else: - cl2 = tuple(sd2._G.columns()) - if cl2 in clusters: - if not up_to_equivalence and i in clusters[cl2][1]: - clusters[cl2][1].remove(i) - else: - gets_bigger = True - if only_sink_source: - orbits = range(n) - else: - orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] - - clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] - if return_paths: - yield (sd2,clusters[cl2][2]) - else: - yield sd2 - depth_counter += 1 - if show_depth and gets_bigger: - timer2 = time.time() - dc = str(depth_counter) - dc += ' ' * (5-len(dc)) - nr = str(len(clusters)) - nr += ' ' * (10-len(nr)) - print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) - - - def cluster_variable(self, k): - if k in range(self._n): - g_mon = prod([self._R.gen(i)**self._G[i,k] for i in xrange(self._n)]) - F_std = self._F[k].subs(self._yhat) - F_trop = self._F[k].subs(self._y).denominator() - return g_mon*F_std*F_trop - if isinstance(k,tuple): - if not k in self._F_dict.keys(): - self.find_cluster_variables([k]) - g_mon = prod([self._R.gen(i)**k[i] for i in xrange(self._n)]) - F_std = self._F_dict[k].subs(self._yhat) - F_trop = self._F_dict[k].subs(self._y).denominator() - return g_mon*F_std*F_trop + pos *= self.F_polynomial(j)**self._B[j,k] + elif self._B[j,k] <0: + neg *= self.F_polynomial(j)**(-self._B[j,k]) + return (pos+neg)//alg.F_polynomial(old_gvect) + + def mutation_sequence(self, sequence, inplace=True, mutating_F=True): + seq = iter(sequence) + + if inplace: + seed = self + else: + seed = self.mutate(seq.next(), inplace=False, mutating_F=mutating_F) + + for k in seq: + seed.mutate(k, inplace=True, mutating_F=mutating_F) + if not inplace: + return seed - +class ClusterAlgebra(Parent): + + Element = ClusterAlgebraElement + + def __init__(self, B0, scalars=ZZ): + self._B0 = copy(B0) + self._n = self._B0.ncols() + self._m = self._B0.nrows()-self._n + + # maybe here scalars can be replaced with just ZZ + self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(self._n)]) + self._F_dict = dict([ (tuple(v) ,self._U(1)) for v in identity_matrix(self._n).columns() ]) + + self.Seed = ClusterAlgebraSeed(self._B0[:self._n,:self._n], identity_matrix(self._n), identity_matrix(self._n), self) + + names = normalize_names(self._n, 'x') + base = LaurentPolynomialRing(scalars,names) + names += normalize_names(self._m, 'y') + self._ambient = LaurentPolynomialRing(scalars,names) + Parent.__init__(self, base=base, category=CommutativeAlgebras(scalars).Subobjects()) + + def _repr_(self): + return "Cluster Algebra of rank %s"%self.rk() + + def rk(self): + return self._n + + def F_polynomial(self, gvect): + gvect = tuple(gvect) + try: + return self._F_dict[gvect] + except: + # TODO: improve this error message + raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") + + # maybe this alias could be removed + F = F_polynomial + + def cluster_variable(self, gvect): + raise NotImplementedError("I will do this, I swear it on the Beatles.") + + def ambient(self): + return self._ambient + + def lift(self, x): + return x.value + + def retract(self, x): + return self(x) + + +#from sage.structure.sage_object import SageObject +#from sage.matrix.constructor import identity_matrix +# +#class ClusterAlgebra(SageObject): +# +# def __init__(self, data): +# if isinstance(data, Matrix): +# self._B0 = copy(data) +# self._m = self._B0.nrows() +# self._n = self._B0.ncols() +# B = copy(self._B0[:self._n,:self._n]) +# if not B.is_skew_symmetrizable(positive=True): +# raise ValueError('data must have skew-symmetrizable principal part.') +# self.current_seed = Seed(B) +# self._U = PolynomialRing(QQ,['u%s'%i for i in xrange(self._n)]) +# self._F = dict([ (self.current_seed.g_vector(i),self._U(1)) for i in xrange(self._n) ]) +# self._R = LaurentPolynomialRing(QQ,['x%s'%i for i in xrange(self._m)]) +# +# def col_to_y(j): +# return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._n,self._m)]) +# self._y = dict([ (self._U.gen(j),col_to_y(j)) for j in xrange(self._n)]) +# +# def col_to_yhat(j): +# return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._m)]) +# self._yhat = dict([ (self._U.gen(j),col_to_yhat(j)) for j in xrange(self._n)]) +# +# # should keep track of the seed too ? +# +# # at the moment we only deal with b_matrices +# else: +# raise NotImplementedError('At the moment we only deal with matrices.') +# +# def __copy__(self): +# other = ClusterAlgebra(self._B0) +# other.current_seed = copy(self.current_seed) +# other._F = copy(self._F) +# +# def mutate(self, sequence, inplace=True, mutate_F=True): +# if not isinstance(mutate_F, bool): +# raise ValueError('mutate_F must be boolean.') +# +# if not isinstance(inplace, bool): +# raise ValueError('inplace must be boolean.') +# if inplace: +# seed = self.current_seed +# else: +# seed = copy(self.current_seed) +# +# if sequence in xrange(seed._n): +# seq = iter([sequence]) +# else: +# seq = iter(sequence) +# +# for k in seq: +# if k not in xrange(seed._n): +# raise ValueError('Cannot mutate in direction' + str(k) + '.') +# +# # G-matrix +# J = identity_matrix(seed._n) +# if any(x > 0 for x in seed._C.column(k)): +# eps = +1 +# else: +# eps = -1 +# for j in xrange(seed._n): +# J[j,k] += max(0, -eps*seed._B[j,k]) +# J[k,k] = -1 +# seed._G = seed._G*J +# +# # F-polynomials +# if mutating_F: +# gvect = tuple(seed._G.column(k)) +# if not self._F_dict.has_key(gvect): +# self._F_dict.setdefault(gvect,self._mutated_F(k)) +# seed._F[k] = self._F_dict[gvect] +# +# # C-matrix +# J = identity_matrix(seed._n) +# if any(x > 0 for x in seed._C.column(k)): +# eps = +1 +# else: +# eps = -1 +# for j in xrange(seed._n): +# J[k,j] += max(0, eps*seed._B[k,j]) +# J[k,k] = -1 +# seed._C = seed._C*J +# +# # B-matrix +# seed._B.mutate(k) +# +# if not inplace: +# return seed +# +#class Seed(SageObject): +# +# def __init__(self, B): +# n = B.ncols() +# self._B = copy(B) +# self._C = identity_matrix(n) +# self._G = identity_matrix(n) +# self._path = [] +# +# def __copy__(self): +# other = Seed(self._B) +# other._C = copy(self._C) +# other._G = copy(self._G) +# other._path = copy(self._path) +# return other +# +# def g_vectors(self): +# return tuple(self._G.columns()) +# +# def g_vector(self, i): +# return self._G.columns()[i] +# +# +# +# +######################## OLD ################### +# +#import time +#from sage.matrix.matrix import Matrix +# +# +# +# def _mutated_F(self, k): +# pos = self._U(1) +# neg = self._U(1) +# for j in xrange(self._n): +# if self._C[j,k] > 0: +# pos *= self._U.gen(j)**self._C[j,k] +# else: +# neg *= self._U.gen(j)**(-self._C[j,k]) +# if self._B[j,k] > 0: +# pos *= self._F[j]**self._B[j,k] +# else: +# neg *= self._F[j]**(-self._B[j,k]) +# return (pos+neg)//self._F[k] +# +# +# def find_cluster_variables(self, glist_tofind=[], num_mutations=infinity): +# MCI = self.mutation_class_iter() +# mutation_counter = 0 +# ## the last check should be done more efficiently +# while mutation_counter < num_mutations and (glist_tofind == [] or not Set(glist_tofind).issubset(Set(self._F_dict.keys()))): +# try: +# MCI.next() +# except: +# break +# mutation_counter += 1 +# print "Found after "+str(mutation_counter)+" mutations." +# +# +# def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, up_to_equivalence=True, only_sink_source=False): +# depth_counter = 0 +# n = self._n +# timer = time.time() +# if return_paths: +# yield (self,[]) +# else: +# yield self +# if up_to_equivalence: +# cl = Set(self._G.columns()) +# else: +# cl = tuple(self._G.columns()) +# clusters = {} +# clusters[ cl ] = [ self, range(n), [] ] +# gets_bigger = True +# if show_depth: +# timer2 = time.time() +# dc = str(depth_counter) +# dc += ' ' * (5-len(dc)) +# nr = str(len(clusters)) +# nr += ' ' * (10-len(nr)) +# print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) +# while gets_bigger and depth_counter < depth: +# gets_bigger = False +# keys = clusters.keys() +# for key in keys: +# sd = clusters[key] +# while sd[1]: +# i = sd[1].pop() +# if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): +# sd2 = sd[0].mutate( i, inplace=False ) +# if up_to_equivalence: +# cl2 = Set(sd2._G.columns()) +# else: +# cl2 = tuple(sd2._G.columns()) +# if cl2 in clusters: +# if not up_to_equivalence and i in clusters[cl2][1]: +# clusters[cl2][1].remove(i) +# else: +# gets_bigger = True +# if only_sink_source: +# orbits = range(n) +# else: +# orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] +# +# clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] +# if return_paths: +# yield (sd2,clusters[cl2][2]) +# else: +# yield sd2 +# depth_counter += 1 +# if show_depth and gets_bigger: +# timer2 = time.time() +# dc = str(depth_counter) +# dc += ' ' * (5-len(dc)) +# nr = str(len(clusters)) +# nr += ' ' * (10-len(nr)) +# print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) +# +# +# def cluster_variable(self, k): +# if k in range(self._n): +# g_mon = prod([self._R.gen(i)**self._G[i,k] for i in xrange(self._n)]) +# F_std = self._F[k].subs(self._yhat) +# F_trop = self._F[k].subs(self._y).denominator() +# return g_mon*F_std*F_trop +# if isinstance(k,tuple): +# if not k in self._F_dict.keys(): +# self.find_cluster_variables([k]) +# g_mon = prod([self._R.gen(i)**k[i] for i in xrange(self._n)]) +# F_std = self._F_dict[k].subs(self._yhat) +# F_trop = self._F_dict[k].subs(self._y).denominator() +# return g_mon*F_std*F_trop +# +# +# +# From 6a1c43dd544dc3363704a35e2a66a9e377b93f8f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 12:55:53 +0200 Subject: [PATCH 003/191] Second big push. Ready to share with Dylan --- cluster_algebra.py | 395 +++++++++++++++++---------------------------- 1 file changed, 147 insertions(+), 248 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 006c77a5a48..56015392a8b 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -1,7 +1,7 @@ +import time from sage.structure.element_wrapper import ElementWrapper from sage.structure.sage_object import SageObject from sage.structure.parent import Parent -from sage.structure.parent_gens import normalize_names from sage.rings.integer_ring import ZZ class ClusterAlgebraElement(ElementWrapper): @@ -11,6 +11,26 @@ class ClusterAlgebraElement(ElementWrapper): def _add_(self, other): return self.parent().retract(self.lift() + other.lift()) + # HACK: LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field + def _lift_to_field(self): + return self.parent().ambient_field()(1)*self.value + + # this function is quite disgusting but at least it works for any element of + # the algebra, can we do better? + def d_vector(self): + factors = self._lift_to_field().factors() + initial = [] + non_initial = [] + [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] + initial = prod(initial+[self.parent().ambient_field()(1)]).numerator() + non_initial = prod(non_initial+[self.parent().ambient_field()(1)]).denominator() + v1 = vector(non_initial.exponents()[0][:self.parent().rk()]) + v2 = vector(initial.exponents()[0][:self.parent().rk()]) + return tuple(v1-v2) + + def g_vector(self): + raise NotImplementederror("This should be computed by substitution") + class ClusterAlgebraSeed(SageObject): def __init__(self, B, C, G, parent): @@ -119,7 +139,7 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): if inplace: seed = self - else: + else: seed = self.mutate(seq.next(), inplace=False, mutating_F=mutating_F) for k in seq: @@ -127,37 +147,99 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): if not inplace: return seed - + + def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, only_sink_source=False): + depth_counter = 0 + n = self.parent().rk() + timer = time.time() + if return_paths: + yield (self,[]) + else: + yield self + cl = Set(self._G.columns()) + clusters = {} + clusters[ cl ] = [ self, range(n), [] ] + gets_bigger = True + if show_depth: + timer2 = time.time() + dc = str(depth_counter) + dc += ' ' * (5-len(dc)) + nr = str(len(clusters)) + nr += ' ' * (10-len(nr)) + print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) + while gets_bigger and depth_counter < depth: + gets_bigger = False + keys = clusters.keys() + for key in keys: + sd = clusters[key] + while sd[1]: + i = sd[1].pop() + if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): + sd2 = sd[0].mutate( i, inplace=False ) + cl2 = Set(sd2._G.columns()) + if cl2 in clusters: + if i in clusters[cl2][1]: + clusters[cl2][1].remove(i) + else: + gets_bigger = True + if only_sink_source: + orbits = range(n) + else: + orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] + + clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] + if return_paths: + yield (sd2,clusters[cl2][2]) + else: + yield sd2 + depth_counter += 1 + if show_depth and gets_bigger: + timer2 = time.time() + dc = str(depth_counter) + dc += ' ' * (5-len(dc)) + nr = str(len(clusters)) + nr += ' ' * (10-len(nr)) + print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) + class ClusterAlgebra(Parent): Element = ClusterAlgebraElement def __init__(self, B0, scalars=ZZ): - self._B0 = copy(B0) - self._n = self._B0.ncols() - self._m = self._B0.nrows()-self._n + # Temporary variables + n = B0.ncols() + m = B0.nrows() + I = identity_matrix(n) # maybe here scalars can be replaced with just ZZ - self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(self._n)]) - self._F_dict = dict([ (tuple(v) ,self._U(1)) for v in identity_matrix(self._n).columns() ]) + self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) + self._F_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) - self.Seed = ClusterAlgebraSeed(self._B0[:self._n,:self._n], identity_matrix(self._n), identity_matrix(self._n), self) + self.Seed = ClusterAlgebraSeed(B0[:n,:n], I, I, self) + + base = LaurentPolynomialRing(scalars, 'x', n) + # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ + Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) + + self._ambient = LaurentPolynomialRing(scalars, 'x', m) + self._ambient_field = self._ambient.fraction_field() + + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) + self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(m)])) for j in xrange(n)]) - names = normalize_names(self._n, 'x') - base = LaurentPolynomialRing(scalars,names) - names += normalize_names(self._m, 'y') - self._ambient = LaurentPolynomialRing(scalars,names) - Parent.__init__(self, base=base, category=CommutativeAlgebras(scalars).Subobjects()) + self._B0 = copy(B0) + self._n = n + self._m = m def _repr_(self): return "Cluster Algebra of rank %s"%self.rk() def rk(self): return self._n - + + @make_hashable def F_polynomial(self, gvect): - gvect = tuple(gvect) try: return self._F_dict[gvect] except: @@ -167,245 +249,62 @@ def F_polynomial(self, gvect): # maybe this alias could be removed F = F_polynomial + @make_hashable + @cached_method def cluster_variable(self, gvect): - raise NotImplementedError("I will do this, I swear it on the Beatles.") + if not gvect in self._F_dict.keys(): + raise ValueError("This Cluster Variable has not been computed yet.") + g_mon = prod([self.ambient().gen(i)**gvect[i] for i in xrange(self.rk())]) + F_std = self.F_polynomial(gvect).subs(self._yhat) + # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field + F_trop = self.ambient_field()(self.F_polynomial(gvect).subs(self._y)).denominator() + return self.retract(g_mon*F_std*F_trop) + + @make_hashable + def find_cluster_variable(self, gvect, num_mutations=infinity): + MCI = self.Seed.mutation_class_iter() + mutation_counter = -1 + ## the last check should be done more efficiently + while mutation_counter < num_mutations and gvect not in self._F_dict.keys(): + try: + MCI.next() + except: + raise ValueError("Could not find a cluster variable with g-vector %s"%str(gvect)) + mutation_counter += 1 + print "Found after "+str(mutation_counter)+" mutations." def ambient(self): return self._ambient + def ambient_field(self): + return self._ambient_field + def lift(self, x): return x.value - + def retract(self, x): return self(x) - -#from sage.structure.sage_object import SageObject -#from sage.matrix.constructor import identity_matrix -# -#class ClusterAlgebra(SageObject): -# -# def __init__(self, data): -# if isinstance(data, Matrix): -# self._B0 = copy(data) -# self._m = self._B0.nrows() -# self._n = self._B0.ncols() -# B = copy(self._B0[:self._n,:self._n]) -# if not B.is_skew_symmetrizable(positive=True): -# raise ValueError('data must have skew-symmetrizable principal part.') -# self.current_seed = Seed(B) -# self._U = PolynomialRing(QQ,['u%s'%i for i in xrange(self._n)]) -# self._F = dict([ (self.current_seed.g_vector(i),self._U(1)) for i in xrange(self._n) ]) -# self._R = LaurentPolynomialRing(QQ,['x%s'%i for i in xrange(self._m)]) -# -# def col_to_y(j): -# return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._n,self._m)]) -# self._y = dict([ (self._U.gen(j),col_to_y(j)) for j in xrange(self._n)]) -# -# def col_to_yhat(j): -# return prod([self._R.gen(i)**self._B0[i,j] for i in xrange(self._m)]) -# self._yhat = dict([ (self._U.gen(j),col_to_yhat(j)) for j in xrange(self._n)]) -# -# # should keep track of the seed too ? -# -# # at the moment we only deal with b_matrices -# else: -# raise NotImplementedError('At the moment we only deal with matrices.') -# -# def __copy__(self): -# other = ClusterAlgebra(self._B0) -# other.current_seed = copy(self.current_seed) -# other._F = copy(self._F) -# -# def mutate(self, sequence, inplace=True, mutate_F=True): -# if not isinstance(mutate_F, bool): -# raise ValueError('mutate_F must be boolean.') -# -# if not isinstance(inplace, bool): -# raise ValueError('inplace must be boolean.') -# if inplace: -# seed = self.current_seed -# else: -# seed = copy(self.current_seed) -# -# if sequence in xrange(seed._n): -# seq = iter([sequence]) -# else: -# seq = iter(sequence) -# -# for k in seq: -# if k not in xrange(seed._n): -# raise ValueError('Cannot mutate in direction' + str(k) + '.') -# -# # G-matrix -# J = identity_matrix(seed._n) -# if any(x > 0 for x in seed._C.column(k)): -# eps = +1 -# else: -# eps = -1 -# for j in xrange(seed._n): -# J[j,k] += max(0, -eps*seed._B[j,k]) -# J[k,k] = -1 -# seed._G = seed._G*J -# -# # F-polynomials -# if mutating_F: -# gvect = tuple(seed._G.column(k)) -# if not self._F_dict.has_key(gvect): -# self._F_dict.setdefault(gvect,self._mutated_F(k)) -# seed._F[k] = self._F_dict[gvect] -# -# # C-matrix -# J = identity_matrix(seed._n) -# if any(x > 0 for x in seed._C.column(k)): -# eps = +1 -# else: -# eps = -1 -# for j in xrange(seed._n): -# J[k,j] += max(0, eps*seed._B[k,j]) -# J[k,k] = -1 -# seed._C = seed._C*J -# -# # B-matrix -# seed._B.mutate(k) -# -# if not inplace: -# return seed -# -#class Seed(SageObject): -# -# def __init__(self, B): -# n = B.ncols() -# self._B = copy(B) -# self._C = identity_matrix(n) -# self._G = identity_matrix(n) -# self._path = [] -# -# def __copy__(self): -# other = Seed(self._B) -# other._C = copy(self._C) -# other._G = copy(self._G) -# other._path = copy(self._path) -# return other -# -# def g_vectors(self): -# return tuple(self._G.columns()) -# -# def g_vector(self, i): -# return self._G.columns()[i] -# -# -# -# -######################## OLD ################### -# -#import time -#from sage.matrix.matrix import Matrix -# -# -# -# def _mutated_F(self, k): -# pos = self._U(1) -# neg = self._U(1) -# for j in xrange(self._n): -# if self._C[j,k] > 0: -# pos *= self._U.gen(j)**self._C[j,k] -# else: -# neg *= self._U.gen(j)**(-self._C[j,k]) -# if self._B[j,k] > 0: -# pos *= self._F[j]**self._B[j,k] -# else: -# neg *= self._F[j]**(-self._B[j,k]) -# return (pos+neg)//self._F[k] -# -# -# def find_cluster_variables(self, glist_tofind=[], num_mutations=infinity): -# MCI = self.mutation_class_iter() -# mutation_counter = 0 -# ## the last check should be done more efficiently -# while mutation_counter < num_mutations and (glist_tofind == [] or not Set(glist_tofind).issubset(Set(self._F_dict.keys()))): -# try: -# MCI.next() -# except: -# break -# mutation_counter += 1 -# print "Found after "+str(mutation_counter)+" mutations." -# -# -# def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, up_to_equivalence=True, only_sink_source=False): -# depth_counter = 0 -# n = self._n -# timer = time.time() -# if return_paths: -# yield (self,[]) -# else: -# yield self -# if up_to_equivalence: -# cl = Set(self._G.columns()) -# else: -# cl = tuple(self._G.columns()) -# clusters = {} -# clusters[ cl ] = [ self, range(n), [] ] -# gets_bigger = True -# if show_depth: -# timer2 = time.time() -# dc = str(depth_counter) -# dc += ' ' * (5-len(dc)) -# nr = str(len(clusters)) -# nr += ' ' * (10-len(nr)) -# print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) -# while gets_bigger and depth_counter < depth: -# gets_bigger = False -# keys = clusters.keys() -# for key in keys: -# sd = clusters[key] -# while sd[1]: -# i = sd[1].pop() -# if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): -# sd2 = sd[0].mutate( i, inplace=False ) -# if up_to_equivalence: -# cl2 = Set(sd2._G.columns()) -# else: -# cl2 = tuple(sd2._G.columns()) -# if cl2 in clusters: -# if not up_to_equivalence and i in clusters[cl2][1]: -# clusters[cl2][1].remove(i) -# else: -# gets_bigger = True -# if only_sink_source: -# orbits = range(n) -# else: -# orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] -# -# clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] -# if return_paths: -# yield (sd2,clusters[cl2][2]) -# else: -# yield sd2 -# depth_counter += 1 -# if show_depth and gets_bigger: -# timer2 = time.time() -# dc = str(depth_counter) -# dc += ' ' * (5-len(dc)) -# nr = str(len(clusters)) -# nr += ' ' * (10-len(nr)) -# print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) -# -# -# def cluster_variable(self, k): -# if k in range(self._n): -# g_mon = prod([self._R.gen(i)**self._G[i,k] for i in xrange(self._n)]) -# F_std = self._F[k].subs(self._yhat) -# F_trop = self._F[k].subs(self._y).denominator() -# return g_mon*F_std*F_trop -# if isinstance(k,tuple): -# if not k in self._F_dict.keys(): -# self.find_cluster_variables([k]) -# g_mon = prod([self._R.gen(i)**k[i] for i in xrange(self._n)]) -# F_std = self._F_dict[k].subs(self._yhat) -# F_trop = self._F_dict[k].subs(self._y).denominator() -# return g_mon*F_std*F_trop -# -# -# -# + + +# Define decorator to automatically transform vectors and lists to tuples +from functools import wraps +def make_hashable(func): + @wraps(func) + def wrapper(*args, **kwargs): + hashable_args = [] + for x in args: + try: + hashable_args.append(tuple(x)) + except: + hashable_args.append(x) + + hashable_kwargs = {} + for x in kwargs: + try: + hashable_kwargs[x] = tuple(kwargs[x]) + except: + hashable_kwargs[x] = kwargs[x] + + return func(*hashable_args, **hashable_kwargs) + return wrapper From f159552f8dc0d279c29847ac67a62a24005ba0f0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 14:03:46 +0200 Subject: [PATCH 004/191] Minor change to find_cluster_variable --- cluster_algebra.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 56015392a8b..4553ff06044 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -263,15 +263,18 @@ def cluster_variable(self, gvect): @make_hashable def find_cluster_variable(self, gvect, num_mutations=infinity): MCI = self.Seed.mutation_class_iter() - mutation_counter = -1 + mutation_counter = 0 ## the last check should be done more efficiently while mutation_counter < num_mutations and gvect not in self._F_dict.keys(): try: MCI.next() except: - raise ValueError("Could not find a cluster variable with g-vector %s"%str(gvect)) + break mutation_counter += 1 - print "Found after "+str(mutation_counter)+" mutations." + if gvect in self._F_dict.keys(): + print "Found after "+str(mutation_counter)+" mutations." + else: + raise ValueError("Could not find a cluster variable with g-vector %s"%str(gvect)) def ambient(self): return self._ambient From c957b444366bba42458542446ac7e403b951feab Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 14:05:34 +0200 Subject: [PATCH 005/191] Fixed typo --- cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 4553ff06044..20a338d1efa 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -18,7 +18,7 @@ def _lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? def d_vector(self): - factors = self._lift_to_field().factors() + factors = self._lift_to_field().factor() initial = [] non_initial = [] [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] From 0d0569c2341bc519cffec995575555a99df3b91f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 14:07:40 +0200 Subject: [PATCH 006/191] Added TODO --- cluster_algebra.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 20a338d1efa..fc63f58ba22 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -262,6 +262,8 @@ def cluster_variable(self, gvect): @make_hashable def find_cluster_variable(self, gvect, num_mutations=infinity): + # TODO: refactor this to output also the mutation sequence that produces + # the variable from self.Seed MCI = self.Seed.mutation_class_iter() mutation_counter = 0 ## the last check should be done more efficiently From 53457e90845f3f4e06eac712d05f768ec82e01da Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 14:16:43 +0200 Subject: [PATCH 007/191] Moved lift_to_field to ClusterAlgebra --- cluster_algebra.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index fc63f58ba22..d2c1739f94e 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -12,13 +12,13 @@ def _add_(self, other): return self.parent().retract(self.lift() + other.lift()) # HACK: LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - def _lift_to_field(self): - return self.parent().ambient_field()(1)*self.value + def lift_to_field(self): + return self.parent().lift_to_field(self) # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? def d_vector(self): - factors = self._lift_to_field().factor() + factors = self.lift_to_field().factor() initial = [] non_initial = [] [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] @@ -284,6 +284,9 @@ def ambient(self): def ambient_field(self): return self._ambient_field + def lift_to_field(self, x): + return self.ambient_field()(1)*x.value + def lift(self, x): return x.value From 6ee2d833786fc0bf7f4b836d2f4c9bb2a9ed5313 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 24 Jul 2015 14:44:58 +0200 Subject: [PATCH 008/191] Removed old comment --- cluster_algebra.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index d2c1739f94e..c53ad5cc3cb 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -266,7 +266,6 @@ def find_cluster_variable(self, gvect, num_mutations=infinity): # the variable from self.Seed MCI = self.Seed.mutation_class_iter() mutation_counter = 0 - ## the last check should be done more efficiently while mutation_counter < num_mutations and gvect not in self._F_dict.keys(): try: MCI.next() From cbc281f9d2ef3076e0f2ee4e9a34968c769af5dd Mon Sep 17 00:00:00 2001 From: drupel Date: Sat, 1 Aug 2015 11:47:39 -0500 Subject: [PATCH 009/191] Modified find_cluster_variable's ValueError message --- cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index c53ad5cc3cb..941e3dbf902 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -275,7 +275,7 @@ def find_cluster_variable(self, gvect, num_mutations=infinity): if gvect in self._F_dict.keys(): print "Found after "+str(mutation_counter)+" mutations." else: - raise ValueError("Could not find a cluster variable with g-vector %s"%str(gvect)) + raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(gvect),str(num_mutations))) def ambient(self): return self._ambient From 163485b116d0e95bcd2a69730264fcbd0ea2aaec Mon Sep 17 00:00:00 2001 From: Etn40ff Date: Sun, 2 Aug 2015 21:51:16 +0200 Subject: [PATCH 010/191] Few planning changes --- cluster_algebra.py | 132 ++++++++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 43 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 941e3dbf902..83d0654f90f 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -3,6 +3,34 @@ from sage.structure.sage_object import SageObject from sage.structure.parent import Parent from sage.rings.integer_ring import ZZ + +#### Helper functions. +# These my have to go in a more standard place or just outside this file + +# Define decorator to automatically transform vectors and lists to tuples +# is there not a standard function wrapper we could use instead of this? +from functools import wraps +def make_hashable(func): + @wraps(func) + def wrapper(*args, **kwargs): + hashable_args = [] + for x in args: + try: + hashable_args.append(tuple(x)) + except: + hashable_args.append(x) + + hashable_kwargs = {} + for x in kwargs: + try: + hashable_kwargs[x] = tuple(kwargs[x]) + except: + hashable_kwargs[x] = kwargs[x] + + return func(*hashable_args, **hashable_kwargs) + return wrapper + +#### Done with helper functions class ClusterAlgebraElement(ElementWrapper): @@ -29,6 +57,11 @@ def d_vector(self): return tuple(v1-v2) def g_vector(self): + # at the moment it is not immediately clear to me how to compute this + # assuming this is a generic element of the cluster algebra it is not + # going to be homogeneous, expecially if we are not using principal + # coefficients. I am not sure it can be done if the bottom part of the + # exchange matrix is not invertible. raise NotImplementederror("This should be computed by substitution") class ClusterAlgebraSeed(SageObject): @@ -38,6 +71,7 @@ def __init__(self, B, C, G, parent): self._C = copy(C) self._G = copy(G) self._parent = parent + self._path = [] def __copy__(self): other = type(self).__new__(type(self)) @@ -45,21 +79,22 @@ def __copy__(self): other._C = copy(self._C) other._G = copy(self._G) other._parent = self._parent + other._path = copy(self._path) return other def parent(self): return self._parent def F_polynomial(self, j): - gvect = tuple(self._G.column(j)) - return self.parent().F_polynomial(gvect) + g_vector = tuple(self._G.column(j)) + return self.parent().F_polynomial(g_vector) #maybe this alias sould be removed F = F_polynomial def cluster_variable(self, j): - gvect = tuple(self._G.column(j)) - return self.parent().cluster_variable(gvect) + g_vector = tuple(self._G.column(j)) + return self.parent().cluster_variable(g_vector) def g_vector(self, j): return tuple(self._G.column(j)) @@ -91,7 +126,7 @@ def mutate(self, k, inplace=True, mutating_F=True): eps = -1 # store the g-vector to be mutated in case we are mutating also F-polynomials - old_gvect = tuple(seed._G.column(k)) + old_g_vector = tuple(seed._G.column(k)) # mutate G-matrix J = identity_matrix(n) @@ -102,9 +137,9 @@ def mutate(self, k, inplace=True, mutating_F=True): # F-polynomials if mutating_F: - gvect = tuple(seed._G.column(k)) - if not self.parent()._F_dict.has_key(gvect): - self.parent()._F_dict.setdefault(gvect, self._mutated_F(k, old_gvect)) + g_vector = tuple(seed._G.column(k)) + if not self.parent()._F_dict.has_key(g_vector): + self.parent()._F_dict.setdefault(g_vector, self._mutated_F(k, old_g_vector)) # C-matrix J = identity_matrix(n) @@ -116,10 +151,16 @@ def mutate(self, k, inplace=True, mutating_F=True): # B-matrix seed._B.mutate(k) + # store mutation path + if seed._path != [] and seed._path[-1] == k: + seed._path.pop() + else: + seed._path.append(k) + if not inplace: return seed - def _mutated_F(self, k, old_gvect): + def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) neg = alg._U(1) @@ -132,7 +173,7 @@ def _mutated_F(self, k, old_gvect): pos *= self.F_polynomial(j)**self._B[j,k] elif self._B[j,k] <0: neg *= self.F_polynomial(j)**(-self._B[j,k]) - return (pos+neg)//alg.F_polynomial(old_gvect) + return (pos+neg)//alg.F_polynomial(old_g_vector) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) @@ -148,6 +189,9 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): if not inplace: return seed + def path_form_initial_seed(self): + return copy(self._path) + def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, only_sink_source=False): depth_counter = 0 n = self.parent().rk() @@ -203,7 +247,12 @@ def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=Fal class ClusterAlgebra(Parent): - + # it would be nice to have inject_variables() to allow the user to + # automagically export the initial cluster variables into the sage shell. + # Unfortunately to do this we need to inherit form ParentWithGens and, as a + # drawback, we get several functions that are meaningless/misleading in a + # cluster algebra like ngens() or gens() + Element = ClusterAlgebraElement def __init__(self, B0, scalars=ZZ): @@ -224,7 +273,7 @@ def __init__(self, B0, scalars=ZZ): self._ambient = LaurentPolynomialRing(scalars, 'x', m) self._ambient_field = self._ambient.fraction_field() - + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(m)])) for j in xrange(n)]) @@ -235,13 +284,16 @@ def __init__(self, B0, scalars=ZZ): def _repr_(self): return "Cluster Algebra of rank %s"%self.rk() + def _an_element_(self): + return self.Seed.cluster_variable(0) + def rk(self): return self._n @make_hashable - def F_polynomial(self, gvect): + def F_polynomial(self, g_vector): try: - return self._F_dict[gvect] + return self._F_dict[g_vector] except: # TODO: improve this error message raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") @@ -251,31 +303,31 @@ def F_polynomial(self, gvect): @make_hashable @cached_method - def cluster_variable(self, gvect): - if not gvect in self._F_dict.keys(): + def cluster_variable(self, g_vector): + if not g_vector in self._F_dict.keys(): raise ValueError("This Cluster Variable has not been computed yet.") - g_mon = prod([self.ambient().gen(i)**gvect[i] for i in xrange(self.rk())]) - F_std = self.F_polynomial(gvect).subs(self._yhat) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) + F_std = self.F_polynomial(g_vector).subs(self._yhat) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - F_trop = self.ambient_field()(self.F_polynomial(gvect).subs(self._y)).denominator() + F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() return self.retract(g_mon*F_std*F_trop) @make_hashable - def find_cluster_variable(self, gvect, num_mutations=infinity): + def find_cluster_variable(self, g_vector, num_mutations=infinity): # TODO: refactor this to output also the mutation sequence that produces # the variable from self.Seed MCI = self.Seed.mutation_class_iter() mutation_counter = 0 - while mutation_counter < num_mutations and gvect not in self._F_dict.keys(): + while mutation_counter < num_mutations and g_vector not in self._F_dict.keys(): try: MCI.next() except: break mutation_counter += 1 - if gvect in self._F_dict.keys(): + if g_vector in self._F_dict.keys(): print "Found after "+str(mutation_counter)+" mutations." else: - raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(gvect),str(num_mutations))) + raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) def ambient(self): return self._ambient @@ -292,26 +344,20 @@ def lift(self, x): def retract(self, x): return self(x) + # DESIDERATA: these function would be super cool to have + def greedy_element(self, d_vector): + pass + def theta_basis_element(self, g_vector): + pass -# Define decorator to automatically transform vectors and lists to tuples -from functools import wraps -def make_hashable(func): - @wraps(func) - def wrapper(*args, **kwargs): - hashable_args = [] - for x in args: - try: - hashable_args.append(tuple(x)) - except: - hashable_args.append(x) - - hashable_kwargs = {} - for x in kwargs: - try: - hashable_kwargs[x] = tuple(kwargs[x]) - except: - hashable_kwargs[x] = kwargs[x] + # some of these are probably irrealistic + def upper_cluster_algebra(self): + pass + + def upper_bound(self): + pass + + def lower_bound(self): + pass - return func(*hashable_args, **hashable_kwargs) - return wrapper From bec6b842548496b447333531636219ef29cd5f63 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 3 Aug 2015 18:04:16 +0200 Subject: [PATCH 011/191] Some notes about g-vectors --- cluster_algebra.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 83d0654f90f..e3bf5dcc04c 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -34,6 +34,16 @@ def wrapper(*args, **kwargs): class ClusterAlgebraElement(ElementWrapper): + # this is to store extra information like g-vector: what I am thinking is to + # store the g_vector whenever possible and pass it along when doing sums of + # elements with the same degree or multiplications of any two elements. + # This can potentially slow things down and make life harder. We need to + # redefine _add_ _mul_ and _lmul_ _rmul_ accordingly if we decide that there + # is no other way to compute g-vectors + def __init__(self, parent, value, g_vector=None): + ElementWrapper.__init__(self, parent=parent, value=value) + self._g_vector = g_vector + # This function needs to be removed once AdditiveMagmas.Subobjects # implements _add_ def _add_(self, other): From 0d5d8f68ccbf64f407b604bfa213e82ba83ebd28 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 11 Aug 2015 01:26:25 +0200 Subject: [PATCH 012/191] Slightly cleaned d-vector; added comment on @property; removed alias F; added logic to deal with methods only defined in some cases --- cluster_algebra.py | 65 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index e3bf5dcc04c..5bdfeb267b7 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -1,4 +1,5 @@ import time +from types import MethodType from sage.structure.element_wrapper import ElementWrapper from sage.structure.sage_object import SageObject from sage.structure.parent import Parent @@ -56,14 +57,16 @@ def lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? def d_vector(self): + n = self.parent().rk() + one = self.parent().ambient_field()(1) factors = self.lift_to_field().factor() initial = [] non_initial = [] [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] - initial = prod(initial+[self.parent().ambient_field()(1)]).numerator() - non_initial = prod(non_initial+[self.parent().ambient_field()(1)]).denominator() - v1 = vector(non_initial.exponents()[0][:self.parent().rk()]) - v2 = vector(initial.exponents()[0][:self.parent().rk()]) + initial = prod(initial+[one]).numerator() + non_initial = prod(non_initial+[one]).denominator() + v1 = vector(non_initial.exponents()[0][:n]) + v2 = vector(initial.exponents()[0][:n]) return tuple(v1-v2) def g_vector(self): @@ -99,9 +102,6 @@ def F_polynomial(self, j): g_vector = tuple(self._G.column(j)) return self.parent().F_polynomial(g_vector) - #maybe this alias sould be removed - F = F_polynomial - def cluster_variable(self, j): g_vector = tuple(self._G.column(j)) return self.parent().cluster_variable(g_vector) @@ -270,7 +270,12 @@ def __init__(self, B0, scalars=ZZ): n = B0.ncols() m = B0.nrows() I = identity_matrix(n) - + + # add methods that are defined only for special cases + if n == 2: + self.greedy_element = MethodType(greedy_element, self, self.__class__) + self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) + # maybe here scalars can be replaced with just ZZ self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) self._F_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) @@ -297,6 +302,26 @@ def _repr_(self): def _an_element_(self): return self.Seed.cluster_variable(0) + # Shall we use properties with setters and getters? This is the example + # maybe it is not a great idea but it saves on parenthesis and makes quantities immutable at the same time + #class C(object): + #def __init__(self): + # self._x = None + + #@property + #def x(self): + # """I'm the 'x' property.""" + # return self._x + + #@x.setter + #def x(self, value): + # self._x = value + + #@x.deleter + #def x(self): + # del self._x + + #@property def rk(self): return self._n @@ -308,9 +333,6 @@ def F_polynomial(self, g_vector): # TODO: improve this error message raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") - # maybe this alias could be removed - F = F_polynomial - @make_hashable @cached_method def cluster_variable(self, g_vector): @@ -354,14 +376,7 @@ def lift(self, x): def retract(self, x): return self(x) - # DESIDERATA: these function would be super cool to have - def greedy_element(self, d_vector): - pass - - def theta_basis_element(self, g_vector): - pass - - # some of these are probably irrealistic + # DESIDERATA. Some of these are probably irrealistic def upper_cluster_algebra(self): pass @@ -371,3 +386,15 @@ def upper_bound(self): def lower_bound(self): pass +#### +# Methods that are only defined for special cases +#### + +# Greedy elements exist only in rank 2 +def greedy_element(self, d_vector): + pass + +# At the moment I know only how to compute theta basis in rank 2 +def theta_basis_element(self, g_vector): + pass + From 68cdf6d0cca17493789d3ff66a2382b12b06dc1f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 12 Aug 2015 02:11:04 +0200 Subject: [PATCH 013/191] Several major changes --- cluster_algebra.py | 286 ++++++++++++++++++++++++--------------------- 1 file changed, 150 insertions(+), 136 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 5bdfeb267b7..b7af61622c6 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -5,22 +5,27 @@ from sage.structure.parent import Parent from sage.rings.integer_ring import ZZ -#### Helper functions. +#### +# Helper functions. +#### # These my have to go in a more standard place or just outside this file # Define decorator to automatically transform vectors and lists to tuples # is there not a standard function wrapper we could use instead of this? +# TODO: this messes up the function signature. In particular ? and ?? do not +# give the desired results. It is a known issue that is fized in python 3.4!!!! +# do we ever hope to have sage running on python 3? from functools import wraps def make_hashable(func): @wraps(func) def wrapper(*args, **kwargs): hashable_args = [] for x in args: - try: + try: hashable_args.append(tuple(x)) except: hashable_args.append(x) - + hashable_kwargs = {} for x in kwargs: try: @@ -31,8 +36,10 @@ def wrapper(*args, **kwargs): return func(*hashable_args, **hashable_kwargs) return wrapper -#### Done with helper functions - +#### +# Elements of a cluster algebra +#### + class ClusterAlgebraElement(ElementWrapper): # this is to store extra information like g-vector: what I am thinking is to @@ -57,14 +64,14 @@ def lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? def d_vector(self): - n = self.parent().rk() + n = self.parent().rk one = self.parent().ambient_field()(1) factors = self.lift_to_field().factor() initial = [] non_initial = [] - [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] + [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] initial = prod(initial+[one]).numerator() - non_initial = prod(non_initial+[one]).denominator() + non_initial = prod(non_initial+[one]).denominator() v1 = vector(non_initial.exponents()[0][:n]) v2 = vector(initial.exponents()[0][:n]) return tuple(v1-v2) @@ -77,8 +84,12 @@ def g_vector(self): # exchange matrix is not invertible. raise NotImplementederror("This should be computed by substitution") +#### +# Seeds +#### + class ClusterAlgebraSeed(SageObject): - + def __init__(self, B, C, G, parent): self._B = copy(B) self._C = copy(C) @@ -98,10 +109,13 @@ def __copy__(self): def parent(self): return self._parent + def b_matrix(self): + return copy(self._B) + def F_polynomial(self, j): g_vector = tuple(self._G.column(j)) return self.parent().F_polynomial(g_vector) - + def cluster_variable(self, j): g_vector = tuple(self._G.column(j)) return self.parent().cluster_variable(g_vector) @@ -117,51 +131,51 @@ def c_vector(self, j): def c_matrix(self): return copy(self._C) - + def mutate(self, k, inplace=True, mutating_F=True): if inplace: seed = self else: seed = copy(self) - - n = self.parent().rk() - + + n = self.parent().rk + if k not in xrange(n): - raise ValueError('Cannot mutate in direction' + str(k) + '.') - + raise ValueError('Cannot mutate in direction ' + str(k) + '.') + # find sign of k-th c-vector if any(x > 0 for x in seed._C.column(k)): eps = +1 else: eps = -1 - + # store the g-vector to be mutated in case we are mutating also F-polynomials old_g_vector = tuple(seed._G.column(k)) - + # mutate G-matrix J = identity_matrix(n) for j in xrange(n): J[j,k] += max(0, -eps*seed._B[j,k]) J[k,k] = -1 seed._G = seed._G*J - + # F-polynomials if mutating_F: g_vector = tuple(seed._G.column(k)) - if not self.parent()._F_dict.has_key(g_vector): - self.parent()._F_dict.setdefault(g_vector, self._mutated_F(k, old_g_vector)) - + if g_vector not in self.parent().g_vectors_so_far(): + self.parent()._data_dict.setdefault(g_vector, (self._mutated_F(k, old_g_vector),self._path+[k])) + # C-matrix J = identity_matrix(n) for j in xrange(n): J[k,j] += max(0, eps*seed._B[k,j]) J[k,k] = -1 seed._C = seed._C*J - + # B-matrix seed._B.mutate(k) - - # store mutation path + + # store mutation path if seed._path != [] and seed._path[-1] == k: seed._path.pop() else: @@ -174,7 +188,7 @@ def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) neg = alg._U(1) - for j in xrange(alg.rk()): + for j in xrange(alg.rk): if self._C[j,k] > 0: pos *= alg._U.gen(j)**self._C[j,k] else: @@ -187,12 +201,12 @@ def _mutated_F(self, k, old_g_vector): def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) - - if inplace: + + if inplace: seed = self else: seed = self.mutate(seq.next(), inplace=False, mutating_F=mutating_F) - + for k in seq: seed.mutate(k, inplace=True, mutating_F=mutating_F) @@ -202,59 +216,10 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): def path_form_initial_seed(self): return copy(self._path) - def mutation_class_iter(self, depth=infinity, show_depth=False, return_paths=False, only_sink_source=False): - depth_counter = 0 - n = self.parent().rk() - timer = time.time() - if return_paths: - yield (self,[]) - else: - yield self - cl = Set(self._G.columns()) - clusters = {} - clusters[ cl ] = [ self, range(n), [] ] - gets_bigger = True - if show_depth: - timer2 = time.time() - dc = str(depth_counter) - dc += ' ' * (5-len(dc)) - nr = str(len(clusters)) - nr += ' ' * (10-len(nr)) - print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) - while gets_bigger and depth_counter < depth: - gets_bigger = False - keys = clusters.keys() - for key in keys: - sd = clusters[key] - while sd[1]: - i = sd[1].pop() - if not only_sink_source or all( entry >= 0 for entry in sd[0]._B.row( i ) ) or all( entry <= 0 for entry in sd[0]._B.row( i ) ): - sd2 = sd[0].mutate( i, inplace=False ) - cl2 = Set(sd2._G.columns()) - if cl2 in clusters: - if i in clusters[cl2][1]: - clusters[cl2][1].remove(i) - else: - gets_bigger = True - if only_sink_source: - orbits = range(n) - else: - orbits = [ index for index in xrange(n) if index > i or sd2._B[index,i] != 0 ] - - clusters[ cl2 ] = [ sd2, orbits, clusters[key][2]+[i] ] - if return_paths: - yield (sd2,clusters[cl2][2]) - else: - yield sd2 - depth_counter += 1 - if show_depth and gets_bigger: - timer2 = time.time() - dc = str(depth_counter) - dc += ' ' * (5-len(dc)) - nr = str(len(clusters)) - nr += ' ' * (10-len(nr)) - print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) +#### +# Cluster algebras +#### class ClusterAlgebra(Parent): # it would be nice to have inject_variables() to allow the user to @@ -264,23 +229,27 @@ class ClusterAlgebra(Parent): # cluster algebra like ngens() or gens() Element = ClusterAlgebraElement - + def __init__(self, B0, scalars=ZZ): # Temporary variables n = B0.ncols() m = B0.nrows() I = identity_matrix(n) - + # add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) - - # maybe here scalars can be replaced with just ZZ + + # TODO: maybe here scalars can be replaced with just ZZ + # ambient space for F-polynomials self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) - self._F_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) - - self.Seed = ClusterAlgebraSeed(B0[:n,:n], I, I, self) + + # dictionary of already computed data: + # index is the g-vector, first entry is the F-polynomial, second entry is path from initial seed + self._data_dict = dict([ (tuple(v), (self._U(1),[])) for v in I.columns() ]) + + self._seed = ClusterAlgebraSeed(B0[:n,:n], I, I, self) base = LaurentPolynomialRing(scalars, 'x', n) # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ @@ -288,105 +257,127 @@ def __init__(self, B0, scalars=ZZ): self._ambient = LaurentPolynomialRing(scalars, 'x', m) self._ambient_field = self._ambient.fraction_field() - - self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) + + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._B0 = copy(B0) self._n = n self._m = m - + def _repr_(self): - return "Cluster Algebra of rank %s"%self.rk() + return "Cluster Algebra of rank %s"%self.rk def _an_element_(self): - return self.Seed.cluster_variable(0) - - # Shall we use properties with setters and getters? This is the example - # maybe it is not a great idea but it saves on parenthesis and makes quantities immutable at the same time - #class C(object): - #def __init__(self): - # self._x = None + return self.current_seed.cluster_variable(0) - #@property - #def x(self): - # """I'm the 'x' property.""" - # return self._x + @property + def rk(self): + r""" + The rank of ``self`` i.e. the number of cluster variables in any seed of + ``self``. + """ + return self._n - #@x.setter - #def x(self, value): - # self._x = value + @property + def current_seed(self): + r""" + The current seed of ``self``. + """ + return self._seed - #@x.deleter - #def x(self): - # del self._x + def g_vectors_so_far(self): + return self._data_dict.keys() - #@property - def rk(self): - return self._n - - @make_hashable + @make_hashable def F_polynomial(self, g_vector): try: - return self._F_dict[g_vector] + return self._data_dict[g_vector][0] except: # TODO: improve this error message raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") - + @make_hashable @cached_method def cluster_variable(self, g_vector): - if not g_vector in self._F_dict.keys(): + if not g_vector in self.g_vectors_so_far(): raise ValueError("This Cluster Variable has not been computed yet.") - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) F_std = self.F_polynomial(g_vector).subs(self._yhat) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() return self.retract(g_mon*F_std*F_trop) @make_hashable - def find_cluster_variable(self, g_vector, num_mutations=infinity): - # TODO: refactor this to output also the mutation sequence that produces - # the variable from self.Seed - MCI = self.Seed.mutation_class_iter() + def find_cluster_variable(self, g_vector, depth=infinity): + r""" + Returns the shortest mutation path to obtain the cluster variable with + given g-vector from the initial seed + """ + seeds = self.seeds(depth=depth) mutation_counter = 0 - while mutation_counter < num_mutations and g_vector not in self._F_dict.keys(): - try: - MCI.next() + while g_vector not in self.g_vectors_so_far(): + try: + seeds.next() except: - break + raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 - if g_vector in self._F_dict.keys(): - print "Found after "+str(mutation_counter)+" mutations." - else: - raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) + return copy(self._data_dict[g_vector][1]) def ambient(self): return self._ambient - + def ambient_field(self): return self._ambient_field - + def lift_to_field(self, x): return self.ambient_field()(1)*x.value - + def lift(self, x): return x.value - + def retract(self, x): return self(x) + def seeds(self, depth=infinity): + yield self.current_seed + depth_counter = 0 + n = self.rk + cl = Set(self.current_seed.g_matrix().columns()) + clusters = {} + clusters[ cl ] = [ self.current_seed, range(n) ] + gets_bigger = True + while gets_bigger and depth_counter < depth: + gets_bigger = False + keys = clusters.keys() + for key in keys: + sd, directions = clusters[key] + while directions: + i = directions.pop() + new_sd = sd.mutate( i, inplace=False ) + new_cl = Set(new_sd.g_matrix().columns()) + if new_cl in clusters: + if i in clusters[new_cl][1]: + clusters[new_cl][1].remove(i) + else: + gets_bigger = True + # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed + new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] + clusters[ new_cl ] = [ new_sd, new_directions ] + yield new_sd + depth_counter += 1 + # DESIDERATA. Some of these are probably irrealistic def upper_cluster_algebra(self): pass def upper_bound(self): pass - + def lower_bound(self): pass -#### +#### # Methods that are only defined for special cases #### @@ -398,3 +389,26 @@ def greedy_element(self, d_vector): def theta_basis_element(self, g_vector): pass +#### +# Scratchpad +#### + +# Shall we use properties with setters and getters? This is the example +# maybe it is not a great idea but it saves on parenthesis and makes quantities immutable at the same time +#class C(object): +#def __init__(self): +# self._x = None + +#@property +#def x(self): +# """I'm the 'x' property.""" +# return self._x + +#@x.setter +#def x(self, value): +# self._x = value + +#@x.deleter +#def x(self): +# del self._x + From d3f99b33b4e2ddca374715791e9b737b3d8fbf20 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 12 Aug 2015 14:37:40 +0200 Subject: [PATCH 014/191] reset_current_seed; _repr_ for ClusteralgebraSeed --- cluster_algebra.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index b7af61622c6..a235a86644a 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -106,6 +106,9 @@ def __copy__(self): other._path = copy(self._path) return other + def _repr_(self): + return "A seed in %s"%str(self.parent()) + def parent(self): return self._parent @@ -230,9 +233,15 @@ class ClusterAlgebra(Parent): Element = ClusterAlgebraElement - def __init__(self, B0, scalars=ZZ): + def __init__(self, data, scalars=ZZ): # Temporary variables + # TODO: right now we use ClusterQuiver to parse input data. It looks + # like a good idea but we should make sure it is. + Q = ClusterQuiver(data) + B0 = Q.b_matrix() n = B0.ncols() + # We use a different m than ClusterSeed and ClusterQuiver: their m is our m-n. + # Should we merge this behaviour? what is the notation in CA I-IV? m = B0.nrows() I = identity_matrix(n) @@ -241,7 +250,7 @@ def __init__(self, B0, scalars=ZZ): self.greedy_element = MethodType(greedy_element, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) - # TODO: maybe here scalars can be replaced with just ZZ + # TODO: maybe here scalars can be replaced with ZZ # ambient space for F-polynomials self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) @@ -249,8 +258,6 @@ def __init__(self, B0, scalars=ZZ): # index is the g-vector, first entry is the F-polynomial, second entry is path from initial seed self._data_dict = dict([ (tuple(v), (self._U(1),[])) for v in I.columns() ]) - self._seed = ClusterAlgebraSeed(B0[:n,:n], I, I, self) - base = LaurentPolynomialRing(scalars, 'x', n) # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) @@ -264,6 +271,7 @@ def __init__(self, B0, scalars=ZZ): self._B0 = copy(B0) self._n = n self._m = m + self.reset_current_seed() def _repr_(self): return "Cluster Algebra of rank %s"%self.rk @@ -286,6 +294,14 @@ def current_seed(self): """ return self._seed + def reset_current_seed(self): + r""" + Reset the current seed to be the initial one + """ + n = self.rk + I = identity_matrix(n) + self._seed = ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) + def g_vectors_so_far(self): return self._data_dict.keys() From fcc07d51c3d5955d09bd7c7160d4524ac313567b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 12 Aug 2015 15:29:23 +0200 Subject: [PATCH 015/191] some documentation --- cluster_algebra.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index a235a86644a..145bdaa0329 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -153,9 +153,9 @@ def mutate(self, k, inplace=True, mutating_F=True): eps = -1 # store the g-vector to be mutated in case we are mutating also F-polynomials - old_g_vector = tuple(seed._G.column(k)) + old_g_vector = self.g_vector(k) - # mutate G-matrix + # G-matrix J = identity_matrix(n) for j in xrange(n): J[j,k] += max(0, -eps*seed._B[j,k]) @@ -164,9 +164,9 @@ def mutate(self, k, inplace=True, mutating_F=True): # F-polynomials if mutating_F: - g_vector = tuple(seed._G.column(k)) + g_vector = self.g_vector(k) if g_vector not in self.parent().g_vectors_so_far(): - self.parent()._data_dict.setdefault(g_vector, (self._mutated_F(k, old_g_vector),self._path+[k])) + self.parent()._data_dict.setdefault( g_vector, (self._mutated_F(k, old_g_vector), self._path+[k]) ) # C-matrix J = identity_matrix(n) @@ -184,6 +184,7 @@ def mutate(self, k, inplace=True, mutating_F=True): else: seed._path.append(k) + # wrap up if not inplace: return seed @@ -237,10 +238,10 @@ def __init__(self, data, scalars=ZZ): # Temporary variables # TODO: right now we use ClusterQuiver to parse input data. It looks # like a good idea but we should make sure it is. - Q = ClusterQuiver(data) + Q = ClusterQuiver(data) B0 = Q.b_matrix() n = B0.ncols() - # We use a different m than ClusterSeed and ClusterQuiver: their m is our m-n. + # We use a different m than ClusterSeed and ClusterQuiver: their m is our m-n. # Should we merge this behaviour? what is the notation in CA I-IV? m = B0.nrows() I = identity_matrix(n) @@ -296,13 +297,16 @@ def current_seed(self): def reset_current_seed(self): r""" - Reset the current seed to be the initial one + Reset the current seed to the initial one """ n = self.rk I = identity_matrix(n) self._seed = ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) def g_vectors_so_far(self): + r""" + Return the g-vectors of cluster variables encountered so far. + """ return self._data_dict.keys() @make_hashable @@ -328,7 +332,9 @@ def cluster_variable(self, g_vector): def find_cluster_variable(self, g_vector, depth=infinity): r""" Returns the shortest mutation path to obtain the cluster variable with - given g-vector from the initial seed + g-vector ``g_vector`` from the initial seed. + + ``depth``: maximum distance from ``self.current_seed`` to reach. """ seeds = self.seeds(depth=depth) mutation_counter = 0 @@ -356,6 +362,10 @@ def retract(self, x): return self(x) def seeds(self, depth=infinity): + r""" + Return an iterator producing all seeds of ``self`` up to distance + ``depth`` from ``self.current_seed``. + """ yield self.current_seed depth_counter = 0 n = self.rk @@ -402,6 +412,8 @@ def greedy_element(self, d_vector): pass # At the moment I know only how to compute theta basis in rank 2 +# maybe we should let ClusterAlgebra have this methon for any rank and have a +# NotImplementedError to encourage someone (read: Greg) to code this def theta_basis_element(self, g_vector): pass From 3c850e4e82f6f8754fddadec338a825badab05db Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 19 Aug 2015 17:27:42 +0200 Subject: [PATCH 016/191] Changed the use of iterators to something python3 compatible. --- cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 145bdaa0329..b0f2d7bb5ba 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -209,7 +209,7 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): if inplace: seed = self else: - seed = self.mutate(seq.next(), inplace=False, mutating_F=mutating_F) + seed = self.mutate(next(seq), inplace=False, mutating_F=mutating_F) for k in seq: seed.mutate(k, inplace=True, mutating_F=mutating_F) @@ -340,7 +340,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): mutation_counter = 0 while g_vector not in self.g_vectors_so_far(): try: - seeds.next() + next(seeds) except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 From 7bc118a77a81e02679d18f8c035df4e6bd512222 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 12:28:48 +0200 Subject: [PATCH 017/191] Removed workaround for memoization, use the more economical tuple and key --- cluster_algebra.py | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index b0f2d7bb5ba..e0cb600b909 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -10,32 +10,6 @@ #### # These my have to go in a more standard place or just outside this file -# Define decorator to automatically transform vectors and lists to tuples -# is there not a standard function wrapper we could use instead of this? -# TODO: this messes up the function signature. In particular ? and ?? do not -# give the desired results. It is a known issue that is fized in python 3.4!!!! -# do we ever hope to have sage running on python 3? -from functools import wraps -def make_hashable(func): - @wraps(func) - def wrapper(*args, **kwargs): - hashable_args = [] - for x in args: - try: - hashable_args.append(tuple(x)) - except: - hashable_args.append(x) - - hashable_kwargs = {} - for x in kwargs: - try: - hashable_kwargs[x] = tuple(kwargs[x]) - except: - hashable_kwargs[x] = kwargs[x] - - return func(*hashable_args, **hashable_kwargs) - return wrapper - #### # Elements of a cluster algebra #### @@ -309,16 +283,15 @@ def g_vectors_so_far(self): """ return self._data_dict.keys() - @make_hashable def F_polynomial(self, g_vector): + g_vector= tuple(g_vector) try: return self._data_dict[g_vector][0] except: # TODO: improve this error message raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") - @make_hashable - @cached_method + @cached_method(key=lambda a,b: tuple(a) ) def cluster_variable(self, g_vector): if not g_vector in self.g_vectors_so_far(): raise ValueError("This Cluster Variable has not been computed yet.") @@ -328,7 +301,6 @@ def cluster_variable(self, g_vector): F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() return self.retract(g_mon*F_std*F_trop) - @make_hashable def find_cluster_variable(self, g_vector, depth=infinity): r""" Returns the shortest mutation path to obtain the cluster variable with @@ -336,6 +308,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): ``depth``: maximum distance from ``self.current_seed`` to reach. """ + g_vector = tuple(g_vector) seeds = self.seeds(depth=depth) mutation_counter = 0 while g_vector not in self.g_vectors_so_far(): From 61912fa32b883aaccc559abcae289770be8ae4e5 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 16:39:08 +0200 Subject: [PATCH 018/191] Fixed bug in mutate due to confusion of seed and self. Added containments logic. Fixed remaining issue with cluster_variable() --- cluster_algebra.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index e0cb600b909..22fdeeb2ae5 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -108,14 +108,14 @@ def c_vector(self, j): def c_matrix(self): return copy(self._C) - + def mutate(self, k, inplace=True, mutating_F=True): if inplace: seed = self else: seed = copy(self) - n = self.parent().rk + n = seed.parent().rk if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') @@ -127,7 +127,7 @@ def mutate(self, k, inplace=True, mutating_F=True): eps = -1 # store the g-vector to be mutated in case we are mutating also F-polynomials - old_g_vector = self.g_vector(k) + old_g_vector = seed.g_vector(k) # G-matrix J = identity_matrix(n) @@ -138,9 +138,9 @@ def mutate(self, k, inplace=True, mutating_F=True): # F-polynomials if mutating_F: - g_vector = self.g_vector(k) - if g_vector not in self.parent().g_vectors_so_far(): - self.parent()._data_dict.setdefault( g_vector, (self._mutated_F(k, old_g_vector), self._path+[k]) ) + g_vector = seed.g_vector(k) + if g_vector not in seed.parent().g_vectors_so_far(): + seed.parent()._data_dict[g_vector] = (seed._mutated_F(k, old_g_vector), seed._path+[k]) # C-matrix J = identity_matrix(n) @@ -194,6 +194,14 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): def path_form_initial_seed(self): return copy(self._path) + def __contains__(self, element): + if isinstance(element, ClusterAlgebraElement ): + cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk) ] + else: + element = tuple(element) + cluster = map( tuple, self.g_matrix().columns() ) + return element in cluster + #### # Cluster algebras @@ -291,8 +299,9 @@ def F_polynomial(self, g_vector): # TODO: improve this error message raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") - @cached_method(key=lambda a,b: tuple(a) ) + @cached_method(key=lambda a,b: tuple(b) ) def cluster_variable(self, g_vector): + g_vector = tuple(g_vector) if not g_vector in self.g_vectors_so_far(): raise ValueError("This Cluster Variable has not been computed yet.") g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) From 0df9cfa27dfe2579d29385432d5ad99e6d287a48 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 16:44:02 +0200 Subject: [PATCH 019/191] Added a copule of todos --- cluster_algebra.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 22fdeeb2ae5..64f16df8887 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -137,6 +137,9 @@ def mutate(self, k, inplace=True, mutating_F=True): seed._G = seed._G*J # F-polynomials + # TODO: we should record the path to g-vectors anyway; maybe the best + # options is to go back to _f_poly_dict and have a separate _paths_dict + # for this. if mutating_F: g_vector = seed.g_vector(k) if g_vector not in seed.parent().g_vectors_so_far(): @@ -342,7 +345,8 @@ def lift(self, x): def retract(self, x): return self(x) - + + #TODO: add option to avoid computing F-polynomials def seeds(self, depth=infinity): r""" Return an iterator producing all seeds of ``self`` up to distance From dc811e5a70c274fbdb212726bc16c56da3906458 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 16:49:43 +0200 Subject: [PATCH 020/191] Even more todos --- cluster_algebra.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 64f16df8887..1d7c420ee4b 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -197,6 +197,13 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): def path_form_initial_seed(self): return copy(self._path) + # TODO: ideally we should allow to mutate in direction "this g-vector" or + # "this cluster variable" or "sink", "urban renewal" and all the other + # options provided by Gregg et al. To do so I guess the best option is to + # have a generic function transforming all these into an index and use it as + # a decorator. In this way we can also use it in this __contains__ even + # though one may write weird things like "sink" in A.current_seed and get + # True as an answer. def __contains__(self, element): if isinstance(element, ClusterAlgebraElement ): cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk) ] From 3fabbebd0447469ed89e36df336365f20c49c7ff Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 17:06:02 +0200 Subject: [PATCH 021/191] Taken care of one of the todos --- cluster_algebra.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 1d7c420ee4b..02f044c0462 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -120,6 +120,12 @@ def mutate(self, k, inplace=True, mutating_F=True): if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') + # store mutation path + if seed._path != [] and seed._path[-1] == k: + seed._path.pop() + else: + seed._path.append(k) + # find sign of k-th c-vector if any(x > 0 for x in seed._C.column(k)): eps = +1 @@ -136,14 +142,13 @@ def mutate(self, k, inplace=True, mutating_F=True): J[k,k] = -1 seed._G = seed._G*J - # F-polynomials - # TODO: we should record the path to g-vectors anyway; maybe the best - # options is to go back to _f_poly_dict and have a separate _paths_dict - # for this. - if mutating_F: - g_vector = seed.g_vector(k) - if g_vector not in seed.parent().g_vectors_so_far(): - seed.parent()._data_dict[g_vector] = (seed._mutated_F(k, old_g_vector), seed._path+[k]) + # g-vector path list + g_vector = seed.g_vector(k) + if g_vector not in seed.parent().g_vectors_so_far(): + seed.parent()._path_dict[g_vector] = copy(seed._path) + # F-polynomials + if mutating_F: + seed.parent()._F_poly_dict[g_vector] = seed._mutated_F(k, old_g_vector) # C-matrix J = identity_matrix(n) @@ -155,12 +160,6 @@ def mutate(self, k, inplace=True, mutating_F=True): # B-matrix seed._B.mutate(k) - # store mutation path - if seed._path != [] and seed._path[-1] == k: - seed._path.pop() - else: - seed._path.append(k) - # wrap up if not inplace: return seed @@ -249,8 +248,8 @@ def __init__(self, data, scalars=ZZ): # dictionary of already computed data: # index is the g-vector, first entry is the F-polynomial, second entry is path from initial seed - self._data_dict = dict([ (tuple(v), (self._U(1),[])) for v in I.columns() ]) - + self._F_poly_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) + self._path_dict = dict([ (tuple(v), []) for v in I.columns() ]) base = LaurentPolynomialRing(scalars, 'x', n) # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) @@ -299,14 +298,15 @@ def g_vectors_so_far(self): r""" Return the g-vectors of cluster variables encountered so far. """ - return self._data_dict.keys() + return self._path_dict.keys() def F_polynomial(self, g_vector): g_vector= tuple(g_vector) try: - return self._data_dict[g_vector][0] + return self._F_poly_dict[g_vector] except: - # TODO: improve this error message + # TODO: improve this error message to include the case in which we + # already know the path raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") @cached_method(key=lambda a,b: tuple(b) ) @@ -336,7 +336,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 - return copy(self._data_dict[g_vector][1]) + return copy(self._path_dict[g_vector]) def ambient(self): return self._ambient From 5fc40503d5dc31ef942ed21e16bfb12983df9591 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 17:10:02 +0200 Subject: [PATCH 022/191] Added comments --- cluster_algebra.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 02f044c0462..bf24ade71e3 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -247,16 +247,18 @@ def __init__(self, data, scalars=ZZ): self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) # dictionary of already computed data: - # index is the g-vector, first entry is the F-polynomial, second entry is path from initial seed self._F_poly_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) self._path_dict = dict([ (tuple(v), []) for v in I.columns() ]) + + # setup Parent and ambient base = LaurentPolynomialRing(scalars, 'x', n) # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) - self._ambient = LaurentPolynomialRing(scalars, 'x', m) self._ambient_field = self._ambient.fraction_field() + # these are used for computing cluster variables using separation of + # additions self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(m)])) for j in xrange(n)]) From 7b7241325d575806462c09436327bdde2455942a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 21:06:51 +0200 Subject: [PATCH 023/191] Big speedup plus minor fixes --- cluster_algebra.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index bf24ade71e3..94223674695 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -145,6 +145,7 @@ def mutate(self, k, inplace=True, mutating_F=True): # g-vector path list g_vector = seed.g_vector(k) if g_vector not in seed.parent().g_vectors_so_far(): + seed.parent()._g_vect_set.add(g_vector) seed.parent()._path_dict[g_vector] = copy(seed._path) # F-polynomials if mutating_F: @@ -177,7 +178,18 @@ def _mutated_F(self, k, old_g_vector): pos *= self.F_polynomial(j)**self._B[j,k] elif self._B[j,k] <0: neg *= self.F_polynomial(j)**(-self._B[j,k]) - return (pos+neg)//alg.F_polynomial(old_g_vector) + # TODO: understand why using // instead of / here slows the code down by + # a factor of 3 but in the original experiments we made at sage days it + # was much faster with // (we were working with cluter variables at the + # time). + # By the way, as of August 28th 2015 we split in half the running time + # compared to sage days. With my laptop plugged in I get + # sage: A = ClusterAlgebra(['E',8]) + # sage: seeds = A.seeds() + # sage: %time void = list(seeds) + # CPU times: user 26.8 s, sys: 21 ms, total: 26.9 s + # Wall time: 26.8 s + return (pos+neg)/alg.F_polynomial(old_g_vector) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) @@ -247,8 +259,9 @@ def __init__(self, data, scalars=ZZ): self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) # dictionary of already computed data: - self._F_poly_dict = dict([ (tuple(v), self._U(1)) for v in I.columns() ]) - self._path_dict = dict([ (tuple(v), []) for v in I.columns() ]) + self._g_vect_set = set([ tuple(v) for v in I.columns() ]) + self._F_poly_dict = dict([ (v, self._U(1)) for v in self._g_vect_set ]) + self._path_dict = dict([ (v, []) for v in self._g_vect_set ]) # setup Parent and ambient base = LaurentPolynomialRing(scalars, 'x', n) @@ -300,7 +313,7 @@ def g_vectors_so_far(self): r""" Return the g-vectors of cluster variables encountered so far. """ - return self._path_dict.keys() + return self._g_vect_set def F_polynomial(self, g_vector): g_vector= tuple(g_vector) @@ -355,18 +368,19 @@ def lift(self, x): def retract(self, x): return self(x) - #TODO: add option to avoid computing F-polynomials - def seeds(self, depth=infinity): + def seeds(self, depth=infinity, mutating_F=True): r""" Return an iterator producing all seeds of ``self`` up to distance ``depth`` from ``self.current_seed``. + + If ``mutating_F`` is set to false it does not compute F_polynomials """ yield self.current_seed depth_counter = 0 n = self.rk - cl = Set(self.current_seed.g_matrix().columns()) + cl = frozenset(self.current_seed.g_matrix().columns()) clusters = {} - clusters[ cl ] = [ self.current_seed, range(n) ] + clusters[cl] = [ self.current_seed, range(n) ] gets_bigger = True while gets_bigger and depth_counter < depth: gets_bigger = False @@ -375,8 +389,8 @@ def seeds(self, depth=infinity): sd, directions = clusters[key] while directions: i = directions.pop() - new_sd = sd.mutate( i, inplace=False ) - new_cl = Set(new_sd.g_matrix().columns()) + new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) + new_cl = frozenset(new_sd.g_matrix().columns()) if new_cl in clusters: if i in clusters[new_cl][1]: clusters[new_cl][1].remove(i) @@ -384,7 +398,7 @@ def seeds(self, depth=infinity): gets_bigger = True # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] - clusters[ new_cl ] = [ new_sd, new_directions ] + clusters[new_cl] = [ new_sd, new_directions ] yield new_sd depth_counter += 1 From afb17bc4847155ce49e954ea1e5cbc6b0aa9d4c6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 25 Aug 2015 21:06:51 +0200 Subject: [PATCH 024/191] Big speedup plus minor fixes --- cluster_algebra.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 94223674695..76fe6adeba0 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -258,7 +258,11 @@ def __init__(self, data, scalars=ZZ): # ambient space for F-polynomials self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) - # dictionary of already computed data: + # already computed data + # TODO: I am unhappy because _g_vect_set is slightly redundant (we could + # use _path_dict.keys() instead) but it is faster to check membership in + # sets than in lists and _path_dict.keys() returns a list. Depending on + # the number of cluster variables this may be relevant or not. self._g_vect_set = set([ tuple(v) for v in I.columns() ]) self._F_poly_dict = dict([ (v, self._U(1)) for v in self._g_vect_set ]) self._path_dict = dict([ (v, []) for v in self._g_vect_set ]) From 8fe555561404c9aa201c490083ec52ecd1d19983 Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 26 Aug 2015 11:31:15 -0400 Subject: [PATCH 025/191] Modified _repr_ for seeds --- cluster_algebra.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 76fe6adeba0..bed6618f4b2 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -81,7 +81,12 @@ def __copy__(self): return other def _repr_(self): - return "A seed in %s"%str(self.parent()) + if self._path == []: + return "The initial seed of %s"%str(self.parent()) + elif self._path.__len__() == 1: + return "The seed of %s obtained from the initial by mutating in direction %s"%(str(self.parent()),str(self._path[0])) + else: + return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) def parent(self): return self._parent From aa1f74c0cb7970f5bd0ac5ee2d69245c02155140 Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 26 Aug 2015 12:46:20 -0400 Subject: [PATCH 026/191] Some edits and comments --- cluster_algebra.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index bed6618f4b2..66fe86d4094 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -94,25 +94,23 @@ def parent(self): def b_matrix(self): return copy(self._B) - def F_polynomial(self, j): - g_vector = tuple(self._G.column(j)) - return self.parent().F_polynomial(g_vector) - - def cluster_variable(self, j): - g_vector = tuple(self._G.column(j)) - return self.parent().cluster_variable(g_vector) + def c_matrix(self): + return copy(self._C) - def g_vector(self, j): - return tuple(self._G.column(j)) + def c_vector(self, j): + return tuple(self._C.column(j)) def g_matrix(self): return copy(self._G) - def c_vector(self, j): - return tuple(self._C.column(j)) + def g_vector(self, j): + return tuple(self._G.column(j)) - def c_matrix(self): - return copy(self._C) + def F_polynomial(self, j): + return self.parent().F_polynomial(self.g_vector(j)) + + def cluster_variable(self, j): + return self.parent().cluster_variable(self.g_vector(j)) def mutate(self, k, inplace=True, mutating_F=True): if inplace: @@ -132,12 +130,13 @@ def mutate(self, k, inplace=True, mutating_F=True): seed._path.append(k) # find sign of k-th c-vector + # Will this be used enough that it should be a built-in function? if any(x > 0 for x in seed._C.column(k)): eps = +1 else: eps = -1 - # store the g-vector to be mutated in case we are mutating also F-polynomials + # store the g-vector to be mutated in case we are mutating F-polynomials also old_g_vector = seed.g_vector(k) # G-matrix @@ -239,6 +238,10 @@ class ClusterAlgebra(Parent): # Unfortunately to do this we need to inherit form ParentWithGens and, as a # drawback, we get several functions that are meaningless/misleading in a # cluster algebra like ngens() or gens() + # If we only care about initial cluster variables we can always do the following: + # def inject_variables(self, scope=None, verbose=True): + # self._ambient.inject_variables(scope=scope,verbose=verbose) + # for labeled non-initial cluster variables we can also manually add them to the scope. Element = ClusterAlgebraElement @@ -250,7 +253,8 @@ def __init__(self, data, scalars=ZZ): B0 = Q.b_matrix() n = B0.ncols() # We use a different m than ClusterSeed and ClusterQuiver: their m is our m-n. - # Should we merge this behaviour? what is the notation in CA I-IV? + # Should we merge this behaviour? what is the notation in CA I-IV? + # They use the m-n convention, the m convention comes out of Fock-Goncharov or Gekhtman-Shapiro-Vainshtein m = B0.nrows() I = identity_matrix(n) @@ -331,6 +335,7 @@ def F_polynomial(self, g_vector): except: # TODO: improve this error message to include the case in which we # already know the path + # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") @cached_method(key=lambda a,b: tuple(b) ) @@ -411,7 +416,7 @@ def seeds(self, depth=infinity, mutating_F=True): yield new_sd depth_counter += 1 - # DESIDERATA. Some of these are probably irrealistic + # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass @@ -430,8 +435,9 @@ def greedy_element(self, d_vector): pass # At the moment I know only how to compute theta basis in rank 2 -# maybe we should let ClusterAlgebra have this methon for any rank and have a +# maybe we should let ClusterAlgebra have this method for any rank and have a # NotImplementedError to encourage someone (read: Greg) to code this +#I think Greg already has some code to do this def theta_basis_element(self, g_vector): pass @@ -441,6 +447,7 @@ def theta_basis_element(self, g_vector): # Shall we use properties with setters and getters? This is the example # maybe it is not a great idea but it saves on parenthesis and makes quantities immutable at the same time +# I am not sure I can give an opinion yet, maybe after I see it in action #class C(object): #def __init__(self): # self._x = None From c2bb10d72b2394b7149d89440011d6c6baea28ce Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 26 Aug 2015 13:31:38 -0400 Subject: [PATCH 027/191] Implemented greedy recursions (ignores coefficients for now) --- cluster_algebra.py | 95 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 66fe86d4094..7ba61a9d599 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -260,7 +260,7 @@ def __init__(self, data, scalars=ZZ): # add methods that are defined only for special cases if n == 2: - self.greedy_element = MethodType(greedy_element, self, self.__class__) + #self.greedy_element = MethodType(greedy_element, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # TODO: maybe here scalars can be replaced with ZZ @@ -426,13 +426,101 @@ def upper_bound(self): def lower_bound(self): pass + def greedy_element(self, d_vector): + b = abs(self._B0[0,1]) + c = abs(self._B0[1,0]) + a1 = d_vector[0] + a2 = d_vector[1] + x1 = self._ambient.gens()[0] + x2 = self._ambient.gens()[1] + if a1 < 0: + if a2 < 0: + return x1**(-a1)*x2**(-a2) + else: + return x1**(-a1)*((1+x2**c)/x1)**a2 + elif a2 < 0: + return ((1+x1**b)/x2)**a1*x2**(-a2) + output = 0 + for p in xrange(0,a2+1): + for q in xrange(0,a1+1): + output += self.greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) + return x1**(-a1)*x2**(-a2)*output + + def greedy_coeff(self,d_vector,p,q): + b = abs(self._B0[0,1]) + c = abs(self._B0[1,0]) + a1 = d_vector[0] + a2 = d_vector[1] + p = Integer(p) + q = Integer(q) + if p == 0 and q == 0: + return 1 + sum1 = 0 + for k in range(1,p+1): + bin = 0 + if a2-c*q+k-1 >= k: + bin = binomial(a2-c*q+k-1,k) + sum1 += (-1)**(k-1)*self.greedy_coeff(d_vector,p-k,q)*bin + sum2 = 0 + for l in range(1,q+1): + bin = 0 + if a1-b*p+l-1 >= l: + bin = binomial(a1-b*p+l-1,l) + sum2 += (-1)**(l-1)*self.greedy_coeff(d_vector,p,q-l)*bin + #print "sum1=",sum1,"sum2=",sum2 + return max(sum1,sum2) + + + #### # Methods that are only defined for special cases #### # Greedy elements exist only in rank 2 -def greedy_element(self, d_vector): - pass +# Does not yet take into account coefficients, this can probably be done by using the greedy coefficients to write down the F-polynomials +#def greedy_element(self, d_vector): +# b = abs(self._B0[0,1]) +# c = abs(self._B0[1,0]) +# a1 = d_vector[0] +# a2 = d_vector[1] +# x1 = self._ambient._gens[0] +# x2 = self._ambient._gens[1] +# if a1 < 0: +# if a2 < 0: +# return x1**(-a1)*x2**(-a2) +# else: +# return x1**(-a1)*((1+x2**c)/x1)**a2 +# elif a2 < 0: +# return ((1+x1**b)/x2)**a1*x2**(-a2) +# output = 0 +# for p in xrange(0,a2+1): +# for q in xrange(0,a1+1): +# output += greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) +# return x1**(-a1)*x2**(-a2)*output + +#def greedy_coeff(self,d_vector,p,q): +# b = abs(self._B0[0,1]) +# c = abs(self._B0[1,0]) +# a1 = d_vector[0] +# a2 = d_vector[1] +# p = Integer(p) +# q = Integer(q) +# if p == 0 and q == 0: +# return 1 +# sum1 = 0 +# for k in range(1,p+1): +# bin = 0 +# if a2-c*q+k-1 >= k: +# bin = binomial(a2-c*q+k-1,k) +# sum1 += (-1)^(k-1)*greedy_coeff(d_vector,p-k,q)*bin +# sum2 = 0 +# for l in range(1,q+1): +# bin = 0 +# if a1-b*p+l-1 >= l: +# bin = binomial(a1-b*p+l-1,l) +# sum2 += (-1)^(l-1)*greedy_coeff(d_vector,p,q-l)*bin +# #print "sum1=",sum1,"sum2=",sum2 +# return max(sum1,sum2) # At the moment I know only how to compute theta basis in rank 2 # maybe we should let ClusterAlgebra have this method for any rank and have a @@ -465,3 +553,4 @@ def theta_basis_element(self, g_vector): #def x(self): # del self._x + From 6bfea080866304df1a3f9e78d42df8af7ab6d02f Mon Sep 17 00:00:00 2001 From: Etn40ff Date: Mon, 31 Aug 2015 17:47:50 +0200 Subject: [PATCH 028/191] Define greedy stuff only when it makes sense, improved its return type --- cluster_algebra.py | 145 +++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 89 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 7ba61a9d599..1a0a7a1ff6d 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -260,7 +260,8 @@ def __init__(self, data, scalars=ZZ): # add methods that are defined only for special cases if n == 2: - #self.greedy_element = MethodType(greedy_element, self, self.__class__) + self.greedy_element = MethodType(greedy_element, self, self.__class__) + self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # TODO: maybe here scalars can be replaced with ZZ @@ -426,101 +427,67 @@ def upper_bound(self): def lower_bound(self): pass - def greedy_element(self, d_vector): - b = abs(self._B0[0,1]) - c = abs(self._B0[1,0]) - a1 = d_vector[0] - a2 = d_vector[1] - x1 = self._ambient.gens()[0] - x2 = self._ambient.gens()[1] - if a1 < 0: - if a2 < 0: - return x1**(-a1)*x2**(-a2) - else: - return x1**(-a1)*((1+x2**c)/x1)**a2 - elif a2 < 0: - return ((1+x1**b)/x2)**a1*x2**(-a2) - output = 0 - for p in xrange(0,a2+1): - for q in xrange(0,a1+1): - output += self.greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) - return x1**(-a1)*x2**(-a2)*output - - def greedy_coeff(self,d_vector,p,q): - b = abs(self._B0[0,1]) - c = abs(self._B0[1,0]) - a1 = d_vector[0] - a2 = d_vector[1] - p = Integer(p) - q = Integer(q) - if p == 0 and q == 0: - return 1 - sum1 = 0 - for k in range(1,p+1): - bin = 0 - if a2-c*q+k-1 >= k: - bin = binomial(a2-c*q+k-1,k) - sum1 += (-1)**(k-1)*self.greedy_coeff(d_vector,p-k,q)*bin - sum2 = 0 - for l in range(1,q+1): - bin = 0 - if a1-b*p+l-1 >= l: - bin = binomial(a1-b*p+l-1,l) - sum2 += (-1)**(l-1)*self.greedy_coeff(d_vector,p,q-l)*bin - #print "sum1=",sum1,"sum2=",sum2 - return max(sum1,sum2) #### # Methods that are only defined for special cases #### - # Greedy elements exist only in rank 2 -# Does not yet take into account coefficients, this can probably be done by using the greedy coefficients to write down the F-polynomials -#def greedy_element(self, d_vector): -# b = abs(self._B0[0,1]) -# c = abs(self._B0[1,0]) -# a1 = d_vector[0] -# a2 = d_vector[1] -# x1 = self._ambient._gens[0] -# x2 = self._ambient._gens[1] -# if a1 < 0: -# if a2 < 0: -# return x1**(-a1)*x2**(-a2) -# else: -# return x1**(-a1)*((1+x2**c)/x1)**a2 -# elif a2 < 0: -# return ((1+x1**b)/x2)**a1*x2**(-a2) -# output = 0 -# for p in xrange(0,a2+1): -# for q in xrange(0,a1+1): -# output += greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) -# return x1**(-a1)*x2**(-a2)*output - -#def greedy_coeff(self,d_vector,p,q): -# b = abs(self._B0[0,1]) -# c = abs(self._B0[1,0]) -# a1 = d_vector[0] -# a2 = d_vector[1] -# p = Integer(p) -# q = Integer(q) -# if p == 0 and q == 0: -# return 1 -# sum1 = 0 -# for k in range(1,p+1): -# bin = 0 -# if a2-c*q+k-1 >= k: -# bin = binomial(a2-c*q+k-1,k) -# sum1 += (-1)^(k-1)*greedy_coeff(d_vector,p-k,q)*bin -# sum2 = 0 -# for l in range(1,q+1): -# bin = 0 -# if a1-b*p+l-1 >= l: -# bin = binomial(a1-b*p+l-1,l) -# sum2 += (-1)^(l-1)*greedy_coeff(d_vector,p,q-l)*bin -# #print "sum1=",sum1,"sum2=",sum2 -# return max(sum1,sum2) +# Does not yet take into account coefficients, this can probably be done by +# using the greedy coefficients to write down the F-polynomials +def greedy_element(self, d_vector): + b = abs(self._B0[0,1]) + c = abs(self._B0[1,0]) + a1 = d_vector[0] + a2 = d_vector[1] + # TODO: we need to have something like initial_cluster_variables so that we + # do not have to use the generators of the ambient field. (this would also + # make it better behaved when allowing different names) + # Warning: there might be issues with coercions, make sure there are not + x1 = self._ambient.gens()[0] + x2 = self._ambient.gens()[1] + if a1 < 0: + if a2 < 0: + return self.retract(x1**(-a1)*x2**(-a2)) + else: + return self.retract(x1**(-a1)*((1+x2**c)/x1)**a2) + elif a2 < 0: + return self.retract(((1+x1**b)/x2)**a1*x2**(-a2)) + output = 0 + for p in xrange(0,a2+1): + for q in xrange(0,a1+1): + output += self.greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) + return self.retract(x1**(-a1)*x2**(-a2)*output) + +# Is this function something we want to make public or do we want to make this a +# private method changing it to _greedy_coeff ? +# Since we are giving long names to things we might want to change this into +# greedy_coefficient +def greedy_coeff(self,d_vector,p,q): + b = abs(self._B0[0,1]) + c = abs(self._B0[1,0]) + a1 = d_vector[0] + a2 = d_vector[1] + p = Integer(p) + q = Integer(q) + if p == 0 and q == 0: + return 1 + sum1 = 0 + for k in range(1,p+1): + bin = 0 + if a2-c*q+k-1 >= k: + bin = binomial(a2-c*q+k-1,k) + sum1 += (-1)**(k-1)*self.greedy_coeff(d_vector,p-k,q)*bin + sum2 = 0 + for l in range(1,q+1): + bin = 0 + if a1-b*p+l-1 >= l: + bin = binomial(a1-b*p+l-1,l) + sum2 += (-1)**(l-1)*self.greedy_coeff(d_vector,p,q-l)*bin + #print "sum1=",sum1,"sum2=",sum2 + return max(sum1,sum2) + # At the moment I know only how to compute theta basis in rank 2 # maybe we should let ClusterAlgebra have this method for any rank and have a From e04269ecc42bb8078aff7d9bcc23e066d71efab6 Mon Sep 17 00:00:00 2001 From: Etn40ff Date: Mon, 31 Aug 2015 18:44:44 +0200 Subject: [PATCH 029/191] Fixed a nasty bug: we were using cluster variables as the base of our ring instead of frozen variables --- cluster_algebra.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 1a0a7a1ff6d..339ea0395d7 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -193,7 +193,7 @@ def _mutated_F(self, k, old_g_vector): # sage: %time void = list(seeds) # CPU times: user 26.8 s, sys: 21 ms, total: 26.9 s # Wall time: 26.8 s - return (pos+neg)/alg.F_polynomial(old_g_vector) + return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) @@ -264,9 +264,9 @@ def __init__(self, data, scalars=ZZ): self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) - # TODO: maybe here scalars can be replaced with ZZ + # TODO: is ZZ the correct ambient here? # ambient space for F-polynomials - self._U = PolynomialRing(scalars,['u%s'%i for i in xrange(n)]) + self._U = PolynomialRing(ZZ, ['u%s'%i for i in xrange(n)]) # already computed data # TODO: I am unhappy because _g_vect_set is slightly redundant (we could @@ -278,11 +278,22 @@ def __init__(self, data, scalars=ZZ): self._path_dict = dict([ (v, []) for v in self._g_vect_set ]) # setup Parent and ambient - base = LaurentPolynomialRing(scalars, 'x', n) + # TODO: at the moment self.base() is not a subobject of self.ambient() + # unfortunately if we change `scalars` to `base` in *** it becomes + # harder to list generators of ambient + if m>n: + # do we want change this to its fraction field (so that we can + # invert coefficients)? + base = LaurentPolynomialRing(scalars, ['x%s'%i for i in xrange(n,m)]) + else: + base = scalars # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) + # *** here we might want to replace scalars with base and m with n self._ambient = LaurentPolynomialRing(scalars, 'x', m) self._ambient_field = self._ambient.fraction_field() + # TODO: understand if we need this + #self._populate_coercion_lists_() # these are used for computing cluster variables using separation of # additions @@ -293,6 +304,10 @@ def __init__(self, data, scalars=ZZ): self._n = n self._m = m self.reset_current_seed() + + # enable standard cohercions: everything that is in the base can be coherced + def _coerce_map_from_(self, other): + return self.base().has_coerce_map_from(other) def _repr_(self): return "Cluster Algebra of rank %s"%self.rk From ac3534c43137e3ac6761fa1ca708436cb81ea114 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 31 Aug 2015 13:37:30 -0400 Subject: [PATCH 030/191] Added some possible use prompts. --- cluster_algebra.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 339ea0395d7..c8f568e8c9a 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -352,6 +352,10 @@ def F_polynomial(self, g_vector): # TODO: improve this error message to include the case in which we # already know the path # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? + # Yes, perhaps with the a prompt first, something like: + #comp = raw_input("This F-polynomial has not been computed yet. It can be found using %s mutations. Continue? (y or n):"%str(directions.__len__())) + #if comp == 'y': + # ...compute the F-polynomial... raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") @cached_method(key=lambda a,b: tuple(b) ) @@ -380,6 +384,14 @@ def find_cluster_variable(self, g_vector, depth=infinity): next(seeds) except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) + #*** referred to in &&& below + #cont = raw_input("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))+" Continue searching? (y or n):") + #if cont == 'y': + # new_depth = 0 + # while int(new_depth) <= mutation_counter: + # new_depth = raw_input("Please enter a new depth greater than %s:"%str(mutation_counter)) + #else: + # raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 return copy(self._path_dict[g_vector]) @@ -412,6 +424,10 @@ def seeds(self, depth=infinity, mutating_F=True): clusters = {} clusters[cl] = [ self.current_seed, range(n) ] gets_bigger = True + #&&& + #Do we need to have a maximum depth built into the iterator? + #I would hope that we can remove it and add dynamic changing of the depth in find_cluster_variables + #using something like *** above while gets_bigger and depth_counter < depth: gets_bigger = False keys = clusters.keys() From f137735651df5ed1e5f7c36771cee88c41a4b7e9 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 31 Aug 2015 16:42:20 -0400 Subject: [PATCH 031/191] Added dynamic changing of depth in seeds() iterator. --- cluster_algebra.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index c8f568e8c9a..1ac41d58d84 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -424,10 +424,6 @@ def seeds(self, depth=infinity, mutating_F=True): clusters = {} clusters[cl] = [ self.current_seed, range(n) ] gets_bigger = True - #&&& - #Do we need to have a maximum depth built into the iterator? - #I would hope that we can remove it and add dynamic changing of the depth in find_cluster_variables - #using something like *** above while gets_bigger and depth_counter < depth: gets_bigger = False keys = clusters.keys() @@ -445,7 +441,9 @@ def seeds(self, depth=infinity, mutating_F=True): # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] - yield new_sd + new_depth = yield new_sd + if new_depth > depth: + depth = new_depth depth_counter += 1 # DESIDERATA. Some of these are probably unrealistic From 7ac3e74cf07cc18476df699588a2e3a0e95938d2 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 31 Aug 2015 17:08:59 -0400 Subject: [PATCH 032/191] Better comments and possible commented code in find_cluster_variable. --- cluster_algebra.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 1ac41d58d84..fa5fb2db1be 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -384,14 +384,18 @@ def find_cluster_variable(self, g_vector, depth=infinity): next(seeds) except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) - #*** referred to in &&& below - #cont = raw_input("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))+" Continue searching? (y or n):") + + # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, + # the following code would allow the user to continue searching the exchange graph + #cont = raw_input("Could not find a cluster variable with g-vector %s up to mutation depth %s."%(str(g_vector),str(depth))+" Continue searching? (y or n):") #if cont == 'y': # new_depth = 0 - # while int(new_depth) <= mutation_counter: - # new_depth = raw_input("Please enter a new depth greater than %s:"%str(mutation_counter)) + # while new_depth <= depth: + # new_depth = raw_input("Please enter a new mutation search depth greater than %s:"%str(depth)) + # seeds.send(new_depth) #else: # raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) + mutation_counter += 1 return copy(self._path_dict[g_vector]) From f011c61c6182abb5d6199dc77f19572ad4965aaa Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 15 Sep 2015 20:08:55 +0200 Subject: [PATCH 033/191] Fix iterator issues --- cluster_algebra.py | 58 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index fa5fb2db1be..800b027e590 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -87,6 +87,10 @@ def _repr_(self): return "The seed of %s obtained from the initial by mutating in direction %s"%(str(self.parent()),str(self._path[0])) else: return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) + + @property + def depth(self): + return len(self._path) def parent(self): return self._parent @@ -304,7 +308,10 @@ def __init__(self, data, scalars=ZZ): self._n = n self._m = m self.reset_current_seed() - + + # internal data for exploring the exchange graph + self.reset_exploring_iterator() + # enable standard cohercions: everything that is in the base can be coherced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) @@ -334,9 +341,17 @@ def reset_current_seed(self): r""" Reset the current seed to the initial one """ + self._seed = self.initial_seed + + @property + def initial_seed(self): + r""" + Return the initial seed + """ n = self.rk I = identity_matrix(n) - self._seed = ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) + return ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) + def g_vectors_so_far(self): r""" @@ -377,11 +392,11 @@ def find_cluster_variable(self, g_vector, depth=infinity): ``depth``: maximum distance from ``self.current_seed`` to reach. """ g_vector = tuple(g_vector) - seeds = self.seeds(depth=depth) mutation_counter = 0 - while g_vector not in self.g_vectors_so_far(): + while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: - next(seeds) + seed = next(self._sd_iter) + self._explored_depth = seed.depth except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) @@ -414,19 +429,24 @@ def lift(self, x): def retract(self, x): return self(x) - def seeds(self, depth=infinity, mutating_F=True): + def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): r""" Return an iterator producing all seeds of ``self`` up to distance - ``depth`` from ``self.current_seed``. + ``depth`` from ``self.initial_seed`` or ``self.current_seed``. If ``mutating_F`` is set to false it does not compute F_polynomials """ - yield self.current_seed + if from_current_seed: + seed = self.current_seed + else: + seed = self.initial_seed + + yield seed depth_counter = 0 n = self.rk - cl = frozenset(self.current_seed.g_matrix().columns()) + cl = frozenset(seed.g_matrix().columns()) clusters = {} - clusters[cl] = [ self.current_seed, range(n) ] + clusters[cl] = [ seed, range(n) ] gets_bigger = True while gets_bigger and depth_counter < depth: gets_bigger = False @@ -445,11 +465,23 @@ def seeds(self, depth=infinity, mutating_F=True): # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] - new_depth = yield new_sd - if new_depth > depth: - depth = new_depth + # Use this if we want to have the user pass info to the + # iterator + #new_depth = yield new_sd + #if new_depth > depth: + # depth = new_depth + yield new_sd depth_counter += 1 + def reset_exploring_iterator(self): + self._sd_iter = self.seeds() + self._explored_depth = 0 + + def explore_to_depth(self, depth): + while self._explored_depth <= depth: + seed = next(self._sd_iter) + self._explored_depth = seed.depth + # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass From ab54d9644703565ff5fc41cc364660d715add128 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 15 Sep 2015 23:17:40 +0200 Subject: [PATCH 034/191] Added try for better handling of finite types (i.e when the internal iterator termiates) --- cluster_algebra.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 800b027e590..97d684eff66 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -479,8 +479,9 @@ def reset_exploring_iterator(self): def explore_to_depth(self, depth): while self._explored_depth <= depth: - seed = next(self._sd_iter) - self._explored_depth = seed.depth + try: + seed = next(self._sd_iter) + self._explored_depth = seed.depth # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): From 8346cc9880df8d1c81be2aea7049eebc579a559d Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 15 Sep 2015 23:18:38 +0200 Subject: [PATCH 035/191] Fixed typo --- cluster_algebra.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 97d684eff66..aab9c9f9b42 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -482,6 +482,8 @@ def explore_to_depth(self, depth): try: seed = next(self._sd_iter) self._explored_depth = seed.depth + except: + pass # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): From 6d8addd3a0a3c26ba2a00e1692ede5050a39c3ac Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 16 Sep 2015 01:09:34 +0200 Subject: [PATCH 036/191] Fixed bug: infinite loop in explore_to_depth --- cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index aab9c9f9b42..3061b3861b9 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -483,7 +483,7 @@ def explore_to_depth(self, depth): seed = next(self._sd_iter) self._explored_depth = seed.depth except: - pass + break # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): From de2fb842b422d99b7519c0023a06f301ba94f8d1 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 17 Sep 2015 00:16:50 +0200 Subject: [PATCH 037/191] Added comparison feature for seeds --- cluster_algebra.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 3061b3861b9..83f9bcf8c8e 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -8,7 +8,7 @@ #### # Helper functions. #### -# These my have to go in a more standard place or just outside this file +# These may have to go in a more standard place or just outside this file #### # Elements of a cluster algebra @@ -80,6 +80,10 @@ def __copy__(self): other._path = copy(self._path) return other + def _eq_(self, other): + P = self.c_matrix().inverse()*other.c_matrix() + return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() + def _repr_(self): if self._path == []: return "The initial seed of %s"%str(self.parent()) @@ -337,6 +341,19 @@ def current_seed(self): """ return self._seed + @current_seed.setter + def current_seed(self, seed): + r""" + Set ``self._seed`` to ``seed`` if it makes sense. + """ + computed_sd = self.initial_seed + computed_sd.mutation_sequence(seed.path, mutating_F=False) + if computed_sd == seed: + self._seed = seed + else: + raise ValueError("This is not a seed in this cluster algebra.") + + def reset_current_seed(self): r""" Reset the current seed to the initial one From fcf8cea053ff7e70f49cd133fa5b7ee12e43a96b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 17 Sep 2015 00:25:58 +0200 Subject: [PATCH 038/191] Finished current_seed setter --- cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 83f9bcf8c8e..d5f72def792 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -80,7 +80,7 @@ def __copy__(self): other._path = copy(self._path) return other - def _eq_(self, other): + def __eq__(self, other): P = self.c_matrix().inverse()*other.c_matrix() return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() @@ -347,7 +347,7 @@ def current_seed(self, seed): Set ``self._seed`` to ``seed`` if it makes sense. """ computed_sd = self.initial_seed - computed_sd.mutation_sequence(seed.path, mutating_F=False) + computed_sd.mutation_sequence(seed._path, mutating_F=False) if computed_sd == seed: self._seed = seed else: From 88bb3f8ad2762a6b9813499254972c392ed2573a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 17 Sep 2015 01:01:17 +0200 Subject: [PATCH 039/191] Added contains_seed --- cluster_algebra.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index d5f72def792..46e2b6c8e4f 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -346,13 +346,15 @@ def current_seed(self, seed): r""" Set ``self._seed`` to ``seed`` if it makes sense. """ - computed_sd = self.initial_seed - computed_sd.mutation_sequence(seed._path, mutating_F=False) - if computed_sd == seed: + if self.contains_seed(seed): self._seed = seed else: raise ValueError("This is not a seed in this cluster algebra.") + def contains_seed(self, seed): + computed_sd = self.initial_seed + computed_sd.mutation_sequence(seed._path, mutating_F=False) + return computed_sd == seed def reset_current_seed(self): r""" From 42c43d5b5737593fc351ffb207055ca978487893 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Sep 2015 03:15:30 +0200 Subject: [PATCH 040/191] Improved repr of cluster variables --- cluster_algebra.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 46e2b6c8e4f..83f317eb618 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -58,6 +58,9 @@ def g_vector(self): # exchange matrix is not invertible. raise NotImplementederror("This should be computed by substitution") + def _repr_(self): + # use this to factor d-vector in the representation + return repr(self.lift_to_field()) #### # Seeds #### @@ -228,7 +231,7 @@ def path_form_initial_seed(self): # though one may write weird things like "sink" in A.current_seed and get # True as an answer. def __contains__(self, element): - if isinstance(element, ClusterAlgebraElement ): + if isinstance(element, ClusterAlgebraElement): cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk) ] else: element = tuple(element) @@ -371,6 +374,10 @@ def initial_seed(self): I = identity_matrix(n) return ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) + @property + def initial_b_matrix(self): + n = self.rk + return copy(self._B0[:n,:n]) def g_vectors_so_far(self): r""" @@ -409,6 +416,10 @@ def find_cluster_variable(self, g_vector, depth=infinity): g-vector ``g_vector`` from the initial seed. ``depth``: maximum distance from ``self.current_seed`` to reach. + + WARNING: if this method is interrupted then ``self._sd_iter`` is left in + an unusable state. To use again this method it is then necessary to + reset ``self._sd_iter`` via self.reset_exploring_iterato() """ g_vector = tuple(g_vector) mutation_counter = 0 From d753ace2613a2de69c2b4e1ca006e8a5b9e7de8e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Sep 2015 05:30:04 +0200 Subject: [PATCH 041/191] Implemented g-vectors for principal coefficients --- cluster_algebra.py | 144 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 83f317eb618..9a3bca9e5b1 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -5,29 +5,22 @@ from sage.structure.parent import Parent from sage.rings.integer_ring import ZZ -#### -# Helper functions. -#### -# These may have to go in a more standard place or just outside this file - -#### +################################################################################ # Elements of a cluster algebra -#### +################################################################################ class ClusterAlgebraElement(ElementWrapper): - # this is to store extra information like g-vector: what I am thinking is to - # store the g_vector whenever possible and pass it along when doing sums of - # elements with the same degree or multiplications of any two elements. - # This can potentially slow things down and make life harder. We need to - # redefine _add_ _mul_ and _lmul_ _rmul_ accordingly if we decide that there - # is no other way to compute g-vectors - def __init__(self, parent, value, g_vector=None): + def __init__(self, parent, value): ElementWrapper.__init__(self, parent=parent, value=value) - self._g_vector = g_vector + + # setup methods defined only in special cases + if parent._deg_matrix: + self.g_vector = MethodType(g_vector, self, self.__class__) + self.is_homogeneous = MethodType(is_homogeneous, self, self.__class__) + self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) - # This function needs to be removed once AdditiveMagmas.Subobjects - # implements _add_ + # This function needs to be removed once AdditiveMagmas.Subobjects implements _add_ def _add_(self, other): return self.parent().retract(self.lift() + other.lift()) @@ -50,21 +43,42 @@ def d_vector(self): v2 = vector(initial.exponents()[0][:n]) return tuple(v1-v2) - def g_vector(self): - # at the moment it is not immediately clear to me how to compute this - # assuming this is a generic element of the cluster algebra it is not - # going to be homogeneous, expecially if we are not using principal - # coefficients. I am not sure it can be done if the bottom part of the - # exchange matrix is not invertible. - raise NotImplementederror("This should be computed by substitution") - def _repr_(self): # use this to factor d-vector in the representation return repr(self.lift_to_field()) + + #### -# Seeds +# Methods not always defined #### +def g_vector(self): + components = self.homogeneous_components() + if len(components) == 1: + return components.keys()[0] + else: + raise ValueError("This element is not homogeneous.") + +def is_homogeneous(self): + return len(self.homogeneous_components()) == 1 + +def homogeneous_components(self): + components = dict() + x = self.lift() + monomials = x.monomials() + for m in monomials: + gvect = tuple(self.parent()._deg_matrix*vector(m.exponents()[0])) + if gvect in components: + components[gvect] += self.parent().retract(x.monomial_coefficient(m)*m) + else: + components[gvect] = self.parent().retract(x.monomial_coefficient(m)*m) + return components + + +################################################################################ +# Seeds +################################################################################ + class ClusterAlgebraSeed(SageObject): def __init__(self, B, C, G, parent): @@ -238,10 +252,9 @@ def __contains__(self, element): cluster = map( tuple, self.g_matrix().columns() ) return element in cluster - -#### +################################################################################ # Cluster algebras -#### +################################################################################ class ClusterAlgebra(Parent): # it would be nice to have inject_variables() to allow the user to @@ -261,20 +274,12 @@ def __init__(self, data, scalars=ZZ): # TODO: right now we use ClusterQuiver to parse input data. It looks # like a good idea but we should make sure it is. Q = ClusterQuiver(data) - B0 = Q.b_matrix() - n = B0.ncols() - # We use a different m than ClusterSeed and ClusterQuiver: their m is our m-n. - # Should we merge this behaviour? what is the notation in CA I-IV? - # They use the m-n convention, the m convention comes out of Fock-Goncharov or Gekhtman-Shapiro-Vainshtein - m = B0.nrows() + n = Q.n() + B0 = Q.b_matrix()[:n,:] + M0 = Q.b_matrix()[n:,:] + m = M0.nrows() + n I = identity_matrix(n) - # add methods that are defined only for special cases - if n == 2: - self.greedy_element = MethodType(greedy_element, self, self.__class__) - self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) - self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) - # TODO: is ZZ the correct ambient here? # ambient space for F-polynomials self._U = PolynomialRing(ZZ, ['u%s'%i for i in xrange(n)]) @@ -308,10 +313,22 @@ def __init__(self, data, scalars=ZZ): # these are used for computing cluster variables using separation of # additions - self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n,m)])) for j in xrange(n)]) - self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(m)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i+n)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) + + # recover g-vector from monomials + # TODO: there should be a method to compute partial inverses + # right now we are failing + #M_p = matrix([ (row if all(x>=0 for x in row) else vector(ZZ, n)) for row in M0.rows() ]) + #if M0.rank() == n: + # self._deg_matrix = block_matrix([[I,-B0*(M0.transpose()*M0).inverse()*M0.transpose()]]) + if M0 == I: + self._deg_matrix = block_matrix([[I,-B0]]) + else: + self._deg_matrix = None self._B0 = copy(B0) + self._M0 = copy(M0) self._n = n self._m = m self.reset_current_seed() @@ -319,6 +336,12 @@ def __init__(self, data, scalars=ZZ): # internal data for exploring the exchange graph self.reset_exploring_iterator() + # add methods that are defined only for special cases + if n == 2: + self.greedy_element = MethodType(greedy_element, self, self.__class__) + self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) + self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) + # enable standard cohercions: everything that is in the base can be coherced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) @@ -372,12 +395,12 @@ def initial_seed(self): """ n = self.rk I = identity_matrix(n) - return ClusterAlgebraSeed(self._B0[:n,:n], I, I, self) + return ClusterAlgebraSeed(self._B0, I, I, self) @property def initial_b_matrix(self): n = self.rk - return copy(self._B0[:n,:n]) + return copy(self._B0) def g_vectors_so_far(self): r""" @@ -525,12 +548,10 @@ def upper_bound(self): def lower_bound(self): pass - - - #### -# Methods that are only defined for special cases +# Methods only defined for special cases #### + # Greedy elements exist only in rank 2 # Does not yet take into account coefficients, this can probably be done by # using the greedy coefficients to write down the F-polynomials @@ -594,28 +615,3 @@ def greedy_coeff(self,d_vector,p,q): def theta_basis_element(self, g_vector): pass -#### -# Scratchpad -#### - -# Shall we use properties with setters and getters? This is the example -# maybe it is not a great idea but it saves on parenthesis and makes quantities immutable at the same time -# I am not sure I can give an opinion yet, maybe after I see it in action -#class C(object): -#def __init__(self): -# self._x = None - -#@property -#def x(self): -# """I'm the 'x' property.""" -# return self._x - -#@x.setter -#def x(self, value): -# self._x = value - -#@x.deleter -#def x(self): -# del self._x - - From 57f032ab961d8ff640b337c2dfbd69fc11cd86a1 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 9 Oct 2015 20:46:26 +0200 Subject: [PATCH 042/191] added variable names, few changes to g_vectors. Found a bug with FractionField that potentially messes up denominator() --- cluster_algebra.py | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 9a3bca9e5b1..b542bea5846 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -269,7 +269,7 @@ class ClusterAlgebra(Parent): Element = ClusterAlgebraElement - def __init__(self, data, scalars=ZZ): + def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_prefix='x', coefficients_names=None, cluster_variables_names=None): # Temporary variables # TODO: right now we use ClusterQuiver to parse input data. It looks # like a good idea but we should make sure it is. @@ -300,30 +300,52 @@ def __init__(self, data, scalars=ZZ): if m>n: # do we want change this to its fraction field (so that we can # invert coefficients)? - base = LaurentPolynomialRing(scalars, ['x%s'%i for i in xrange(n,m)]) + # I really think we do. S. + # we may want to allow coeff_vars to be a single name and produce the list on the fly + if coefficients_names: + if len(coefficients_names) == m-n: + variables = coefficients_names + else: + raise ValueError("coefficients_names should be a list of %d valid variable names"%(m-n)) + else: + if coefficients_prefix == cluster_variables_prefix: + offset = n + else: + offset = 0 + variables = [coefficients_prefix+'%s'%i for i in xrange(offset,m-n+offset)] + base = LaurentPolynomialRing(scalars, variables) else: base = scalars # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) # *** here we might want to replace scalars with base and m with n - self._ambient = LaurentPolynomialRing(scalars, 'x', m) + if cluster_variables_names: + if len(cluster_variables_names) == n: + variables = cluster_variables_names + else: + raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) + else: + variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] + self._ambient = LaurentPolynomialRing(base, variables) self._ambient_field = self._ambient.fraction_field() # TODO: understand if we need this #self._populate_coercion_lists_() # these are used for computing cluster variables using separation of # additions - self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(i+n)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # recover g-vector from monomials # TODO: there should be a method to compute partial inverses # right now we are failing - #M_p = matrix([ (row if all(x>=0 for x in row) else vector(ZZ, n)) for row in M0.rows() ]) - #if M0.rank() == n: - # self._deg_matrix = block_matrix([[I,-B0*(M0.transpose()*M0).inverse()*M0.transpose()]]) - if M0 == I: - self._deg_matrix = block_matrix([[I,-B0]]) + if M0.rank() == n: + #M0p = matrix(map(lambda row: map(lambda x: max(x, 0),row), M0.rows())) + #M0m = M0 - M0p + #self._deg_matrix = block_matrix([[I,-B0*(M0p.transpose()*M0p).inverse()*M0p.transpose()]]) + self._deg_matrix = block_matrix([[I,-B0*(M0.transpose()*M0).inverse()*M0.transpose()]]) + #if M0 == I: + # self._deg_matrix = block_matrix([[I,-B0]]) else: self._deg_matrix = None From 42e20e85a608a8e90ef3e82ffb5a10279997e6eb Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 19 Oct 2015 00:39:30 +0200 Subject: [PATCH 043/191] Added TropicalSemifield; of course LaurentPolynomialRing(TropicalSemifield(prefix='y', n=3),'x1,x2,x3') fails miserably --- cluster_algebra.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index b542bea5846..86003013951 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -637,3 +637,76 @@ def greedy_coeff(self,d_vector,p,q): def theta_basis_element(self, g_vector): pass + + +################################################################################ +# Elements of a tropical semifield +################################################################################ + +class TropicalSemifieldElement(ElementWrapper): + + def __init__(self, parent, value): + ElementWrapper.__init__(self, parent=parent, value=value) + + def vector(self): + try: + return vector(self.lift().exponents()[0]) + except: + return vector(ZZ, self.parent().ngens()) + + def _add_(self, other): + s = self.vector() + o = other.vector() + r = map(min,zip(s,o)) + v = self.parent().gens() + return prod([ x**i for (x,i) in zip(v,r) ]) + + def _repr_(self): + return repr(self.lift()) + + def __invert__(self): + return self.parent().retract(self.lift().__invert__()) + +################################################################################ +# Tropical Semifield +################################################################################ + +class TropicalSemifield(Parent): + + Element = TropicalSemifieldElement + + def __init__(self, n=1, prefix='x', names=None): + + # setup Parent and ambient + if names == None: + names = [prefix+'%s'%i for i in xrange(n)] + Parent.__init__(self, base=ZZ, category=Rings().Subobjects(), names=names, gens=names) + self._ambient = LaurentPolynomialRing(ZZ, names) + + def gens(self): + return map(lambda x: self(x), self._ambient.gens()) + + def ngens(self): + return self._ambient.ngens() + + # enable standard cohercions: everything that is in the base can be coerced + def _coerce_map_from_(self, other): + return self.base().has_coerce_map_from(other) + + def _repr_(self): + return "Tropical Semifield on %s generators"%self.ngens() + + def _an_element_(self): + return self(1) + + def lift(self, x): + return x.value + + def retract(self, x): + return self(x) + + def ambient(self): + return self._ambient + + + From 42e2be1de47b0197f2a626c95c8c5543298ec7d4 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 20 Oct 2015 01:02:14 +0200 Subject: [PATCH 044/191] Bad news, we are slow :( --- cluster_algebra.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index 86003013951..10861027ce4 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -218,6 +218,10 @@ def _mutated_F(self, k, old_g_vector): # sage: %time void = list(seeds) # CPU times: user 26.8 s, sys: 21 ms, total: 26.9 s # Wall time: 26.8 s + ##### + # Bad news: as of 19/10/2015 we got a huge slowdown: + # right now it takes 150s with / and 100s with // + # what did we do wrong? return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): From 2bd1943191e5aa56ae7ab7ba0364612d8f76b069 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 20 Oct 2015 01:17:24 +0200 Subject: [PATCH 045/191] Figured out why we were slow; I do not like the solution. Dylan any idea? --- cluster_algebra.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 10861027ce4..beb2f5f5e2c 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -222,7 +222,15 @@ def _mutated_F(self, k, old_g_vector): # Bad news: as of 19/10/2015 we got a huge slowdown: # right now it takes 150s with / and 100s with // # what did we do wrong? - return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) + ## + # I figured it out: the problem is that casting the result to alg._U is + # quite slow: it amounts to run // instead of / :( + # do we need to perform this or can we be less precise here and allow + # F-polynomials to be rational funtions? + # I am partucularly unhappy about this, for the moment the correct and + # slow code is commented + #return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) + return (pos+neg)/alg.F_polynomial(old_g_vector) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) From de5cd7b568198fa71b0773adf051bfd04575af8b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 20 Oct 2015 01:23:06 +0200 Subject: [PATCH 046/191] Few more comments on the slowdown --- cluster_algebra.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cluster_algebra.py b/cluster_algebra.py index beb2f5f5e2c..5dbb9950b48 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -230,6 +230,10 @@ def _mutated_F(self, k, old_g_vector): # I am partucularly unhappy about this, for the moment the correct and # slow code is commented #return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) + ## + # One more comment: apparently even without casting the result is a + # polynomial! This is really weird but I am not going to complain. I + # suppose we should not do the casting then return (pos+neg)/alg.F_polynomial(old_g_vector) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): From 7dff379ee02dd4ef8436690dc5c0f3f1ef1e4eed Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 2 Nov 2015 15:42:42 +0100 Subject: [PATCH 047/191] Few addition to TropicalSemifield; Change to the ambient of F-polynomials --- cluster_algebra.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 5dbb9950b48..4e0a124950d 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -1,4 +1,3 @@ -import time from types import MethodType from sage.structure.element_wrapper import ElementWrapper from sage.structure.sage_object import SageObject @@ -296,9 +295,11 @@ def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_ m = M0.nrows() + n I = identity_matrix(n) - # TODO: is ZZ the correct ambient here? - # ambient space for F-polynomials - self._U = PolynomialRing(ZZ, ['u%s'%i for i in xrange(n)]) + # Ambient space for F-polynomials + # For speed purposes we need to have QQ here instead of the more natural ZZ + # the reason is that _mutated_F is faster if we do not cast the result + # to polynomials but then we get "rational" coefficients + self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) # already computed data # TODO: I am unhappy because _g_vect_set is slightly redundant (we could @@ -676,7 +677,7 @@ def _add_(self, other): r = map(min,zip(s,o)) v = self.parent().gens() return prod([ x**i for (x,i) in zip(v,r) ]) - + def _repr_(self): return repr(self.lift()) @@ -696,7 +697,7 @@ def __init__(self, n=1, prefix='x', names=None): # setup Parent and ambient if names == None: names = [prefix+'%s'%i for i in xrange(n)] - Parent.__init__(self, base=ZZ, category=Rings().Subobjects(), names=names, gens=names) + Parent.__init__(self, base=ZZ, category=CommutativeRings().Subobjects(), names=names, gens=names) self._ambient = LaurentPolynomialRing(ZZ, names) def gens(self): @@ -724,5 +725,15 @@ def retract(self, x): def ambient(self): return self._ambient + def is_field(self): + return False + + def is_prime_field(self): + return False + + def is_integral_domain(self, proof=True): + return True + def is_finite(self): + return False From add611fc995f6295f40be61106d241959b5ff21b Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 2 Nov 2015 15:43:46 +0100 Subject: [PATCH 048/191] Fixed a typo, added some code that is far from complete, but I wanted to push the typo fix --- cluster_algebra.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 4e0a124950d..26b0354f27a 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -29,6 +29,7 @@ def lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? + #For cluster variables yes we can do better: use CA4 Prop 7.16 def d_vector(self): n = self.parent().rk one = self.parent().ambient_field()(1) @@ -193,6 +194,25 @@ def mutate(self, k, inplace=True, mutating_F=True): if not inplace: return seed + def mutate_initial(self, k, inplace=True, mutating_F=True): + if inplace: + seed = self + else: + seed = copy(self) + + n = seed.parent().rk + + if k not in xrange(n): + raise ValueError('Cannot mutate in direction ' + str(k) + '.') + + #store mutation path + if seed._path != [] and seed._path[0] == k: + seed._path.pop(0) + else: + seed._path.insert(0,k) + + + def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) @@ -249,7 +269,7 @@ def mutation_sequence(self, sequence, inplace=True, mutating_F=True): if not inplace: return seed - def path_form_initial_seed(self): + def path_from_initial_seed(self): return copy(self._path) # TODO: ideally we should allow to mutate in direction "this g-vector" or From 504680ad7adbefdabe50d1fa7ddbebc3c27aa194 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 2 Nov 2015 18:19:34 +0100 Subject: [PATCH 049/191] Corrected an error in seeds(), it slowed the calculation of E8 by 5 seconds, maybe not worth optimizing further --- cluster_algebra.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 26b0354f27a..addd6d446ca 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -570,7 +570,12 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_matrix().columns()) if new_cl in clusters: - if i in clusters[new_cl][1]: + #the following lines were throwing away the wrong mutation direction + #because it is comparing sets and not ordered sets + #removing it slowed my computation of E8 by 5 seconds + #is it worth it to optimize this? + #would it even speed things up to compare lists instead of sets? + if i in clusters[new_cl][1] and false: clusters[new_cl][1].remove(i) else: gets_bigger = True From deae7cf9b0c69cba39d98e3426599026119ecf48 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 5 Nov 2015 00:08:11 +0100 Subject: [PATCH 050/191] Temporary fix for some of the bugs of LaurentPolynomialRing --- cluster_algebra.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index addd6d446ca..f2c7724e58d 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -304,7 +304,7 @@ class ClusterAlgebra(Parent): Element = ClusterAlgebraElement - def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_prefix='x', coefficients_names=None, cluster_variables_names=None): + def __init__(self, data, scalars=ZZ, coefficients_prefix='y', cluster_variables_prefix='x', coefficients_names=None, cluster_variables_names=None): # Temporary variables # TODO: right now we use ClusterQuiver to parse input data. It looks # like a good idea but we should make sure it is. @@ -341,7 +341,7 @@ def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_ # we may want to allow coeff_vars to be a single name and produce the list on the fly if coefficients_names: if len(coefficients_names) == m-n: - variables = coefficients_names + coefficients = coefficients_names else: raise ValueError("coefficients_names should be a list of %d valid variable names"%(m-n)) else: @@ -349,10 +349,13 @@ def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_ offset = n else: offset = 0 - variables = [coefficients_prefix+'%s'%i for i in xrange(offset,m-n+offset)] - base = LaurentPolynomialRing(scalars, variables) + coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m-n+offset)] + # TODO: replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented + base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars + # BUG WORKAROUD: remove the following line once base is recognized as a subobject of ambient + coefficients = [] # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) # *** here we might want to replace scalars with base and m with n @@ -363,14 +366,19 @@ def __init__(self, data, scalars=ZZ, coefficients_prefix='x', cluster_variables_ raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) else: variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] - self._ambient = LaurentPolynomialRing(base, variables) + # BUG WORKAROUND: In an ideal world this should be + # self._ambient = LaurentPolynomialRing(base, variables) + # but at the moment base is not recognized as a subobject of ambient + self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) self._ambient_field = self._ambient.fraction_field() # TODO: understand if we need this #self._populate_coercion_lists_() - # these are used for computing cluster variables using separation of - # additions - self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + # these are used for computing cluster variables using separation of additions + # BUG WORKAROUND: replace the following line with + # self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + # once base is recognized as a subobject of ambient + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # recover g-vector from monomials From 62fbc9045f6c6b07d6861ff446012528e93c6ee4 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 5 Nov 2015 00:40:32 +0100 Subject: [PATCH 051/191] Fixed properly the bug with seeds in E_8. It is slightly faster than doing nothing now but one has to check different cases: I guess that when there are no repetition of cluster it is slower. --- cluster_algebra.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index f2c7724e58d..8a35c71fdf7 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -578,13 +578,11 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_matrix().columns()) if new_cl in clusters: - #the following lines were throwing away the wrong mutation direction - #because it is comparing sets and not ordered sets - #removing it slowed my computation of E8 by 5 seconds - #is it worth it to optimize this? - #would it even speed things up to compare lists instead of sets? - if i in clusters[new_cl][1] and false: - clusters[new_cl][1].remove(i) + j = map(tuple,clusters[new_cl][0].g_matrix().columns()).index(new_sd.g_vector(i)) + try: + clusters[new_cl][1].remove(j) + except: + pass else: gets_bigger = True # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed From 1a8b758f62d446ac90df0b3520ac0724dece0c2b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 7 Nov 2015 20:27:18 +0100 Subject: [PATCH 052/191] Cleaned up ClusterAlgebra.__init__ and few more changes --- cluster_algebra.py | 228 +++++++++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 99 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 8a35c71fdf7..0a0a1418377 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -12,9 +12,9 @@ class ClusterAlgebraElement(ElementWrapper): def __init__(self, parent, value): ElementWrapper.__init__(self, parent=parent, value=value) - + # setup methods defined only in special cases - if parent._deg_matrix: + if parent._is_principal: self.g_vector = MethodType(g_vector, self, self.__class__) self.is_homogeneous = MethodType(is_homogeneous, self, self.__class__) self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) @@ -23,6 +23,10 @@ def __init__(self, parent, value): def _add_(self, other): return self.parent().retract(self.lift() + other.lift()) + # I am not sure we want to have this function: its output is most of the times not in the algebra but it is convenient to have + def __div__(self, other): + return self.parent().retract(self.lift()/other.lift()) + # HACK: LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field def lift_to_field(self): return self.parent().lift_to_field(self) @@ -63,11 +67,12 @@ def is_homogeneous(self): return len(self.homogeneous_components()) == 1 def homogeneous_components(self): + deg_matrix = block_matrix([[self.parent()._M0,-self.parent()._B0]]) components = dict() x = self.lift() monomials = x.monomials() for m in monomials: - gvect = tuple(self.parent()._deg_matrix*vector(m.exponents()[0])) + gvect = tuple(deg_matrix*vector(m.exponents()[0])) if gvect in components: components[gvect] += self.parent().retract(x.monomial_coefficient(m)*m) else: @@ -108,7 +113,7 @@ def _repr_(self): return "The seed of %s obtained from the initial by mutating in direction %s"%(str(self.parent()),str(self._path[0])) else: return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) - + @property def depth(self): return len(self._path) @@ -125,18 +130,24 @@ def c_matrix(self): def c_vector(self, j): return tuple(self._C.column(j)) + def c_vectors(self): + return map(tuple, self._C.columns()) + def g_matrix(self): return copy(self._G) def g_vector(self, j): return tuple(self._G.column(j)) + def g_vectors(self): + return map(tuple, self._G.columns()) + def F_polynomial(self, j): return self.parent().F_polynomial(self.g_vector(j)) def cluster_variable(self, j): return self.parent().cluster_variable(self.g_vector(j)) - + def mutate(self, k, inplace=True, mutating_F=True): if inplace: seed = self @@ -174,7 +185,6 @@ def mutate(self, k, inplace=True, mutating_F=True): # g-vector path list g_vector = seed.g_vector(k) if g_vector not in seed.parent().g_vectors_so_far(): - seed.parent()._g_vect_set.add(g_vector) seed.parent()._path_dict[g_vector] = copy(seed._path) # F-polynomials if mutating_F: @@ -211,8 +221,6 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): else: seed._path.insert(0,k) - - def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) @@ -241,11 +249,11 @@ def _mutated_F(self, k, old_g_vector): # Bad news: as of 19/10/2015 we got a huge slowdown: # right now it takes 150s with / and 100s with // # what did we do wrong? - ## + ## # I figured it out: the problem is that casting the result to alg._U is # quite slow: it amounts to run // instead of / :( # do we need to perform this or can we be less precise here and allow - # F-polynomials to be rational funtions? + # F-polynomials to be rational funtions? # I am partucularly unhappy about this, for the moment the correct and # slow code is commented #return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) @@ -292,123 +300,134 @@ def __contains__(self, element): ################################################################################ class ClusterAlgebra(Parent): - # it would be nice to have inject_variables() to allow the user to - # automagically export the initial cluster variables into the sage shell. - # Unfortunately to do this we need to inherit form ParentWithGens and, as a - # drawback, we get several functions that are meaningless/misleading in a - # cluster algebra like ngens() or gens() - # If we only care about initial cluster variables we can always do the following: - # def inject_variables(self, scope=None, verbose=True): - # self._ambient.inject_variables(scope=scope,verbose=verbose) - # for labeled non-initial cluster variables we can also manually add them to the scope. + r""" + INPUT: + + - ``data`` -- some data defining a cluster algebra. + + - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra + is defined. + + - ``cluster_variables_prefix`` -- string (default 'x'). + + - ``cluster_variables_names`` -- a list of strings. Superseedes + ``cluster_variables_prefix``. + + - ``coefficients_prefix`` -- string (default 'y'). + + - ``coefficients_names`` -- a list of strings. Superseedes + ``cluster_variables_prefix``. + + - ``principal_coefficients`` -- bool (default: False). Superseedes any + coefficient defined by ``data``. + """ Element = ClusterAlgebraElement - def __init__(self, data, scalars=ZZ, coefficients_prefix='y', cluster_variables_prefix='x', coefficients_names=None, cluster_variables_names=None): + def __init__(self, data, **kwargs): + r""" + See :class:`ClusterAlgebra` for full documentation. + """ + # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. + # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented + # Temporary variables - # TODO: right now we use ClusterQuiver to parse input data. It looks - # like a good idea but we should make sure it is. Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n,:] - M0 = Q.b_matrix()[n:,:] - m = M0.nrows() + n I = identity_matrix(n) + if 'principal_coefficients' in kwargs and kwargs['principal_coefficients']: + M0 = I + else: + M0 = Q.b_matrix()[n:,:] + m = M0.nrows() + n # Ambient space for F-polynomials - # For speed purposes we need to have QQ here instead of the more natural ZZ - # the reason is that _mutated_F is faster if we do not cast the result - # to polynomials but then we get "rational" coefficients + # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) - # already computed data - # TODO: I am unhappy because _g_vect_set is slightly redundant (we could - # use _path_dict.keys() instead) but it is faster to check membership in - # sets than in lists and _path_dict.keys() returns a list. Depending on - # the number of cluster variables this may be relevant or not. - self._g_vect_set = set([ tuple(v) for v in I.columns() ]) - self._F_poly_dict = dict([ (v, self._U(1)) for v in self._g_vect_set ]) - self._path_dict = dict([ (v, []) for v in self._g_vect_set ]) - - # setup Parent and ambient - # TODO: at the moment self.base() is not a subobject of self.ambient() - # unfortunately if we change `scalars` to `base` in *** it becomes - # harder to list generators of ambient - if m>n: - # do we want change this to its fraction field (so that we can - # invert coefficients)? - # I really think we do. S. - # we may want to allow coeff_vars to be a single name and produce the list on the fly - if coefficients_names: - if len(coefficients_names) == m-n: - coefficients = coefficients_names + # Storage for computed data + self._path_dict = dict([ (v, []) for v in map(tuple,I.columns()) ]) + self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) + + # Determine the names of the initial cluster variables + if 'cluster_variables_names' in kwargs: + if len(kwargs['cluster_variables_names']) == n: + variables = kwargs['cluster_variables_names'] + else: + raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) + else: + try: + cluster_variables_prefix = kwargs['cluster_variables_prefix'] + except: + cluster_variables_prefix = 'x' + variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] + + # Determine scalars + try: + scalars = kwargs['scalars'] + except: + scalars = ZZ + + # Determine coefficients and setup self._base + if m>n: + if 'coefficients_names' in kwargs: + if len(kwargs['coefficients_names']) == m-n: + coefficients = kwargs['coefficients_names'] else: raise ValueError("coefficients_names should be a list of %d valid variable names"%(m-n)) - else: + else: + try: + coefficients_prefix = kwargs['coefficients_prefix'] + except: + coefficients_prefix = 'y' if coefficients_prefix == cluster_variables_prefix: offset = n else: offset = 0 coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m-n+offset)] - # TODO: replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented + # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars - # BUG WORKAROUD: remove the following line once base is recognized as a subobject of ambient + # TODO: next line should be removed when (***) is implemented coefficients = [] - # TODO: understand why using CommutativeAlgebras() instead of Rings() makes A(1) complain of missing _lmul_ - Parent.__init__(self, base=base, category=Rings(scalars).Subobjects()) - # *** here we might want to replace scalars with base and m with n - if cluster_variables_names: - if len(cluster_variables_names) == n: - variables = cluster_variables_names - else: - raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) - else: - variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] - # BUG WORKAROUND: In an ideal world this should be - # self._ambient = LaurentPolynomialRing(base, variables) - # but at the moment base is not recognized as a subobject of ambient + + # setup Parent and ambient + # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) self._ambient_field = self._ambient.fraction_field() - # TODO: understand if we need this - #self._populate_coercion_lists_() + # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ + Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) - # these are used for computing cluster variables using separation of additions - # BUG WORKAROUND: replace the following line with - # self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) - # once base is recognized as a subobject of ambient - self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + # Data to compute cluster variables using separation of additions + # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next + #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) - # recover g-vector from monomials - # TODO: there should be a method to compute partial inverses - # right now we are failing - if M0.rank() == n: - #M0p = matrix(map(lambda row: map(lambda x: max(x, 0),row), M0.rows())) - #M0m = M0 - M0p - #self._deg_matrix = block_matrix([[I,-B0*(M0p.transpose()*M0p).inverse()*M0p.transpose()]]) - self._deg_matrix = block_matrix([[I,-B0*(M0.transpose()*M0).inverse()*M0.transpose()]]) - #if M0 == I: - # self._deg_matrix = block_matrix([[I,-B0]]) - else: - self._deg_matrix = None + # Have we principal coefficients? + self._is_principal = (M0 == I) + # Store initial data self._B0 = copy(B0) self._M0 = copy(M0) self._n = n self._m = m self.reset_current_seed() - # internal data for exploring the exchange graph + # Internal data for exploring the exchange graph self.reset_exploring_iterator() - # add methods that are defined only for special cases + # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) + # TODO: understand if we need this + #self._populate_coercion_lists_() + # enable standard cohercions: everything that is in the base can be coherced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) @@ -447,7 +466,7 @@ def current_seed(self, seed): def contains_seed(self, seed): computed_sd = self.initial_seed computed_sd.mutation_sequence(seed._path, mutating_F=False) - return computed_sd == seed + return computed_sd == seed def reset_current_seed(self): r""" @@ -473,7 +492,7 @@ def g_vectors_so_far(self): r""" Return the g-vectors of cluster variables encountered so far. """ - return self._g_vect_set + return self._path_dict.keys() def F_polynomial(self, g_vector): g_vector= tuple(g_vector) @@ -520,7 +539,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): except: raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) - # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, + # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, # the following code would allow the user to continue searching the exchange graph #cont = raw_input("Could not find a cluster variable with g-vector %s up to mutation depth %s."%(str(g_vector),str(depth))+" Continue searching? (y or n):") #if cont == 'y': @@ -548,7 +567,13 @@ def lift(self, x): def retract(self, x): return self(x) - + + def gens(self): + r""" + Return the generators of :meth:`self.ambient` + """ + return map(self.retract, self.ambient().gens()) + def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): r""" Return an iterator producing all seeds of ``self`` up to distance @@ -596,9 +621,9 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): yield new_sd depth_counter += 1 - def reset_exploring_iterator(self): - self._sd_iter = self.seeds() - self._explored_depth = 0 + def reset_exploring_iterator(self, mutating_F=True): + self._sd_iter = self.seeds(mutating_F=mutating_F) + self._explored_depth = 0 def explore_to_depth(self, depth): while self._explored_depth <= depth: @@ -608,6 +633,11 @@ def explore_to_depth(self, depth): except: break + def cluster_fan(self, depth=infinity): + seeds = self.seeds(depth=depth, mutating_F=False) + cones = map(lambda s: Cone(s.g_vectors()), seeds) + return Fan(cones) + # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass @@ -690,12 +720,12 @@ def theta_basis_element(self, g_vector): ################################################################################ # Elements of a tropical semifield ################################################################################ - + class TropicalSemifieldElement(ElementWrapper): - + def __init__(self, parent, value): ElementWrapper.__init__(self, parent=parent, value=value) - + def vector(self): try: return vector(self.lift().exponents()[0]) @@ -708,13 +738,13 @@ def _add_(self, other): r = map(min,zip(s,o)) v = self.parent().gens() return prod([ x**i for (x,i) in zip(v,r) ]) - + def _repr_(self): return repr(self.lift()) - + def __invert__(self): return self.parent().retract(self.lift().__invert__()) - + ################################################################################ # Tropical Semifield ################################################################################ @@ -724,7 +754,7 @@ class TropicalSemifield(Parent): Element = TropicalSemifieldElement def __init__(self, n=1, prefix='x', names=None): - + # setup Parent and ambient if names == None: names = [prefix+'%s'%i for i in xrange(n)] From 68b1cf9d2e932874b80356058f223eaf08fc591e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 7 Nov 2015 21:32:04 +0100 Subject: [PATCH 053/191] More cleanups --- cluster_algebra.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 0a0a1418377..424f6da7d69 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -103,8 +103,10 @@ def __copy__(self): return other def __eq__(self, other): - P = self.c_matrix().inverse()*other.c_matrix() - return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() + return frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + # Why was I doing something so convoluted? + #P = self.c_matrix().inverse()*other.c_matrix() + #return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() def _repr_(self): if self._path == []: @@ -292,7 +294,7 @@ def __contains__(self, element): cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk) ] else: element = tuple(element) - cluster = map( tuple, self.g_matrix().columns() ) + cluster = self.g_vectors() return element in cluster ################################################################################ @@ -589,7 +591,7 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): yield seed depth_counter = 0 n = self.rk - cl = frozenset(seed.g_matrix().columns()) + cl = frozenset(seed.g_vectors()) clusters = {} clusters[cl] = [ seed, range(n) ] gets_bigger = True @@ -601,9 +603,9 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): while directions: i = directions.pop() new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) - new_cl = frozenset(new_sd.g_matrix().columns()) + new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: - j = map(tuple,clusters[new_cl][0].g_matrix().columns()).index(new_sd.g_vector(i)) + j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) except: From 2c2f7b577775aa943a45a9f665a4b3a09e26f293 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 3 Dec 2015 15:48:27 -0500 Subject: [PATCH 054/191] Added comment about equality of cluster seeds, are we considering seeds up to permutation of cluster variables? --- cluster_algebra.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 424f6da7d69..833f40833b7 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -33,7 +33,7 @@ def lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? - #For cluster variables yes we can do better: use CA4 Prop 7.16 + # For cluster variables yes we can do better: use CA4 Prop 7.16 def d_vector(self): n = self.parent().rk one = self.parent().ambient_field()(1) @@ -105,6 +105,10 @@ def __copy__(self): def __eq__(self, other): return frozenset(self.g_vectors()) == frozenset(other.g_vectors()) # Why was I doing something so convoluted? + # It looks like the idea was to consider seeds up to simultaneous permutation of rows and columns, + # the relation P between the c-matrices determines if there could exist such a permutation, + # the remaining checks then ask about the remaining data + #P = self.c_matrix().inverse()*other.c_matrix() #return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() From c348cb3920593c4593d6956482a961a1970acc25 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 3 Dec 2015 19:18:41 -0500 Subject: [PATCH 055/191] Random minor edits and comments throughout, resolved a couple of small TODO's. --- cluster_algebra.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 833f40833b7..4b3e91d5dd1 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -267,6 +267,8 @@ def _mutated_F(self, k, old_g_vector): # One more comment: apparently even without casting the result is a # polynomial! This is really weird but I am not going to complain. I # suppose we should not do the casting then + + # DR: Now I get the same computation time for / and //, 49.7s while simultaneously rebuiling sage return (pos+neg)/alg.F_polynomial(old_g_vector) def mutation_sequence(self, sequence, inplace=True, mutating_F=True): @@ -368,6 +370,7 @@ def __init__(self, data, **kwargs): except: cluster_variables_prefix = 'x' variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] + # why not just put str(i) instead of '%s'%i? # Determine scalars try: @@ -434,7 +437,7 @@ def __init__(self, data, **kwargs): # TODO: understand if we need this #self._populate_coercion_lists_() - # enable standard cohercions: everything that is in the base can be coherced + # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) @@ -505,22 +508,24 @@ def F_polynomial(self, g_vector): try: return self._F_poly_dict[g_vector] except: - # TODO: improve this error message to include the case in which we - # already know the path # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? # Yes, perhaps with the a prompt first, something like: #comp = raw_input("This F-polynomial has not been computed yet. It can be found using %s mutations. Continue? (y or n):"%str(directions.__len__())) #if comp == 'y': # ...compute the F-polynomial... - raise ValueError("This F-polynomial has not been computed yet. Did you explore the tree with compute_F=False ?") + if g_vector in self._path_dict: + raise ValueError("The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s."%(str(g_vector),str(self._path_dict[g_vector])) + else: + raise ValueError("The F-polynomial with g-vector %s has not been computed yet."%str(g_vector)) @cached_method(key=lambda a,b: tuple(b) ) def cluster_variable(self, g_vector): g_vector = tuple(g_vector) if not g_vector in self.g_vectors_so_far(): + # Should we let the self.F_polynomial below handle raising the exception? raise ValueError("This Cluster Variable has not been computed yet.") - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) F_std = self.F_polynomial(g_vector).subs(self._yhat) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() return self.retract(g_mon*F_std*F_trop) @@ -534,7 +539,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): WARNING: if this method is interrupted then ``self._sd_iter`` is left in an unusable state. To use again this method it is then necessary to - reset ``self._sd_iter`` via self.reset_exploring_iterato() + reset ``self._sd_iter`` via self.reset_exploring_iterator() """ g_vector = tuple(g_vector) mutation_counter = 0 @@ -543,7 +548,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): seed = next(self._sd_iter) self._explored_depth = seed.depth except: - raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) + raise ValueError("Could not find a cluster variable with g-vector %s up to mutation depth %s after performing %s mutations."%(str(g_vector),str(depth),str(mutation_counter))) # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, # the following code would allow the user to continue searching the exchange graph @@ -569,6 +574,9 @@ def lift_to_field(self, x): return self.ambient_field()(1)*x.value def lift(self, x): + r""" + Return x as an element of self._ambient + """ return x.value def retract(self, x): @@ -682,14 +690,14 @@ def greedy_element(self, d_vector): output = 0 for p in xrange(0,a2+1): for q in xrange(0,a1+1): - output += self.greedy_coeff(d_vector,p,q)*x1**(b*p)*x2**(c*q) + output += self.greedy_coefficient(d_vector,p,q)*x1**(b*p)*x2**(c*q) return self.retract(x1**(-a1)*x2**(-a2)*output) # Is this function something we want to make public or do we want to make this a # private method changing it to _greedy_coeff ? # Since we are giving long names to things we might want to change this into # greedy_coefficient -def greedy_coeff(self,d_vector,p,q): +def greedy_coefficient(self,d_vector,p,q): b = abs(self._B0[0,1]) c = abs(self._B0[1,0]) a1 = d_vector[0] @@ -703,13 +711,13 @@ def greedy_coeff(self,d_vector,p,q): bin = 0 if a2-c*q+k-1 >= k: bin = binomial(a2-c*q+k-1,k) - sum1 += (-1)**(k-1)*self.greedy_coeff(d_vector,p-k,q)*bin + sum1 += (-1)**(k-1)*self.greedy_coefficient(d_vector,p-k,q)*bin sum2 = 0 for l in range(1,q+1): bin = 0 if a1-b*p+l-1 >= l: bin = binomial(a1-b*p+l-1,l) - sum2 += (-1)**(l-1)*self.greedy_coeff(d_vector,p,q-l)*bin + sum2 += (-1)**(l-1)*self.greedy_coefficient(d_vector,p,q-l)*bin #print "sum1=",sum1,"sum2=",sum2 return max(sum1,sum2) @@ -717,7 +725,8 @@ def greedy_coeff(self,d_vector,p,q): # At the moment I know only how to compute theta basis in rank 2 # maybe we should let ClusterAlgebra have this method for any rank and have a # NotImplementedError to encourage someone (read: Greg) to code this -#I think Greg already has some code to do this +# I think Greg already has some code to do this +# I asked about the code and it seems Greg has very little confidence in the code he has so far... def theta_basis_element(self, g_vector): pass @@ -773,7 +782,7 @@ def gens(self): def ngens(self): return self._ambient.ngens() - # enable standard cohercions: everything that is in the base can be coerced + # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) @@ -798,6 +807,7 @@ def is_field(self): def is_prime_field(self): return False + # I'm not sure the term makes sense for a semifield, its group algebra certainly has to be an integral domain def is_integral_domain(self, proof=True): return True From 8979d2507e6cac6e8d4f4510624d9ffc0539f5ae Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 19 Dec 2015 11:58:41 +0100 Subject: [PATCH 056/191] Merge temporary stash --- cluster_algebra.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index 4b3e91d5dd1..d71a5badc70 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -24,7 +24,7 @@ def _add_(self, other): return self.parent().retract(self.lift() + other.lift()) # I am not sure we want to have this function: its output is most of the times not in the algebra but it is convenient to have - def __div__(self, other): + def _div_(self, other): return self.parent().retract(self.lift()/other.lift()) # HACK: LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field @@ -34,6 +34,10 @@ def lift_to_field(self): # this function is quite disgusting but at least it works for any element of # the algebra, can we do better? # For cluster variables yes we can do better: use CA4 Prop 7.16 + # It looks to me that Prop 7.16 in CA IV only works if you know the + # F-polynomial; in particular one ask d_vector() of a cluster variable (or + # maybe of a cluster monomial if we know the cluster decomposition). The + # current implementation is uglier but more general. def d_vector(self): n = self.parent().rk one = self.parent().ambient_field()(1) @@ -151,9 +155,15 @@ def g_vectors(self): def F_polynomial(self, j): return self.parent().F_polynomial(self.g_vector(j)) + def F_polynomials(self): + return (self.parent().F_polynomial(g) for g in self.g_vectors()) + def cluster_variable(self, j): return self.parent().cluster_variable(self.g_vector(j)) + def cluster_variables(self): + return (self.parent().cluster_variable(g) for g in self.g_vectors()) + def mutate(self, k, inplace=True, mutating_F=True): if inplace: seed = self @@ -209,7 +219,11 @@ def mutate(self, k, inplace=True, mutating_F=True): # wrap up if not inplace: return seed - + + # I am perplexed by this (yet unfinished) function. I was expecting + # mutate_initial do be a function of ClusterAlgebra: if we change the + # initial seed all the g-vectors, F_polynomials and paths need to mutate + # accordingly. Am I right? def mutate_initial(self, k, inplace=True, mutating_F=True): if inplace: seed = self From a4cf0bed54c62695c7bd9e28a1dcf6f2efbea60d Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 1 Mar 2016 23:24:02 +0100 Subject: [PATCH 057/191] Added missing ) --- cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster_algebra.py b/cluster_algebra.py index d71a5badc70..17a62fc6284 100644 --- a/cluster_algebra.py +++ b/cluster_algebra.py @@ -528,7 +528,7 @@ def F_polynomial(self, g_vector): #if comp == 'y': # ...compute the F-polynomial... if g_vector in self._path_dict: - raise ValueError("The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s."%(str(g_vector),str(self._path_dict[g_vector])) + raise ValueError("The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s."%(str(g_vector),str(self._path_dict[g_vector]))) else: raise ValueError("The F-polynomial with g-vector %s has not been computed yet."%str(g_vector)) From e9a6b92aadba1bb0664f0bcbd0c8000701632061 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 00:14:26 +0100 Subject: [PATCH 058/191] Moved cluster_algebra.py to the correct location --- cluster_algebra.py => src/sage/algebras/cluster_algebra.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cluster_algebra.py => src/sage/algebras/cluster_algebra.py (100%) diff --git a/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py similarity index 100% rename from cluster_algebra.py rename to src/sage/algebras/cluster_algebra.py From 6720a305e078230eeee07c74ba52a2f3380d63d0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 16:31:49 +0100 Subject: [PATCH 059/191] Removed code about tropical semifields --- src/sage/algebras/cluster_algebra.py | 85 ---------------------------- 1 file changed, 85 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 17a62fc6284..a100a1df75f 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -743,88 +743,3 @@ def greedy_coefficient(self,d_vector,p,q): # I asked about the code and it seems Greg has very little confidence in the code he has so far... def theta_basis_element(self, g_vector): pass - - - -################################################################################ -# Elements of a tropical semifield -################################################################################ - -class TropicalSemifieldElement(ElementWrapper): - - def __init__(self, parent, value): - ElementWrapper.__init__(self, parent=parent, value=value) - - def vector(self): - try: - return vector(self.lift().exponents()[0]) - except: - return vector(ZZ, self.parent().ngens()) - - def _add_(self, other): - s = self.vector() - o = other.vector() - r = map(min,zip(s,o)) - v = self.parent().gens() - return prod([ x**i for (x,i) in zip(v,r) ]) - - def _repr_(self): - return repr(self.lift()) - - def __invert__(self): - return self.parent().retract(self.lift().__invert__()) - -################################################################################ -# Tropical Semifield -################################################################################ - -class TropicalSemifield(Parent): - - Element = TropicalSemifieldElement - - def __init__(self, n=1, prefix='x', names=None): - - # setup Parent and ambient - if names == None: - names = [prefix+'%s'%i for i in xrange(n)] - Parent.__init__(self, base=ZZ, category=CommutativeRings().Subobjects(), names=names, gens=names) - self._ambient = LaurentPolynomialRing(ZZ, names) - - def gens(self): - return map(lambda x: self(x), self._ambient.gens()) - - def ngens(self): - return self._ambient.ngens() - - # enable standard coercions: everything that is in the base can be coerced - def _coerce_map_from_(self, other): - return self.base().has_coerce_map_from(other) - - def _repr_(self): - return "Tropical Semifield on %s generators"%self.ngens() - - def _an_element_(self): - return self(1) - - def lift(self, x): - return x.value - - def retract(self, x): - return self(x) - - def ambient(self): - return self._ambient - - def is_field(self): - return False - - def is_prime_field(self): - return False - - # I'm not sure the term makes sense for a semifield, its group algebra certainly has to be an integral domain - def is_integral_domain(self, proof=True): - return True - - def is_finite(self): - return False - From 346e5830dce971eacc3bb91e36726d51964d390c Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 17:01:47 +0100 Subject: [PATCH 060/191] import ClusterAlgebra into sage --- src/sage/algebras/all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/algebras/all.py b/src/sage/algebras/all.py index c585ea425e3..6c388961947 100644 --- a/src/sage/algebras/all.py +++ b/src/sage/algebras/all.py @@ -56,3 +56,4 @@ lazy_import('sage.algebras.q_system', 'QSystem') +lazy_import('sage.algebras.cluster_algebra', 'ClusterAlgebra') From 6f1f52882fd715a654f35a08bf037236590e9aba Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 17:11:36 +0100 Subject: [PATCH 061/191] added missing imports --- src/sage/algebras/cluster_algebra.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index a100a1df75f..2f73cdbe94b 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -3,6 +3,16 @@ from sage.structure.sage_object import SageObject from sage.structure.parent import Parent from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.misc.cachefunc import cached_method +from sage.rings.infinity import infinity +from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver +from sage.matrix.constructor import identity_matrix +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.categories.rings import Rings +from sage.misc.misc_c import prod +from copy import copy ################################################################################ # Elements of a cluster algebra From 8100cfa92ce407278b3343c91f5575745ab935f8 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 17:15:37 +0100 Subject: [PATCH 062/191] Fixed greedy_coefficient not consistently renamed --- src/sage/algebras/cluster_algebra.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 2f73cdbe94b..8e836548f30 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -455,7 +455,7 @@ def __init__(self, data, **kwargs): # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) - self.greedy_coeff = MethodType(greedy_coeff, self, self.__class__) + self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # TODO: understand if we need this @@ -718,9 +718,7 @@ def greedy_element(self, d_vector): return self.retract(x1**(-a1)*x2**(-a2)*output) # Is this function something we want to make public or do we want to make this a -# private method changing it to _greedy_coeff ? -# Since we are giving long names to things we might want to change this into -# greedy_coefficient +# private method changing it to _greedy_coefficient ? def greedy_coefficient(self,d_vector,p,q): b = abs(self._B0[0,1]) c = abs(self._B0[1,0]) From 43b70c637c84e954f4e5ab2dfcb966e8ef5445ca Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 17:22:04 +0100 Subject: [PATCH 063/191] Few more imports --- src/sage/algebras/cluster_algebra.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8e836548f30..096f708acd0 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,8 +1,11 @@ +# TODO: check that we import all we need and possibly move some import used +# rarely close to where needed from types import MethodType from sage.structure.element_wrapper import ElementWrapper from sage.structure.sage_object import SageObject from sage.structure.parent import Parent from sage.rings.integer_ring import ZZ +from sage.rings.integer import Integer from sage.rings.rational_field import QQ from sage.misc.cachefunc import cached_method from sage.rings.infinity import infinity @@ -13,6 +16,8 @@ from sage.categories.rings import Rings from sage.misc.misc_c import prod from copy import copy +from sage.functions.other import binomial + ################################################################################ # Elements of a cluster algebra From d3f00b721c772e1c469476236db430e84f57df5d Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 2 Mar 2016 18:13:25 +0100 Subject: [PATCH 064/191] Added comment on proposed change to mutate --- src/sage/algebras/cluster_algebra.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 096f708acd0..dfe8677fcd6 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -300,6 +300,14 @@ def _mutated_F(self, k, old_g_vector): # DR: Now I get the same computation time for / and //, 49.7s while simultaneously rebuiling sage return (pos+neg)/alg.F_polynomial(old_g_vector) + # TODO: I propose to rename this function mutate and change mutate into + # _mutate. Then we change the top of this function to check that sequence + # is iterable (otherwise we make a list of only one leement) and keep the + # rest as is. This introduces a small overhead on each call of mutate from + # the user but exposes only one function (similar to what happens now in + # ClusterSeed. Moreover this would be better suited to implement things like + # mutate at sinks and urban renewal. For speed purposes we can always call + # _mutate internally where needed. def mutation_sequence(self, sequence, inplace=True, mutating_F=True): seq = iter(sequence) From 990c4c6b0bfcd08079d13ff5bf9d156a15b346cc Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 14 Mar 2016 03:28:17 +0100 Subject: [PATCH 065/191] Implemented first attempt of exchange relations --- src/sage/algebras/cluster_algebra.py | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index dfe8677fcd6..b00bb3742ea 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -231,6 +231,16 @@ def mutate(self, k, inplace=True, mutating_F=True): # B-matrix seed._B.mutate(k) + # exchange relation + if self.parent()._store_exchange_relations: + ex_pair = frozenset([g_vector,old_g_vector]) + if ex_pair not in self.parent()._exchange_relations: + coefficient = self.coefficient(k).lift() + variables = zip(self.g_vectors(), self.b_matrix().column(k)) + Mp = [ (g,p) for (g,p) in variables if p > 0 ] + Mm = [ (g,-p) for (g,p) in variables if p < 0 ] + self.parent()._exchange_relations[ex_pair] = ( (Mp,coefficient.numerator()), (Mm,coefficient.denominator()) ) + # wrap up if not inplace: return seed @@ -340,6 +350,23 @@ def __contains__(self, element): cluster = self.g_vectors() return element in cluster + def Y(self, j): + r""" + The j-th element of the Y-pattern in the universal coefficient semifield + C.f. CA4 definition 3.10 + Uses CA4 Proposition 3.13 + """ + Y = prod(map(lambda y,c: y**c, self.parent()._U.gens(), self.c_vector(j))) + for i in range(self.parent()._n): + Y *= self.F_polynomial(i)**self.b_matrix()[i,j] + return self.parent()._U.fraction_field()(Y) + + def coefficient(self, j): + # TODO: the name of this function can be confusing: what this returns is the ration of the two coefficients in the exchange relations or, if you prefer, the j-th column of the bottom part of the extended exchange matrix at this seed + ev = self.Y(j).subs(self.parent()._y) + #ev = self.parent().ambient_field()(ev) + return self.parent().retract(tropical_evaluation(ev)) + ################################################################################ # Cluster algebras ################################################################################ @@ -465,6 +492,19 @@ def __init__(self, data, **kwargs): # Internal data for exploring the exchange graph self.reset_exploring_iterator() + # Internal data to store exchange relations + # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) + # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). + # Each of them contains two things + # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term + # 2) the coefficient part of the term + # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature + self._exchange_relations = dict() + if 'store_exchange_relations' in kwargs and kwargs['store_exchange_relations']: + self._store_exchange_relations = True + else: + self._store_exchange_relations = False + # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) @@ -685,6 +725,8 @@ def explore_to_depth(self, depth): break def cluster_fan(self, depth=infinity): + from sage.geometry.cone import Cone + from sage.geometry.fan import Fan seeds = self.seeds(depth=depth, mutating_F=False) cones = map(lambda s: Cone(s.g_vectors()), seeds) return Fan(cones) @@ -764,3 +806,17 @@ def greedy_coefficient(self,d_vector,p,q): # I asked about the code and it seems Greg has very little confidence in the code he has so far... def theta_basis_element(self, g_vector): pass + +################################################################################ +# helper functions +################################################################################ +def tropical_evaluation(f): + try: + # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions + f = f.parent().fraction_field()(f) + num_exponents = map(min, zip(*f.numerator().exponents())) + den_exponents = map(min, zip(*f.denominator().exponents())) + variables = f.parent().gens() + return prod(map(lambda x,p: x**p, variables,num_exponents))*prod(map(lambda x,p: x**(-p), variables,den_exponents)) + except: # This should only happen when f is a constant + return 1 From 40f0aace47b0aa9199e6a7ec0d8b5cc2e467483e Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 14 Mar 2016 15:54:10 -0400 Subject: [PATCH 066/191] Comment added. --- src/sage/algebras/cluster_algebra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b00bb3742ea..59cc5be4289 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -127,7 +127,8 @@ def __eq__(self, other): # It looks like the idea was to consider seeds up to simultaneous permutation of rows and columns, # the relation P between the c-matrices determines if there could exist such a permutation, # the remaining checks then ask about the remaining data - + # We should still check that parents are the same, right? Then equality of g-vectors guarantees everything is the same (up to permutation). + #P = self.c_matrix().inverse()*other.c_matrix() #return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() From ec3954545cfa539fcee93b7602e41aac8f3f4159 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 14 Mar 2016 16:27:13 -0400 Subject: [PATCH 067/191] Moved mutate_initial to the appropriate place and set out things to be coded. --- src/sage/algebras/cluster_algebra.py | 38 +++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 59cc5be4289..52f836f26f0 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -245,27 +245,6 @@ def mutate(self, k, inplace=True, mutating_F=True): # wrap up if not inplace: return seed - - # I am perplexed by this (yet unfinished) function. I was expecting - # mutate_initial do be a function of ClusterAlgebra: if we change the - # initial seed all the g-vectors, F_polynomials and paths need to mutate - # accordingly. Am I right? - def mutate_initial(self, k, inplace=True, mutating_F=True): - if inplace: - seed = self - else: - seed = copy(self) - - n = seed.parent().rk - - if k not in xrange(n): - raise ValueError('Cannot mutate in direction ' + str(k) + '.') - - #store mutation path - if seed._path != [] and seed._path[0] == k: - seed._path.pop(0) - else: - seed._path.insert(0,k) def _mutated_F(self, k, old_g_vector): alg = self.parent() @@ -717,6 +696,23 @@ def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 + def mutate_initial(self, k, inplace=True, mutating_F=True): + if inplace: + algebra = self + else: + algebra = copy(self) + + n = algebra.rk + + if k not in xrange(n): + raise ValueError('Cannot mutate in direction ' + str(k) + '.') + + #modify algebra._path_dict + #modify algebra._F_poly_dict + #modify algebra._B0 + #modify algebra._M0 + #modify algebra._yhat + def explore_to_depth(self, depth): while self._explored_depth <= depth: try: From 1bd3c6042cd28b50337265eac13190df19806ca2 Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 15 Mar 2016 23:20:44 -0400 Subject: [PATCH 068/191] Added g-vector recursion for initial seed mutations, not yet tested. --- src/sage/algebras/cluster_algebra.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 52f836f26f0..1e7c79bc94c 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -707,9 +707,30 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') - #modify algebra._path_dict + #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) + new_path_dict = dict() + for g_vect in algebra._path_dict: + path = algebra._path_dict[g_vect] + new_path_dict[tuple(-identity_matrix(n).column(k-1))] = [k] + if path == []: + new_path_dict[g_vect] = path + elif path != [k]: + if path[0] != k: + new_path = [k] + path + else: + new_path = path[1:] + new_g_vect = vector(g_vect) - 2*g_vect[k-1]*identity_matrix(n).column(k-1) + if g_vect[k-1] > 0: + sgn = -1 + else: + sgn = 1 + for i in xrange(n): + new_g_vect += max(sgn*algebra._B0[i,k],0)*g_vect[k-1]*identity_matrix(n).column(i-1) + #modify algebra._F_poly_dict #modify algebra._B0 + algebra._B0.mutate(k) + #modify algebra._M0 #modify algebra._yhat From 4ff7818e703f54b4224afdfd6c3b765ba513af22 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 24 Mar 2016 14:12:29 -0400 Subject: [PATCH 069/191] Added F-polynomial recursion and corrected g-vector recursion (still not tested, sorry sage is rebuilding). --- src/sage/algebras/cluster_algebra.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1e7c79bc94c..a5bc0784aa2 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -707,27 +707,32 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') - #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) + #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) and algebra._F_poly_dict using CA-IV (6.21) new_path_dict = dict() + new_F_dict = dict() + new_path_dict[tuple(identity_matrix(n).column(k))] = [] + new_F_dict[tuple(identity_matrix(n).column(k))] == self._U(1) for g_vect in algebra._path_dict: path = algebra._path_dict[g_vect] - new_path_dict[tuple(-identity_matrix(n).column(k-1))] = [k] - if path == []: - new_path_dict[g_vect] = path - elif path != [k]: + if path != [k]: if path[0] != k: new_path = [k] + path else: new_path = path[1:] - new_g_vect = vector(g_vect) - 2*g_vect[k-1]*identity_matrix(n).column(k-1) - if g_vect[k-1] > 0: + new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) + if g_vect[k] > 0: sgn = -1 else: sgn = 1 for i in xrange(n): - new_g_vect += max(sgn*algebra._B0[i,k],0)*g_vect[k-1]*identity_matrix(n).column(i-1) + new_g_vect += max(sgn*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) + new_path_dict[new_g_vect] = new_path + var('u') + h_subs_tuple = tuple([u^(-1) if i==k-1 else u^self._B0[k][i] for i in xrange(self.rk)]) + h = -algebra._F_poly_dict[g_vect](h_subs_tuple).degree(u) + F_subs_tuple = tuple([self._U.gen(k)^(-1) if j==k else self._U.gen(j)*self._U.gen(k)^max(self._B0[k][j],0)*(1+self._U.gen(k))^(-self._B0[k][j]) for j in xrange(self.rk)]) + new_F_dict[new_g_vect] = algebra._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)^h*(self._U.gen(k)+1)^g_vect[k] - #modify algebra._F_poly_dict #modify algebra._B0 algebra._B0.mutate(k) From 4891f1309884c5e6953d309471b0f80d41e6ff0c Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 28 Mar 2016 11:41:29 -0700 Subject: [PATCH 070/191] Fixed initial seed recursions. --- src/sage/algebras/cluster_algebra.py | 65 +++++++++++++++++++--------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index a5bc0784aa2..7d6806c8da0 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -705,40 +705,55 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): n = algebra.rk if k not in xrange(n): - raise ValueError('Cannot mutate in direction ' + str(k) + '.') + raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) and algebra._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() new_path_dict[tuple(identity_matrix(n).column(k))] = [] - new_F_dict[tuple(identity_matrix(n).column(k))] == self._U(1) + new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) + + poly_ring = PolynomialRing(ZZ,'u') + h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-algebra._B0[k][j],0) for j in xrange(n)]) + F_subs_tuple = tuple([algebra._U.gen(k)**(-1) if j==k else algebra._U.gen(j)*algebra._U.gen(k)**max(-algebra._B0[k][j],0)*(1+algebra._U.gen(k))**(algebra._B0[k][j]) for j in xrange(n)]) + for g_vect in algebra._path_dict: + #compute new path path = algebra._path_dict[g_vect] - if path != [k]: + if g_vect == tuple(identity_matrix(n).column(k)): + new_path = [k] + elif path != []: if path[0] != k: new_path = [k] + path else: new_path = path[1:] - new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) - if g_vect[k] > 0: - sgn = -1 - else: - sgn = 1 - for i in xrange(n): - new_g_vect += max(sgn*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) - new_path_dict[new_g_vect] = new_path - var('u') - h_subs_tuple = tuple([u^(-1) if i==k-1 else u^self._B0[k][i] for i in xrange(self.rk)]) - h = -algebra._F_poly_dict[g_vect](h_subs_tuple).degree(u) - F_subs_tuple = tuple([self._U.gen(k)^(-1) if j==k else self._U.gen(j)*self._U.gen(k)^max(self._B0[k][j],0)*(1+self._U.gen(k))^(-self._B0[k][j]) for j in xrange(self.rk)]) - new_F_dict[new_g_vect] = algebra._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)^h*(self._U.gen(k)+1)^g_vect[k] - - #modify algebra._B0 + else: + new_path = [] + + #compute new g-vector + for i in xrange(n): + new_g_vect += max(sign(g_vect[k])*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) + new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) + new_path_dict[tuple(new_g_vect)] = new_path + + #compute new F-polynomial + h = 0 + trop = tropical_evaluation(algebra._F_poly_dict[g_vect](h_subs_tuple)) + if trop != 1: + h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] + new_F_dict[tuple(new_g_vect)] = algebra._F_poly_dict[g_vect](F_subs_tuple)*algebra._U.gen(k)**h*(algebra._U.gen(k)+1)**g_vect[k] + + algebra._path_dict = new_path_dict + algebra._F_poly_dict = new_F_dict + algebra._B0.mutate(k) #modify algebra._M0 #modify algebra._yhat + if not inplace: + return algebra + def explore_to_depth(self, depth): while self._explored_depth <= depth: try: @@ -837,9 +852,17 @@ def tropical_evaluation(f): try: # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions f = f.parent().fraction_field()(f) - num_exponents = map(min, zip(*f.numerator().exponents())) - den_exponents = map(min, zip(*f.denominator().exponents())) + num_exponents = f.numerator().exponents() + if type(num_exponents[0]) != int: + num_exponent = map(min, zip(*num_exponents)) + else: + num_exponent = [min(num_exponents)] + den_exponents = f.denominator().exponents() + if type(den_exponents[0]) != int: + den_exponent = map(min, zip(*den_exponents)) + else: + den_exponent = [min(den_exponents)] variables = f.parent().gens() - return prod(map(lambda x,p: x**p, variables,num_exponents))*prod(map(lambda x,p: x**(-p), variables,den_exponents)) + return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) except: # This should only happen when f is a constant return 1 From eda93139260ffd7a66d5f23c7037086a6d0de16d Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 31 Mar 2016 10:25:00 -0700 Subject: [PATCH 071/191] Fixed new_g_vect error. --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7d6806c8da0..3340dccd211 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -731,9 +731,9 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): new_path = [] #compute new g-vector + new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) for i in xrange(n): new_g_vect += max(sign(g_vect[k])*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) - new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) new_path_dict[tuple(new_g_vect)] = new_path #compute new F-polynomial From 40b8937e9fc24eca81e7beaf859405a2b83955c2 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 31 Mar 2016 21:15:57 +0200 Subject: [PATCH 072/191] Implemented parallel initial seed mutation: it is slower --- src/sage/algebras/cluster_algebra.py | 67 +++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3340dccd211..df427b50385 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -17,7 +17,8 @@ from sage.misc.misc_c import prod from copy import copy from sage.functions.other import binomial - +from sage.modules.free_module_element import vector +from sage.functions.generalized import sign ################################################################################ # Elements of a cluster algebra @@ -696,6 +697,70 @@ def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 + def mutate_initial_parallel(self, k, inplace=True, mutating_F=True): + if inplace: + algebra = self + else: + algebra = copy(self) + + n = algebra.rk + + if k not in xrange(n): + raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) + + #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) and algebra._F_poly_dict using CA-IV (6.21) + poly_ring = PolynomialRing(ZZ,'u') + h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-algebra._B0[k][j],0) for j in xrange(n)]) + F_subs_tuple = tuple([algebra._U.gen(k)**(-1) if j==k else algebra._U.gen(j)*algebra._U.gen(k)**max(-algebra._B0[k][j],0)*(1+algebra._U.gen(k))**(algebra._B0[k][j]) for j in xrange(n)]) + + from sage.parallel.decorate import parallel + @parallel + def mutation_helper(g_vect): + #compute new path + path = algebra._path_dict[g_vect] + if g_vect == tuple(identity_matrix(n).column(k)): + new_path = [k] + elif path != []: + if path[0] != k: + new_path = [k] + path + else: + new_path = path[1:] + else: + new_path = [] + + #compute new g-vector + new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) + for i in xrange(n): + new_g_vect += max(sign(g_vect[k])*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) + + #compute new F-polynomial + h = 0 + trop = tropical_evaluation(algebra._F_poly_dict[g_vect](h_subs_tuple)) + if trop != 1: + h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] + new_F = algebra._F_poly_dict[g_vect](F_subs_tuple)*algebra._U.gen(k)**h*(algebra._U.gen(k)+1)**g_vect[k] + + return [tuple(new_g_vect), new_path, new_F] + + new_data = mutation_helper([(g_vect,) for g_vect in algebra._path_dict]) + new_path_dict = dict() + new_F_poly_dict = dict() + for _,(new_g_vect, new_path, new_F) in new_data: + new_path_dict[new_g_vect] = new_path + new_F_poly_dict[new_g_vect] = new_F + algebra._path_dict = new_path_dict + algebra._F_poly_dict = new_F_poly_dict + algebra._path_dict[tuple(identity_matrix(n).column(k))] = [] + algebra._F_poly_dict[tuple(identity_matrix(n).column(k))] = self._U(1) + + algebra._B0.mutate(k) + + #modify algebra._M0 + #modify algebra._yhat + + if not inplace: + return algebra + def mutate_initial(self, k, inplace=True, mutating_F=True): if inplace: algebra = self From df60d69e51d291abf5a22fee699693c4b1385dc6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 31 Mar 2016 21:16:33 +0200 Subject: [PATCH 073/191] Removed parallel initial seed mutation: it is slower --- src/sage/algebras/cluster_algebra.py | 64 ---------------------------- 1 file changed, 64 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index df427b50385..2b59a12a91b 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -697,70 +697,6 @@ def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 - def mutate_initial_parallel(self, k, inplace=True, mutating_F=True): - if inplace: - algebra = self - else: - algebra = copy(self) - - n = algebra.rk - - if k not in xrange(n): - raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) - - #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) and algebra._F_poly_dict using CA-IV (6.21) - poly_ring = PolynomialRing(ZZ,'u') - h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-algebra._B0[k][j],0) for j in xrange(n)]) - F_subs_tuple = tuple([algebra._U.gen(k)**(-1) if j==k else algebra._U.gen(j)*algebra._U.gen(k)**max(-algebra._B0[k][j],0)*(1+algebra._U.gen(k))**(algebra._B0[k][j]) for j in xrange(n)]) - - from sage.parallel.decorate import parallel - @parallel - def mutation_helper(g_vect): - #compute new path - path = algebra._path_dict[g_vect] - if g_vect == tuple(identity_matrix(n).column(k)): - new_path = [k] - elif path != []: - if path[0] != k: - new_path = [k] + path - else: - new_path = path[1:] - else: - new_path = [] - - #compute new g-vector - new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) - for i in xrange(n): - new_g_vect += max(sign(g_vect[k])*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) - - #compute new F-polynomial - h = 0 - trop = tropical_evaluation(algebra._F_poly_dict[g_vect](h_subs_tuple)) - if trop != 1: - h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] - new_F = algebra._F_poly_dict[g_vect](F_subs_tuple)*algebra._U.gen(k)**h*(algebra._U.gen(k)+1)**g_vect[k] - - return [tuple(new_g_vect), new_path, new_F] - - new_data = mutation_helper([(g_vect,) for g_vect in algebra._path_dict]) - new_path_dict = dict() - new_F_poly_dict = dict() - for _,(new_g_vect, new_path, new_F) in new_data: - new_path_dict[new_g_vect] = new_path - new_F_poly_dict[new_g_vect] = new_F - algebra._path_dict = new_path_dict - algebra._F_poly_dict = new_F_poly_dict - algebra._path_dict[tuple(identity_matrix(n).column(k))] = [] - algebra._F_poly_dict[tuple(identity_matrix(n).column(k))] = self._U(1) - - algebra._B0.mutate(k) - - #modify algebra._M0 - #modify algebra._yhat - - if not inplace: - return algebra - def mutate_initial(self, k, inplace=True, mutating_F=True): if inplace: algebra = self From 96df9748083e503aeef36ba38ae54c66fab3873c Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 31 Mar 2016 12:35:25 -0700 Subject: [PATCH 074/191] Added copy and equaltiy checking for cluster algebras. --- src/sage/algebras/cluster_algebra.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 2b59a12a91b..1469a50c177 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -495,6 +495,25 @@ def __init__(self, data, **kwargs): # TODO: understand if we need this #self._populate_coercion_lists_() + def __copy__(self): + other = type(self).__new__(type(self)) + other._U = self._U + other._path_dict = copy(self._path_dict) + other._F_poly_dict = copy(self._F_poly_dict) + other._ambient = self._ambient + other._ambient_field = self._ambient_field + other._y = copy(self._y) + other._yhat = copy(self._yhat) + other._is_principal = self._is_principal + other._B0 = copy(self._B0) + other._M0 = copy(self._M0) + other._n = self._n + other._m = self._m + # We probably need to put n=2 initializations here also + + def __eq__(self,other): + return self._B0 == other._B0 and self._yhat == other._yhat + # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) From 461a3bae6f8eff134e16679c307f7194f3829445 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 31 Mar 2016 14:03:56 -0700 Subject: [PATCH 075/191] Return the copied algebra. --- src/sage/algebras/cluster_algebra.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1469a50c177..c0be4997812 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -510,6 +510,7 @@ def __copy__(self): other._n = self._n other._m = self._m # We probably need to put n=2 initializations here also + return other def __eq__(self,other): return self._B0 == other._B0 and self._yhat == other._yhat From 785a4c9dd4d81be12c2e65d487027ed397e735fa Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 1 Apr 2016 00:02:43 +0200 Subject: [PATCH 076/191] Removed not used data _M0 and _m --- src/sage/algebras/cluster_algebra.py | 31 +++++++++++----------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c0be4997812..186cb719d76 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -87,16 +87,16 @@ def is_homogeneous(self): return len(self.homogeneous_components()) == 1 def homogeneous_components(self): - deg_matrix = block_matrix([[self.parent()._M0,-self.parent()._B0]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rk),-self.parent()._B0]]) components = dict() x = self.lift() monomials = x.monomials() for m in monomials: - gvect = tuple(deg_matrix*vector(m.exponents()[0])) - if gvect in components: - components[gvect] += self.parent().retract(x.monomial_coefficient(m)*m) + g_vect = tuple(deg_matrix*vector(m.exponents()[0])) + if g_vect in components: + components[g_vect] += self.parent().retract(x.monomial_coefficient(m)*m) else: - components[gvect] = self.parent().retract(x.monomial_coefficient(m)*m) + components[g_vect] = self.parent().retract(x.monomial_coefficient(m)*m) return components @@ -393,7 +393,7 @@ def __init__(self, data, **kwargs): M0 = I else: M0 = Q.b_matrix()[n:,:] - m = M0.nrows() + n + m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients @@ -424,12 +424,12 @@ def __init__(self, data, **kwargs): scalars = ZZ # Determine coefficients and setup self._base - if m>n: + if m>0: if 'coefficients_names' in kwargs: - if len(kwargs['coefficients_names']) == m-n: + if len(kwargs['coefficients_names']) == m: coefficients = kwargs['coefficients_names'] else: - raise ValueError("coefficients_names should be a list of %d valid variable names"%(m-n)) + raise ValueError("coefficients_names should be a list of %d valid variable names"%m) else: try: coefficients_prefix = kwargs['coefficients_prefix'] @@ -439,7 +439,7 @@ def __init__(self, data, **kwargs): offset = n else: offset = 0 - coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m-n+offset)] + coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m+offset)] # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: @@ -456,8 +456,8 @@ def __init__(self, data, **kwargs): # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next - #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) - self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m-n)])) for j in xrange(n)]) + #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # Have we principal coefficients? @@ -465,9 +465,7 @@ def __init__(self, data, **kwargs): # Store initial data self._B0 = copy(B0) - self._M0 = copy(M0) self._n = n - self._m = m self.reset_current_seed() # Internal data for exploring the exchange graph @@ -506,9 +504,7 @@ def __copy__(self): other._yhat = copy(self._yhat) other._is_principal = self._is_principal other._B0 = copy(self._B0) - other._M0 = copy(self._M0) other._n = self._n - other._m = self._m # We probably need to put n=2 initializations here also return other @@ -769,9 +765,6 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): algebra._B0.mutate(k) - #modify algebra._M0 - #modify algebra._yhat - if not inplace: return algebra From a9d776aa393345805ca9f174813151ea6c9d84c8 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 1 Apr 2016 21:32:44 +0200 Subject: [PATCH 077/191] Inproved equality testing --- src/sage/algebras/cluster_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 186cb719d76..490bf59e149 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -123,12 +123,11 @@ def __copy__(self): return other def __eq__(self, other): - return frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + return self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) # Why was I doing something so convoluted? # It looks like the idea was to consider seeds up to simultaneous permutation of rows and columns, # the relation P between the c-matrices determines if there could exist such a permutation, # the remaining checks then ask about the remaining data - # We should still check that parents are the same, right? Then equality of g-vectors guarantees everything is the same (up to permutation). #P = self.c_matrix().inverse()*other.c_matrix() #return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() @@ -407,6 +406,7 @@ def __init__(self, data, **kwargs): if 'cluster_variables_names' in kwargs: if len(kwargs['cluster_variables_names']) == n: variables = kwargs['cluster_variables_names'] + cluster_variables_prefix='dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public else: raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) else: @@ -508,8 +508,8 @@ def __copy__(self): # We probably need to put n=2 initializations here also return other - def __eq__(self,other): - return self._B0 == other._B0 and self._yhat == other._yhat + def __eq__(self, other): + return type(self) == type(other) and self._B0 == other._B0 and self._yhat == other._yhat # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): From 5f1fb5ff4a602f6a1a206aa7a21cb1be2a5ee7ed Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 6 Apr 2016 21:33:17 +0200 Subject: [PATCH 078/191] major changes at the code for both final and initial mutations, we now use a wrapper --- src/sage/algebras/cluster_algebra.py | 126 +++++++++++++++++---------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 490bf59e149..ddf69b20d32 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -19,6 +19,44 @@ from sage.functions.other import binomial from sage.modules.free_module_element import vector from sage.functions.generalized import sign +from functools import wraps + +def mutation_parse(mutate): + r""" + Parse input for mutation functions; it should provide: + - inplace + - mutate along sequence + - mutate at cluster variariable + - mutate at all sinks/sources + - urban renewals? (I do not care much abouth this) + - other? + """ + mutate.__doc__ += r""" + + - inplace: bool (default True) whether to mutate in place or to return a new object + - direction: can be + - an integer + - an iterable of integers + """ + @wraps(mutate) + def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): + if inplace: + to_mutate = self + else: + to_mutate = copy(self) + + try: + seq = iter(direction) + except TypeError: + seq = iter((direction,)) + + for k in seq: + mutate(to_mutate, k, *args, **kwargs) + + if not inplace: + return to_mutate + + return mutate_wrapper ################################################################################ # Elements of a cluster algebra @@ -180,57 +218,57 @@ def cluster_variable(self, j): def cluster_variables(self): return (self.parent().cluster_variable(g) for g in self.g_vectors()) - def mutate(self, k, inplace=True, mutating_F=True): - if inplace: - seed = self - else: - seed = copy(self) - - n = seed.parent().rk + @mutation_parse + def mutate(self, k, mutating_F=True): + r""" + mutate seed + bla bla ba + """ + n = self.parent().rk if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') # store mutation path - if seed._path != [] and seed._path[-1] == k: - seed._path.pop() + if self._path != [] and self._path[-1] == k: + self._path.pop() else: - seed._path.append(k) + self._path.append(k) # find sign of k-th c-vector # Will this be used enough that it should be a built-in function? - if any(x > 0 for x in seed._C.column(k)): + if any(x > 0 for x in self._C.column(k)): eps = +1 else: eps = -1 # store the g-vector to be mutated in case we are mutating F-polynomials also - old_g_vector = seed.g_vector(k) + old_g_vector = self.g_vector(k) # G-matrix J = identity_matrix(n) for j in xrange(n): - J[j,k] += max(0, -eps*seed._B[j,k]) + J[j,k] += max(0, -eps*self._B[j,k]) J[k,k] = -1 - seed._G = seed._G*J + self._G = self._G*J # g-vector path list - g_vector = seed.g_vector(k) - if g_vector not in seed.parent().g_vectors_so_far(): - seed.parent()._path_dict[g_vector] = copy(seed._path) + g_vector = self.g_vector(k) + if g_vector not in self.parent().g_vectors_so_far(): + self.parent()._path_dict[g_vector] = copy(self._path) # F-polynomials if mutating_F: - seed.parent()._F_poly_dict[g_vector] = seed._mutated_F(k, old_g_vector) + self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) # C-matrix J = identity_matrix(n) for j in xrange(n): - J[k,j] += max(0, eps*seed._B[k,j]) + J[k,j] += max(0, eps*self._B[k,j]) J[k,k] = -1 - seed._C = seed._C*J + self._C = self._C*J # B-matrix - seed._B.mutate(k) + self._B.mutate(k) # exchange relation if self.parent()._store_exchange_relations: @@ -242,10 +280,6 @@ def mutate(self, k, inplace=True, mutating_F=True): Mm = [ (g,-p) for (g,p) in variables if p < 0 ] self.parent()._exchange_relations[ex_pair] = ( (Mp,coefficient.numerator()), (Mm,coefficient.denominator()) ) - # wrap up - if not inplace: - return seed - def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) @@ -456,8 +490,8 @@ def __init__(self, data, **kwargs): # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next - #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) - self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # Have we principal coefficients? @@ -506,6 +540,7 @@ def __copy__(self): other._B0 = copy(self._B0) other._n = self._n # We probably need to put n=2 initializations here also + # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ return other def __eq__(self, other): @@ -713,30 +748,28 @@ def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 - def mutate_initial(self, k, inplace=True, mutating_F=True): - if inplace: - algebra = self - else: - algebra = copy(self) - - n = algebra.rk + @mutation_parse + def mutate_initial(self, k): + r""" + """ + n = self.rk if k not in xrange(n): raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) - #modify algebra._path_dict using Nakanishi-Zelevinsky (4.1) and algebra._F_poly_dict using CA-IV (6.21) + #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() new_path_dict[tuple(identity_matrix(n).column(k))] = [] new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) poly_ring = PolynomialRing(ZZ,'u') - h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-algebra._B0[k][j],0) for j in xrange(n)]) - F_subs_tuple = tuple([algebra._U.gen(k)**(-1) if j==k else algebra._U.gen(j)*algebra._U.gen(k)**max(-algebra._B0[k][j],0)*(1+algebra._U.gen(k))**(algebra._B0[k][j]) for j in xrange(n)]) + h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-self._B0[k][j],0) for j in xrange(n)]) + F_subs_tuple = tuple([self._U.gen(k)**(-1) if j==k else self._U.gen(j)*self._U.gen(k)**max(-self._B0[k][j],0)*(1+self._U.gen(k))**(self._B0[k][j]) for j in xrange(n)]) - for g_vect in algebra._path_dict: + for g_vect in self._path_dict: #compute new path - path = algebra._path_dict[g_vect] + path = self._path_dict[g_vect] if g_vect == tuple(identity_matrix(n).column(k)): new_path = [k] elif path != []: @@ -750,23 +783,20 @@ def mutate_initial(self, k, inplace=True, mutating_F=True): #compute new g-vector new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) for i in xrange(n): - new_g_vect += max(sign(g_vect[k])*algebra._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) + new_g_vect += max(sign(g_vect[k])*self._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) new_path_dict[tuple(new_g_vect)] = new_path #compute new F-polynomial h = 0 - trop = tropical_evaluation(algebra._F_poly_dict[g_vect](h_subs_tuple)) + trop = tropical_evaluation(self._F_poly_dict[g_vect](h_subs_tuple)) if trop != 1: h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] - new_F_dict[tuple(new_g_vect)] = algebra._F_poly_dict[g_vect](F_subs_tuple)*algebra._U.gen(k)**h*(algebra._U.gen(k)+1)**g_vect[k] - - algebra._path_dict = new_path_dict - algebra._F_poly_dict = new_F_dict + new_F_dict[tuple(new_g_vect)] = self._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)**h*(self._U.gen(k)+1)**g_vect[k] - algebra._B0.mutate(k) + self._path_dict = new_path_dict + self._F_poly_dict = new_F_dict - if not inplace: - return algebra + self._B0.mutate(k) def explore_to_depth(self, depth): while self._explored_depth <= depth: From bb46909bfc15f705b1c92384d958ef5d9a2449f6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 7 Apr 2016 00:45:29 +0200 Subject: [PATCH 079/191] Remove mutation_sequence --- src/sage/algebras/cluster_algebra.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ddf69b20d32..88d3f1fe2d2 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -324,28 +324,6 @@ def _mutated_F(self, k, old_g_vector): # DR: Now I get the same computation time for / and //, 49.7s while simultaneously rebuiling sage return (pos+neg)/alg.F_polynomial(old_g_vector) - # TODO: I propose to rename this function mutate and change mutate into - # _mutate. Then we change the top of this function to check that sequence - # is iterable (otherwise we make a list of only one leement) and keep the - # rest as is. This introduces a small overhead on each call of mutate from - # the user but exposes only one function (similar to what happens now in - # ClusterSeed. Moreover this would be better suited to implement things like - # mutate at sinks and urban renewal. For speed purposes we can always call - # _mutate internally where needed. - def mutation_sequence(self, sequence, inplace=True, mutating_F=True): - seq = iter(sequence) - - if inplace: - seed = self - else: - seed = self.mutate(next(seq), inplace=False, mutating_F=mutating_F) - - for k in seq: - seed.mutate(k, inplace=True, mutating_F=mutating_F) - - if not inplace: - return seed - def path_from_initial_seed(self): return copy(self._path) @@ -583,7 +561,7 @@ def current_seed(self, seed): def contains_seed(self, seed): computed_sd = self.initial_seed - computed_sd.mutation_sequence(seed._path, mutating_F=False) + computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed def reset_current_seed(self): From 484c82469577177c079f817f9736d496d9159d84 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 7 Apr 2016 00:46:02 +0200 Subject: [PATCH 080/191] Blank spaces --- src/sage/algebras/cluster_algebra.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 88d3f1fe2d2..e9db1aa2dda 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -27,12 +27,12 @@ def mutation_parse(mutate): - inplace - mutate along sequence - mutate at cluster variariable - - mutate at all sinks/sources + - mutate at all sinks/sources - urban renewals? (I do not care much abouth this) - other? """ mutate.__doc__ += r""" - + - inplace: bool (default True) whether to mutate in place or to return a new object - direction: can be - an integer @@ -44,7 +44,7 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): to_mutate = self else: to_mutate = copy(self) - + try: seq = iter(direction) except TypeError: @@ -52,10 +52,10 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): for k in seq: mutate(to_mutate, k, *args, **kwargs) - + if not inplace: - return to_mutate - + return to_mutate + return mutate_wrapper ################################################################################ @@ -485,7 +485,7 @@ def __init__(self, data, **kwargs): # Internal data to store exchange relations # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) - # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). + # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). # Each of them contains two things # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term # 2) the coefficient part of the term From 6abf32c5c56941d49c9131cf57cd04b1d1370c6f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 7 Apr 2016 00:58:47 +0200 Subject: [PATCH 081/191] Moved things around --- src/sage/algebras/cluster_algebra.py | 43 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e9db1aa2dda..bbc9d5b1277 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -21,6 +21,28 @@ from sage.functions.generalized import sign from functools import wraps +################################################################################ +# Helper functions +################################################################################ +def tropical_evaluation(f): + try: + # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions + f = f.parent().fraction_field()(f) + num_exponents = f.numerator().exponents() + if type(num_exponents[0]) != int: + num_exponent = map(min, zip(*num_exponents)) + else: + num_exponent = [min(num_exponents)] + den_exponents = f.denominator().exponents() + if type(den_exponents[0]) != int: + den_exponent = map(min, zip(*den_exponents)) + else: + den_exponent = [min(den_exponents)] + variables = f.parent().gens() + return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) + except: # This should only happen when f is a constant + return 1 + def mutation_parse(mutate): r""" Parse input for mutation functions; it should provide: @@ -867,24 +889,3 @@ def greedy_coefficient(self,d_vector,p,q): def theta_basis_element(self, g_vector): pass -################################################################################ -# helper functions -################################################################################ -def tropical_evaluation(f): - try: - # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions - f = f.parent().fraction_field()(f) - num_exponents = f.numerator().exponents() - if type(num_exponents[0]) != int: - num_exponent = map(min, zip(*num_exponents)) - else: - num_exponent = [min(num_exponents)] - den_exponents = f.denominator().exponents() - if type(den_exponents[0]) != int: - den_exponent = map(min, zip(*den_exponents)) - else: - den_exponent = [min(den_exponents)] - variables = f.parent().gens() - return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) - except: # This should only happen when f is a constant - return 1 From c711914188ec41314cde6c003ef118be62c29fde Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 7 Apr 2016 15:00:56 +0200 Subject: [PATCH 082/191] Begin writing documentation --- src/sage/algebras/cluster_algebra.py | 47 +++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index bbc9d5b1277..8f3a9044268 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,3 +1,30 @@ +r""" +Cluster algebras + +Implementation of cluster algebras as an algebra using mainly structural theorems from CA IV +We should write a nice paragraph here. + +AUTHORS: + +- Dylan Rupel (2015-06-15): initial version + +- Salvatore Stella (2015-06-15): initial version + +EXAMPLES:: + + +""" + +#***************************************************************************** +# Copyright (C) 2013 YOUR NAME Dylan Rupel and Salvaroe Stella +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + # TODO: check that we import all we need and possibly move some import used # rarely close to where needed from types import MethodType @@ -21,9 +48,9 @@ from sage.functions.generalized import sign from functools import wraps -################################################################################ +############################################################################## # Helper functions -################################################################################ +############################################################################## def tropical_evaluation(f): try: # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions @@ -80,9 +107,9 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): return mutate_wrapper -################################################################################ +############################################################################## # Elements of a cluster algebra -################################################################################ +############################################################################## class ClusterAlgebraElement(ElementWrapper): @@ -160,9 +187,9 @@ def homogeneous_components(self): return components -################################################################################ +############################################################################## # Seeds -################################################################################ +############################################################################## class ClusterAlgebraSeed(SageObject): @@ -381,9 +408,9 @@ def coefficient(self, j): #ev = self.parent().ambient_field()(ev) return self.parent().retract(tropical_evaluation(ev)) -################################################################################ +############################################################################## # Cluster algebras -################################################################################ +############################################################################## class ClusterAlgebra(Parent): r""" @@ -751,6 +778,10 @@ def reset_exploring_iterator(self, mutating_F=True): @mutation_parse def mutate_initial(self, k): r""" + Mutate ``self`` in direction `k` at the initial cluster. + + INPUT: + - ``k`` -- integer in between 0 and ``self.rk`` """ n = self.rk From 54501d2a7d90704bf86ca05b652cf71789edb045 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 9 Apr 2016 00:18:38 +0200 Subject: [PATCH 083/191] work in progress to remove @property --- src/sage/algebras/cluster_algebra.py | 38 ++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8f3a9044268..ba4d71e811e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -227,11 +227,35 @@ def _repr_(self): else: return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) - @property def depth(self): + r""" + Retun the length of a path from the initial seed of :meth:`parent` to ``self``. + + .. WARNING:: + This is the length of the path returned by :meth:`path_from_initial_seed` which needs not be the shortest possible. + + .. EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: S = A.current_seed() + sage: S.depth() + 0 + sage: S.mutate([0,1]) + sage: S.depth() + 2 + + """ return len(self._path) def parent(self): + r""" + Return the parent of ``self``. + + .. EXAMPLES:: + sage: A = ClusterAlgebra(['B',3]) + sage: A.current_seed().parent() == A + True + """ return self._parent def b_matrix(self): @@ -581,7 +605,7 @@ def _repr_(self): return "Cluster Algebra of rank %s"%self.rk def _an_element_(self): - return self.current_seed.cluster_variable(0) + return self.current_seed().cluster_variable(0) @property def rk(self): @@ -591,15 +615,13 @@ def rk(self): """ return self._n - @property def current_seed(self): r""" The current seed of ``self``. """ return self._seed - @current_seed.setter - def current_seed(self, seed): + def set_current_seed(self, seed): r""" Set ``self._seed`` to ``seed`` if it makes sense. """ @@ -682,7 +704,7 @@ def find_cluster_variable(self, g_vector, depth=infinity): while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) - self._explored_depth = seed.depth + self._explored_depth = seed.depth() except: raise ValueError("Could not find a cluster variable with g-vector %s up to mutation depth %s after performing %s mutations."%(str(g_vector),str(depth),str(mutation_counter))) @@ -732,7 +754,7 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): If ``mutating_F`` is set to false it does not compute F_polynomials """ if from_current_seed: - seed = self.current_seed + seed = self.current_seed() else: seed = self.initial_seed @@ -833,7 +855,7 @@ def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) - self._explored_depth = seed.depth + self._explored_depth = seed.depth() except: break From 7d220678ac071731e64a35ea0fefb0581a682aa5 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 6 Aug 2016 01:12:04 +0200 Subject: [PATCH 084/191] Stasted adding documentation --- src/sage/algebras/cluster_algebra.py | 183 ++++++++++++++++++++++----- 1 file changed, 148 insertions(+), 35 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ba4d71e811e..8e085591b9d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2,7 +2,8 @@ Cluster algebras Implementation of cluster algebras as an algebra using mainly structural theorems from CA IV -We should write a nice paragraph here. + +TODO: We should write a nice paragraph here. AUTHORS: @@ -12,11 +13,11 @@ EXAMPLES:: - + TODO: we need to write a complete example of usage here """ #***************************************************************************** -# Copyright (C) 2013 YOUR NAME Dylan Rupel and Salvaroe Stella +# Copyright (C) 2015 Dylan Rupel and Salvaroe Stella # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,45 +48,91 @@ from sage.modules.free_module_element import vector from sage.functions.generalized import sign from functools import wraps +from sage.categories.quotient_fields import QuotientFields +from sage.matrix.special import block_matrix ############################################################################## # Helper functions ############################################################################## -def tropical_evaluation(f): - try: - # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions - f = f.parent().fraction_field()(f) - num_exponents = f.numerator().exponents() - if type(num_exponents[0]) != int: - num_exponent = map(min, zip(*num_exponents)) - else: - num_exponent = [min(num_exponents)] - den_exponents = f.denominator().exponents() - if type(den_exponents[0]) != int: - den_exponent = map(min, zip(*den_exponents)) - else: - den_exponent = [min(den_exponents)] - variables = f.parent().gens() - return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) - except: # This should only happen when f is a constant +def tropical_evaluation(f): #READY + r""" + Return the tropical evaluation of ``f``. + + INPUT: + + - ``f`` -- a (Laurent) polynomial or a rational function without subtractions. + + OUTPUT: + + The Laurent monomial obtained by evaluating ``f`` in the tropical semifield of Laurent monomials with min and + + + EXAMPLES:: + + sage: from sage.algebras.cluster_algebra import tropical_evaluation + sage: tropical_evaluation(4+6/3) + 1 + sage: R. = PolynomialRing(ZZ) + sage: f = (x^3+2*x*y^3+x^2*y)/(x^2*z^3+y*z^2+z) + sage: f.parent() + Fraction Field of Multivariate Polynomial Ring in x, y, z over Integer Ring + sage: tropical_evaluation(f) + x/z + sage: var('t') + t + sage: g = t^3+5 + sage: g.parent() + Symbolic Ring + sage: tropical_evaluation(g) + Traceback (most recent call last): + ... + ValueError: Cannot compute numerator and denominator of t^3 + 5 + + """ + ambient = f.parent().fraction_field() + if ambient not in QuotientFields: + raise ValueError("Cannot compute numerator and denominator of %s"%str(f)) + + if ambient is QQ: return 1 -def mutation_parse(mutate): + # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions + # we could probably gain some marginal speed by doing things better + f = ambient(f) + num_exponents = f.numerator().exponents() + if type(num_exponents[0]) != int: + num_exponent = map(min, zip(*num_exponents)) + else: + num_exponent = [min(num_exponents)] + den_exponents = f.denominator().exponents() + if type(den_exponents[0]) != int: + den_exponent = map(min, zip(*den_exponents)) + else: + den_exponent = [min(den_exponents)] + variables = f.parent().gens() + return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) + +def mutation_parse(mutate): # READY r""" - Parse input for mutation functions; it should provide: + Preparse input for mutation functions. + + This wrapper provides: - inplace - mutate along sequence - - mutate at cluster variariable - mutate at all sinks/sources - - urban renewals? (I do not care much abouth this) + + Possible things to iplement later include: + - mutate at cluster variariable + - mutate at a g-vector + - urban renewals - other? """ mutate.__doc__ += r""" - inplace: bool (default True) whether to mutate in place or to return a new object - direction: can be - - an integer - - an iterable of integers + - an integer in ``range(self.rk())`` + - an iterable of such integers + - a string "sinks" or "sources" """ @wraps(mutate) def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): @@ -93,11 +140,18 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): to_mutate = self else: to_mutate = copy(self) - - try: - seq = iter(direction) - except TypeError: - seq = iter((direction,)) + + if direction == "sinks": + B = self.b_matrix() + seq = [ i for i in range(B.ncols()) if all( x<=0 for x in B.column(i)) ] + elif direction == "sources": + B = self.b_matrix() + seq = [ i for i in range(B.ncols()) if all( x>=0 for x in B.column(i)) ] + else: + try: + seq = iter(direction) + except TypeError: + seq = iter((direction,)) for k in seq: mutate(to_mutate, k, *args, **kwargs) @@ -163,17 +217,46 @@ def _repr_(self): # Methods not always defined #### -def g_vector(self): +def g_vector(self): #READY + r""" + Return the g-vector of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A.cluster_variable((1,0)).g_vector() == (1,0) + True + """ components = self.homogeneous_components() if len(components) == 1: return components.keys()[0] else: raise ValueError("This element is not homogeneous.") -def is_homogeneous(self): +def is_homogeneous(self): #READY + r""" + Return ``True`` if ``self`` is an homogeneous element of ``self.parent()``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) + sage: x.is_homogeneous() + False + """ return len(self.homogeneous_components()) == 1 -def homogeneous_components(self): +def homogeneous_components(self): #READY + r""" + Return a dictionary of the homogeneous components of ``self''. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) + sage: x.homogeneous_components() + {(0, 1): x1, (1, 0): x0} + """ deg_matrix = block_matrix([[identity_matrix(self.parent().rk),-self.parent()._B0]]) components = dict() x = self.lift() @@ -651,7 +734,7 @@ def initial_seed(self): return ClusterAlgebraSeed(self._B0, I, I, self) @property - def initial_b_matrix(self): + def initial_b_matrix(self): # change this to b_matrix for mutation wrapper n = self.rk return copy(self._B0) @@ -799,6 +882,36 @@ def reset_exploring_iterator(self, mutating_F=True): @mutation_parse def mutate_initial(self, k): + # WARNING: at the moment this function does not behave well with respect to coefficients: + # sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + # sage: A.initial_seed.b_matrix() + # [ 0 1] + # [-2 0] + # sage: A.initial_seed.c_matrix() + # [1 0] + # [0 1] + # sage: A.mutate_initial(0) + # sage: A.initial_seed.b_matrix() + # [ 0 -1] + # [ 2 0] + # sage: A.initial_seed.c_matrix() + # [1 0] + # [0 1] + # this creates several issues + # BTW: mutating the initial seed in place creates several issues with elements of the algebra: for example: + # sage: A = ClusterAlgebra(['B',4],principal_coefficients=True) + # sage: A.explore_to_depth(infinity) + # sage: x = A.cluster_variable((-1, 1, -2, 2)) + # sage: x.is_homogeneous() + # True + # sage: A.mutate_initial([0,3]) + # sage: x in A + # True + # sage: (-1, 1, -2, 2) in A.g_vectors_so_far() + # False + # sage: x.is_homogeneous() + # False + r""" Mutate ``self`` in direction `k` at the initial cluster. From 6d13bd7e2e618890e682e9d7d21de3c58fcdf6a6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 6 Aug 2016 01:29:58 +0200 Subject: [PATCH 085/191] Minor changes --- src/sage/algebras/cluster_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8e085591b9d..8bd9a9efcb3 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -121,8 +121,8 @@ def mutation_parse(mutate): # READY - mutate at all sinks/sources Possible things to iplement later include: - - mutate at cluster variariable - - mutate at a g-vector + - mutate at a cluster variariable + - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) - urban renewals - other? """ @@ -143,10 +143,10 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): if direction == "sinks": B = self.b_matrix() - seq = [ i for i in range(B.ncols()) if all( x<=0 for x in B.column(i)) ] + seq = [ i for i in range(B.ncols()) if all( x<=0 for x in B.column(i) ) ] elif direction == "sources": B = self.b_matrix() - seq = [ i for i in range(B.ncols()) if all( x>=0 for x in B.column(i)) ] + seq = [ i for i in range(B.ncols()) if all( x>=0 for x in B.column(i) ) ] else: try: seq = iter(direction) From 28638581b202482a7d8d8efa806455d4e6566a4e Mon Sep 17 00:00:00 2001 From: drupel Date: Fri, 5 Aug 2016 21:46:58 -0400 Subject: [PATCH 086/191] Fixed spelling of Salvatore :) --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8bd9a9efcb3..4206fb5b8e4 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -17,7 +17,7 @@ """ #***************************************************************************** -# Copyright (C) 2015 Dylan Rupel and Salvaroe Stella +# Copyright (C) 2015 Dylan Rupel and Salvatore Stella # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 2891ac1a2a0bcd8295f7207c5da53bd510457f0f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 6 Aug 2016 16:06:04 +0200 Subject: [PATCH 087/191] Fieed mess with coercion, I now understand how it works! --- src/sage/algebras/cluster_algebra.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 4206fb5b8e4..0c4a8425ab8 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -658,8 +658,11 @@ def __init__(self, data, **kwargs): self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) - # TODO: understand if we need this - #self._populate_coercion_lists_() + # Register embedding into self.ambient() + from sage.categories.morphism import SetMorphism + from sage.categories.homset import Hom + embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) + self._populate_coercion_lists_(embedding=embedding) def __copy__(self): other = type(self).__new__(type(self)) @@ -678,10 +681,13 @@ def __copy__(self): return other def __eq__(self, other): - return type(self) == type(other) and self._B0 == other._B0 and self._yhat == other._yhat + return type(self) == type(other) and self._B0 == other._B0 and self._yhat == other._yhat and self.base() == other.base() - # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): + # if other is a cluster algebra allow inherit coercions from ambients + if isinstance(other, ClusterAlgebra): + return self.ambient().has_coerce_map_from(other.ambient()) + # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) def _repr_(self): @@ -818,7 +824,7 @@ def lift(self, x): r""" Return x as an element of self._ambient """ - return x.value + return self.ambient()(x.value) def retract(self, x): return self(x) From 97df73d627cf76e9eb484e3391f8440ac29ee4b7 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 12:29:39 +0200 Subject: [PATCH 088/191] Fixed mutate_wrapper, removed inplace for initial seed mutations and keep current seed on the same node --- src/sage/algebras/cluster_algebra.py | 54 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 0c4a8425ab8..7039a0158e1 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -116,30 +116,38 @@ def mutation_parse(mutate): # READY Preparse input for mutation functions. This wrapper provides: - - inplace + - inplace (only for seeds) - mutate along sequence - mutate at all sinks/sources - Possible things to iplement later include: + Possible things to implement later include: - mutate at a cluster variariable - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) - urban renewals - other? """ - mutate.__doc__ += r""" - - - inplace: bool (default True) whether to mutate in place or to return a new object - - direction: can be - - an integer in ``range(self.rk())`` - - an iterable of such integers - - a string "sinks" or "sources" - """ + doc = mutate.__doc__.split("INPUT:") + doc[0] += "INPUT:" + if mutate.__name__ == "mutate": + doc[0] += r""" + + - ``inplace`` -- bool (default True) whether to mutate in place or to return a new object + """ + doc[0] += r""" + + - ``direction`` -- in which direction(s) to mutate. It can be + - an integer in ``range(self.rk())`` to mutate in one direction only; + - an iterable of such integers to mutate along a sequence; + - a string "sinks" or "sources" to mutate at all sinks or sources simultaneously. + """ + mutate.__doc__ = doc[0] + doc[1] + @wraps(mutate) def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): - if inplace: - to_mutate = self - else: + if not inplace or mutate.__name__ == "mutate_initial": to_mutate = copy(self) + else: + to_mutate = self if direction == "sinks": B = self.b_matrix() @@ -156,7 +164,7 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): for k in seq: mutate(to_mutate, k, *args, **kwargs) - if not inplace: + if not inplace or mutate.__name__ == "mutate_initial": return to_mutate return mutate_wrapper @@ -378,6 +386,9 @@ def cluster_variables(self): def mutate(self, k, mutating_F=True): r""" mutate seed + + INPUT: + bla bla ba """ n = self.parent().rk @@ -676,6 +687,8 @@ def __copy__(self): other._is_principal = self._is_principal other._B0 = copy(self._B0) other._n = self._n + other._seed = copy(self._seed) + other._store_exchange_relations = self._store_exchange_relations # We probably need to put n=2 initializations here also # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ return other @@ -739,8 +752,10 @@ def initial_seed(self): I = identity_matrix(n) return ClusterAlgebraSeed(self._B0, I, I, self) - @property - def initial_b_matrix(self): # change this to b_matrix for mutation wrapper + def b_matrix(self): + r""" + Return the initial exchange matrix of ``self``. + """ n = self.rk return copy(self._B0) @@ -928,7 +943,9 @@ def mutate_initial(self, k): if k not in xrange(n): raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) - + + #store current seed location + path_to_current = self.current_seed().path_from_initial_seed() #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() @@ -969,6 +986,9 @@ def mutate_initial(self, k): self._F_poly_dict = new_F_dict self._B0.mutate(k) + + # keep the current seed were it was on the exchange graph + self._seed = self.initial_seed.mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) def explore_to_depth(self, depth): while self._explored_depth <= depth: From e21502223445ed663adab4f6137a7e0efc504e9a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 14:31:43 +0200 Subject: [PATCH 089/191] few more improvements to mutate_wrapper --- src/sage/algebras/cluster_algebra.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7039a0158e1..866026f1291 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -143,11 +143,12 @@ def mutation_parse(mutate): # READY mutate.__doc__ = doc[0] + doc[1] @wraps(mutate) - def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): - if not inplace or mutate.__name__ == "mutate_initial": - to_mutate = copy(self) - else: + def mutate_wrapper(self, direction, *args, **kwargs): + inplace = kwargs.pop('inplace', True) and mutate.__name__ != "mutate_initial" + if inplace: to_mutate = self + else: + to_mutate = copy(self) if direction == "sinks": B = self.b_matrix() @@ -164,7 +165,7 @@ def mutate_wrapper(self, direction, inplace=True, *args, **kwargs): for k in seq: mutate(to_mutate, k, *args, **kwargs) - if not inplace or mutate.__name__ == "mutate_initial": + if not inplace: return to_mutate return mutate_wrapper From 032f4d249ed0a309da7e796e0084532ebaddf468 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 15:26:17 +0200 Subject: [PATCH 090/191] Started cleanup of init --- src/sage/algebras/cluster_algebra.py | 60 +++++++++------------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 866026f1291..50cbb33a7cc 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -560,22 +560,21 @@ def __init__(self, data, **kwargs): r""" See :class:`ClusterAlgebra` for full documentation. """ - # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. - # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented - # Temporary variables Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n,:] I = identity_matrix(n) - if 'principal_coefficients' in kwargs and kwargs['principal_coefficients']: + if kwargs.get('principal_coefficients', False): M0 = I else: M0 = Q.b_matrix()[n:,:] m = M0.nrows() # Ambient space for F-polynomials - # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients + # NOTE: for speed purposes we need to have QQ here instead of the more + # natural ZZ. The reason is that _mutated_F is faster if we do not cast + # the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) # Storage for computed data @@ -583,61 +582,38 @@ def __init__(self, data, **kwargs): self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) # Determine the names of the initial cluster variables - if 'cluster_variables_names' in kwargs: - if len(kwargs['cluster_variables_names']) == n: - variables = kwargs['cluster_variables_names'] - cluster_variables_prefix='dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public - else: - raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) - else: - try: - cluster_variables_prefix = kwargs['cluster_variables_prefix'] - except: - cluster_variables_prefix = 'x' - variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] - # why not just put str(i) instead of '%s'%i? + variables_prefix = kwargs.get('cluster_variables_prefix','x') + variables = kwargs.get('cluster_variables_names', [variables_prefix+str(i) for i in xrange(n)]) + if len(variables) != n: + raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) # Determine scalars - try: - scalars = kwargs['scalars'] - except: - scalars = ZZ + scalars = kwargs.get('scalars', ZZ) # Determine coefficients and setup self._base if m>0: - if 'coefficients_names' in kwargs: - if len(kwargs['coefficients_names']) == m: - coefficients = kwargs['coefficients_names'] - else: - raise ValueError("coefficients_names should be a list of %d valid variable names"%m) + coefficients_prefix = kwargs.get('coefficients_prefix', 'y') + if coefficients_prefix == variables_prefix: + offset = n else: - try: - coefficients_prefix = kwargs['coefficients_prefix'] - except: - coefficients_prefix = 'y' - if coefficients_prefix == cluster_variables_prefix: - offset = n - else: - offset = 0 - coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m+offset)] - # TODO: (***) base should eventually become the group algebra of a tropical semifield + offset = 0 + coefficients = kwargs.get('coefficients_names', [coefficients_prefix+str(i) for i in xrange(offset,m+offset)]) + if len(coefficients) != m: + raise ValueError("coefficients_names should be a list of %d valid variable names"%m) base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars - # TODO: next line should be removed when (***) is implemented coefficients = [] # setup Parent and ambient - # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) self._ambient_field = self._ambient.fraction_field() - # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next - self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) - #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # Have we principal coefficients? From 978d44fc00fb0249afdf34d84c7c38fc5791d44f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 15:42:10 +0200 Subject: [PATCH 091/191] Store extended b-matrix to compute subalgebras --- src/sage/algebras/cluster_algebra.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 50cbb33a7cc..7156c126167 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -266,7 +266,7 @@ def homogeneous_components(self): #READY sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rk),-self.parent()._B0]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rk),-self.parent().b_matrix()]]) components = dict() x = self.lift() monomials = x.monomials() @@ -611,16 +611,17 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions - # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next + # BUG WORKAROUND: if your sage installation does not have trac:`19538` + # merged uncomment the following line and comment the next #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) - # Have we principal coefficients? + # Have we got principal coefficients? self._is_principal = (M0 == I) # Store initial data - self._B0 = copy(B0) + self._B0 = block_matrix([[B0],[M0]]) self._n = n self.reset_current_seed() @@ -727,14 +728,14 @@ def initial_seed(self): """ n = self.rk I = identity_matrix(n) - return ClusterAlgebraSeed(self._B0, I, I, self) + return ClusterAlgebraSeed(self.b_matrix(), I, I, self) def b_matrix(self): r""" Return the initial exchange matrix of ``self``. """ n = self.rk - return copy(self._B0) + return copy(self._B0[:n,:]) def g_vectors_so_far(self): r""" From 73c5862d18b07e2e27d50af0a8d4978662c9ce9d Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 15:59:00 +0200 Subject: [PATCH 092/191] Zapped all the code to compute exchange relations: it was not ready for public use. The code can still be found in the branch cluster_algebras_exchange_relations --- src/sage/algebras/cluster_algebra.py | 41 ---------------------------- 1 file changed, 41 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7156c126167..d76df84fd06 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -438,16 +438,6 @@ def mutate(self, k, mutating_F=True): # B-matrix self._B.mutate(k) - # exchange relation - if self.parent()._store_exchange_relations: - ex_pair = frozenset([g_vector,old_g_vector]) - if ex_pair not in self.parent()._exchange_relations: - coefficient = self.coefficient(k).lift() - variables = zip(self.g_vectors(), self.b_matrix().column(k)) - Mp = [ (g,p) for (g,p) in variables if p > 0 ] - Mm = [ (g,-p) for (g,p) in variables if p < 0 ] - self.parent()._exchange_relations[ex_pair] = ( (Mp,coefficient.numerator()), (Mm,coefficient.denominator()) ) - def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) @@ -510,23 +500,6 @@ def __contains__(self, element): cluster = self.g_vectors() return element in cluster - def Y(self, j): - r""" - The j-th element of the Y-pattern in the universal coefficient semifield - C.f. CA4 definition 3.10 - Uses CA4 Proposition 3.13 - """ - Y = prod(map(lambda y,c: y**c, self.parent()._U.gens(), self.c_vector(j))) - for i in range(self.parent()._n): - Y *= self.F_polynomial(i)**self.b_matrix()[i,j] - return self.parent()._U.fraction_field()(Y) - - def coefficient(self, j): - # TODO: the name of this function can be confusing: what this returns is the ration of the two coefficients in the exchange relations or, if you prefer, the j-th column of the bottom part of the extended exchange matrix at this seed - ev = self.Y(j).subs(self.parent()._y) - #ev = self.parent().ambient_field()(ev) - return self.parent().retract(tropical_evaluation(ev)) - ############################################################################## # Cluster algebras ############################################################################## @@ -628,19 +601,6 @@ def __init__(self, data, **kwargs): # Internal data for exploring the exchange graph self.reset_exploring_iterator() - # Internal data to store exchange relations - # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) - # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). - # Each of them contains two things - # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term - # 2) the coefficient part of the term - # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature - self._exchange_relations = dict() - if 'store_exchange_relations' in kwargs and kwargs['store_exchange_relations']: - self._store_exchange_relations = True - else: - self._store_exchange_relations = False - # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) @@ -666,7 +626,6 @@ def __copy__(self): other._B0 = copy(self._B0) other._n = self._n other._seed = copy(self._seed) - other._store_exchange_relations = self._store_exchange_relations # We probably need to put n=2 initializations here also # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ return other From 870d52f6d9c23d12053682d0126471505d389ed5 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 17:50:38 +0200 Subject: [PATCH 093/191] few more changes to init --- src/sage/algebras/cluster_algebra.py | 53 +++++++++++++--------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d76df84fd06..1edd4fac2c3 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -28,28 +28,30 @@ # TODO: check that we import all we need and possibly move some import used # rarely close to where needed -from types import MethodType -from sage.structure.element_wrapper import ElementWrapper -from sage.structure.sage_object import SageObject -from sage.structure.parent import Parent -from sage.rings.integer_ring import ZZ -from sage.rings.integer import Integer -from sage.rings.rational_field import QQ -from sage.misc.cachefunc import cached_method -from sage.rings.infinity import infinity -from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver -from sage.matrix.constructor import identity_matrix -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing -from sage.categories.rings import Rings -from sage.misc.misc_c import prod from copy import copy -from sage.functions.other import binomial -from sage.modules.free_module_element import vector -from sage.functions.generalized import sign from functools import wraps +from sage.categories.homset import Hom +from sage.categories.morphism import SetMorphism from sage.categories.quotient_fields import QuotientFields +from sage.categories.rings import Rings +from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver +from sage.functions.generalized import sign +from sage.functions.other import binomial +from sage.matrix.constructor import identity_matrix from sage.matrix.special import block_matrix +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.modules.free_module_element import vector +from sage.rings.infinity import infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.structure.element_wrapper import ElementWrapper +from sage.structure.parent import Parent +from sage.structure.sage_object import SageObject +from types import MethodType ############################################################################## # Helper functions @@ -602,14 +604,11 @@ def __init__(self, data, **kwargs): self.reset_exploring_iterator() # Add methods that are defined only for special cases - if n == 2: + if n == 2 and m == 0: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) - self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # Register embedding into self.ambient() - from sage.categories.morphism import SetMorphism - from sage.categories.homset import Hom embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) @@ -952,6 +951,9 @@ def upper_bound(self): def lower_bound(self): pass + def theta_basis_element(self, g_vector): + pass + #### # Methods only defined for special cases #### @@ -1010,11 +1012,4 @@ def greedy_coefficient(self,d_vector,p,q): return max(sum1,sum2) -# At the moment I know only how to compute theta basis in rank 2 -# maybe we should let ClusterAlgebra have this method for any rank and have a -# NotImplementedError to encourage someone (read: Greg) to code this -# I think Greg already has some code to do this -# I asked about the code and it seems Greg has very little confidence in the code he has so far... -def theta_basis_element(self, g_vector): - pass From ae2a93281763fe55016876fd877407ee9a47e73b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 19:09:10 +0200 Subject: [PATCH 094/191] Shorten up definition of _yhat. This should be improved if possible --- src/sage/algebras/cluster_algebra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1edd4fac2c3..e2c55edf786 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -538,12 +538,12 @@ def __init__(self, data, **kwargs): # Temporary variables Q = ClusterQuiver(data) n = Q.n() - B0 = Q.b_matrix()[:n,:] I = identity_matrix(n) if kwargs.get('principal_coefficients', False): M0 = I else: M0 = Q.b_matrix()[n:,:] + B0 = block_matrix([[Q.b_matrix()[:n,:]],[M0]]) m = M0.nrows() # Ambient space for F-polynomials @@ -590,13 +590,13 @@ def __init__(self, data, **kwargs): # merged uncomment the following line and comment the next #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) - self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) + self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n+m)])) for j in xrange(n)]) # Have we got principal coefficients? self._is_principal = (M0 == I) # Store initial data - self._B0 = block_matrix([[B0],[M0]]) + self._B0 = copy(B0) self._n = n self.reset_current_seed() From 2385f637499373c183379a3a9a341119bd9b5dd3 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 7 Aug 2016 20:05:39 +0200 Subject: [PATCH 095/191] Use the Force Luke! --- src/sage/algebras/cluster_algebra.py | 63 +--------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e2c55edf786..b29104aa8b5 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -56,63 +56,6 @@ ############################################################################## # Helper functions ############################################################################## -def tropical_evaluation(f): #READY - r""" - Return the tropical evaluation of ``f``. - - INPUT: - - - ``f`` -- a (Laurent) polynomial or a rational function without subtractions. - - OUTPUT: - - The Laurent monomial obtained by evaluating ``f`` in the tropical semifield of Laurent monomials with min and + - - EXAMPLES:: - - sage: from sage.algebras.cluster_algebra import tropical_evaluation - sage: tropical_evaluation(4+6/3) - 1 - sage: R. = PolynomialRing(ZZ) - sage: f = (x^3+2*x*y^3+x^2*y)/(x^2*z^3+y*z^2+z) - sage: f.parent() - Fraction Field of Multivariate Polynomial Ring in x, y, z over Integer Ring - sage: tropical_evaluation(f) - x/z - sage: var('t') - t - sage: g = t^3+5 - sage: g.parent() - Symbolic Ring - sage: tropical_evaluation(g) - Traceback (most recent call last): - ... - ValueError: Cannot compute numerator and denominator of t^3 + 5 - - """ - ambient = f.parent().fraction_field() - if ambient not in QuotientFields: - raise ValueError("Cannot compute numerator and denominator of %s"%str(f)) - - if ambient is QQ: - return 1 - - # This is an hack to use the same code on polynomials, laurent polynomials and rational expressions - # we could probably gain some marginal speed by doing things better - f = ambient(f) - num_exponents = f.numerator().exponents() - if type(num_exponents[0]) != int: - num_exponent = map(min, zip(*num_exponents)) - else: - num_exponent = [min(num_exponents)] - den_exponents = f.denominator().exponents() - if type(den_exponents[0]) != int: - den_exponent = map(min, zip(*den_exponents)) - else: - den_exponent = [min(den_exponents)] - variables = f.parent().gens() - return prod(map(lambda x,p: x**p, variables,num_exponent))*prod(map(lambda x,p: x**(-p), variables,den_exponent)) - def mutation_parse(mutate): # READY r""" Preparse input for mutation functions. @@ -889,7 +832,6 @@ def mutate_initial(self, k): new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) poly_ring = PolynomialRing(ZZ,'u') - h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-self._B0[k][j],0) for j in xrange(n)]) F_subs_tuple = tuple([self._U.gen(k)**(-1) if j==k else self._U.gen(j)*self._U.gen(k)**max(-self._B0[k][j],0)*(1+self._U.gen(k))**(self._B0[k][j]) for j in xrange(n)]) for g_vect in self._path_dict: @@ -912,10 +854,7 @@ def mutate_initial(self, k): new_path_dict[tuple(new_g_vect)] = new_path #compute new F-polynomial - h = 0 - trop = tropical_evaluation(self._F_poly_dict[g_vect](h_subs_tuple)) - if trop != 1: - h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] + h = -min(0,g_vect[k]) new_F_dict[tuple(new_g_vect)] = self._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)**h*(self._U.gen(k)+1)**g_vect[k] self._path_dict = new_path_dict From a8fb5c78a103683e512f2efa4563cc55db42601f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 01:41:04 +0200 Subject: [PATCH 096/191] polished d-vector and _repr_ --- src/sage/algebras/cluster_algebra.py | 50 +++++++++++++++------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b29104aa8b5..2bffa897e71 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -138,35 +138,37 @@ def _add_(self, other): def _div_(self, other): return self.parent().retract(self.lift()/other.lift()) - # HACK: LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - def lift_to_field(self): - return self.parent().lift_to_field(self) - - # this function is quite disgusting but at least it works for any element of - # the algebra, can we do better? - # For cluster variables yes we can do better: use CA4 Prop 7.16 - # It looks to me that Prop 7.16 in CA IV only works if you know the - # F-polynomial; in particular one ask d_vector() of a cluster variable (or - # maybe of a cluster monomial if we know the cluster decomposition). The - # current implementation is uglier but more general. def d_vector(self): - n = self.parent().rk - one = self.parent().ambient_field()(1) - factors = self.lift_to_field().factor() - initial = [] - non_initial = [] - [(initial if x[1] > 0 and len(x[0].monomials()) == 1 else non_initial).append(x[0]**x[1]) for x in factors] - initial = prod(initial+[one]).numerator() - non_initial = prod(non_initial+[one]).denominator() - v1 = vector(non_initial.exponents()[0][:n]) - v2 = vector(initial.exponents()[0][:n]) - return tuple(v1-v2) + r""" + Return the d-vector of ``self`` as a tuple of integers. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4], principal_coefficients=True) + sage: A.current_seed().mutate([0, 2, 1]) + sage: x = A.cluster_variable((-1, 2, -2, 2)) + sage: x = A.cluster_variable((-1, 2, -2, 2)) * A.cluster_variable((0,0,0,1))**2 + sage: x.d_vector() + (1, 1, 2, -2) + """ + monomials = self.lift()._dict().keys() + minimal = map(min, zip(*monomials)) + return tuple(-vector(minimal))[:self.parent().rk] def _repr_(self): - # use this to factor d-vector in the representation - return repr(self.lift_to_field()) + r""" + Return a string representation of ``self``. + EXAMPLES:: + sage: A = ClusterAlgebra(['F',4], principal_coefficients=True) + sage: A.current_seed().mutate([0, 2, 1]) + sage: A.cluster_variable((-1, 2, -2, 2)) + (x0*x2^2*y0*y1*y2^2 + x1^3*x3^2 + x1^2*x3^2*y0 + 2*x1^2*x3*y2 + 2*x1*x3*y0*y2 + x1*y2^2 + y0*y2^2)/(x0*x1*x2^2) + """ + numer, denom = self.lift()._fraction_pair() + return repr(numer/denom) + #### # Methods not always defined #### From 916040f9dec12d25f2a7f6f7dace37aea521fb8b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 01:53:41 +0200 Subject: [PATCH 097/191] Zapped ambient_field! The code is really getting better --- src/sage/algebras/cluster_algebra.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 2bffa897e71..c06b808bb6e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -138,7 +138,7 @@ def _add_(self, other): def _div_(self, other): return self.parent().retract(self.lift()/other.lift()) - def d_vector(self): + def d_vector(self): # READY r""" Return the d-vector of ``self`` as a tuple of integers. @@ -155,7 +155,7 @@ def d_vector(self): minimal = map(min, zip(*monomials)) return tuple(-vector(minimal))[:self.parent().rk] - def _repr_(self): + def _repr_(self): # READY r""" Return a string representation of ``self``. @@ -527,7 +527,6 @@ def __init__(self, data, **kwargs): # setup Parent and ambient self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) - self._ambient_field = self._ambient.fraction_field() Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions @@ -563,7 +562,6 @@ def __copy__(self): other._path_dict = copy(self._path_dict) other._F_poly_dict = copy(self._F_poly_dict) other._ambient = self._ambient - other._ambient_field = self._ambient_field other._y = copy(self._y) other._yhat = copy(self._yhat) other._is_principal = self._is_principal @@ -670,7 +668,7 @@ def cluster_variable(self, g_vector): F_std = self.F_polynomial(g_vector).subs(self._yhat) g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() + F_trop = self.F_polynomial(g_vector).subs(self._y)._fraction_pair()[1] return self.retract(g_mon*F_std*F_trop) def find_cluster_variable(self, g_vector, depth=infinity): @@ -710,12 +708,6 @@ def find_cluster_variable(self, g_vector, depth=infinity): def ambient(self): return self._ambient - def ambient_field(self): - return self._ambient_field - - def lift_to_field(self, x): - return self.ambient_field()(1)*x.value - def lift(self, x): r""" Return x as an element of self._ambient From c59e3b1abc37e481dddc06b9801065222207e20e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 11:41:06 +0200 Subject: [PATCH 098/191] ClusterAlgebraElement is ready! --- src/sage/algebras/cluster_algebra.py | 67 +++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c06b808bb6e..f1b91f297d9 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -119,9 +119,25 @@ def mutate_wrapper(self, direction, *args, **kwargs): # Elements of a cluster algebra ############################################################################## -class ClusterAlgebraElement(ElementWrapper): +class ClusterAlgebraElement(ElementWrapper): # READY - def __init__(self, parent, value): + def __init__(self, parent, value): # READY + r""" + An element of a cluster algebra. + + INPUT: + + - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the element belongs; + + - ``value`` -- the value of the element. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: from sage.algebras.cluster_algebra import ClusterAlgebraElement + sage: ClusterAlgebraElement(A,1) + 1 + """ ElementWrapper.__init__(self, parent=parent, value=value) # setup methods defined only in special cases @@ -130,13 +146,40 @@ def __init__(self, parent, value): self.is_homogeneous = MethodType(is_homogeneous, self, self.__class__) self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) - # This function needs to be removed once AdditiveMagmas.Subobjects implements _add_ - def _add_(self, other): + # AdditiveMagmas.Subobjects currently does not implements _add_ + def _add_(self, other): # READY + r""" + Return the sum of ``self`` and ``other``. + + INPUT: + + - ``other`` - an element of ``self.parent()`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.an_element() + A.an_element() + 2*x0 + """ return self.parent().retract(self.lift() + other.lift()) - # I am not sure we want to have this function: its output is most of the times not in the algebra but it is convenient to have - def _div_(self, other): - return self.parent().retract(self.lift()/other.lift()) + def _div_(self, other): # READY + r""" + Return the quotient of ``self`` and ``other``. + + .. WARNING:: + + This method returns an element of ``self.parent().ambient()`` + rather than an element of ``self.parent()`` because, a priori, + we cannot guarantee membership. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.an_element() / A.an_element() + 1 + """ + return self.lift()/other.lift() def d_vector(self): # READY r""" @@ -146,7 +189,6 @@ def d_vector(self): # READY sage: A = ClusterAlgebra(['F',4], principal_coefficients=True) sage: A.current_seed().mutate([0, 2, 1]) - sage: x = A.cluster_variable((-1, 2, -2, 2)) sage: x = A.cluster_variable((-1, 2, -2, 2)) * A.cluster_variable((0,0,0,1))**2 sage: x.d_vector() (1, 1, 2, -2) @@ -157,7 +199,7 @@ def d_vector(self): # READY def _repr_(self): # READY r""" - Return a string representation of ``self``. + Return the string representation of ``self``. EXAMPLES:: @@ -273,7 +315,7 @@ def depth(self): .. WARNING:: This is the length of the path returned by :meth:`path_from_initial_seed` which needs not be the shortest possible. - .. EXAMPLES:: + EXAMPLES:: sage: A = ClusterAlgebra(['A',2]) sage: S = A.current_seed() @@ -290,7 +332,7 @@ def parent(self): r""" Return the parent of ``self``. - .. EXAMPLES:: + EXAMPLES:: sage: A = ClusterAlgebra(['B',3]) sage: A.current_seed().parent() == A True @@ -668,7 +710,7 @@ def cluster_variable(self, g_vector): F_std = self.F_polynomial(g_vector).subs(self._yhat) g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - F_trop = self.F_polynomial(g_vector).subs(self._y)._fraction_pair()[1] + F_trop = self.ambient()(self.F_polynomial(g_vector).subs(self._y))._fraction_pair()[1] return self.retract(g_mon*F_std*F_trop) def find_cluster_variable(self, g_vector, depth=infinity): @@ -734,6 +776,7 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): seed = self.current_seed() else: seed = self.initial_seed + # add allowed_directions yield seed depth_counter = 0 From 0547f47236d7731cf29a05a1354573775016a9a0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 12:59:06 +0200 Subject: [PATCH 099/191] Removed all @property. Still plowing through ClusterAlgebraSeed --- src/sage/algebras/cluster_algebra.py | 169 ++++++++++++++++++++------- 1 file changed, 124 insertions(+), 45 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index f1b91f297d9..d747b804e4e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -167,7 +167,7 @@ def _div_(self, other): # READY r""" Return the quotient of ``self`` and ``other``. - .. WARNING:: + WARNING:: This method returns an element of ``self.parent().ambient()`` rather than an element of ``self.parent()`` because, a priori, @@ -195,7 +195,7 @@ def d_vector(self): # READY """ monomials = self.lift()._dict().keys() minimal = map(min, zip(*monomials)) - return tuple(-vector(minimal))[:self.parent().rk] + return tuple(-vector(minimal))[:self.parent().rk()] def _repr_(self): # READY r""" @@ -255,7 +255,7 @@ def homogeneous_components(self): #READY sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rk),-self.parent().b_matrix()]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rk()),-self.parent().b_matrix()]]) components = dict() x = self.lift() monomials = x.monomials() @@ -274,14 +274,60 @@ def homogeneous_components(self): #READY class ClusterAlgebraSeed(SageObject): - def __init__(self, B, C, G, parent): + def __init__(self, B, C, G, parent, **kwargs): # READY + r""" + A seed in a cluster algebra. + + INPUT: + + - ``B`` -- a skew-symmetrizable integer matrix; + + - ``C`` -- the matrix of c-vectors of ``self``; + + - ``G`` -- the matrix of g-vectors of ``self``; + + - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the + seed belongs; + + - ``path`` -- list (default: []) the mutation sequence from the initial + seed of ``parent`` to `self`` + + WARNING: + + Seeds should **not** be created manually: no test is performed to + assert that they are built from consistent data nor that they + really are seeds of ``parent``. If you create seeds with + inconsistent data all sort of things can go wrong, even + :meth:`__eq__` is no longer guaranteed to give correct answers. + Use at your ouwn risk. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed + sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) + The seed of Cluster Algebra of rank 4 obtained from the initial by mutating along the sequence [1, 2, 3] + + """ self._B = copy(B) self._C = copy(C) self._G = copy(G) self._parent = parent - self._path = [] + self._path = kwargs.get('path', []) - def __copy__(self): + def __copy__(self): # READY + r""" + Return a copy of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = copy(A.current_seed()) + sage: S == A.current_seed() + True + sage: S is not A.current_seed() + True + """ other = type(self).__new__(type(self)) other._B = copy(self._B) other._C = copy(self._C) @@ -290,41 +336,76 @@ def __copy__(self): other._path = copy(self._path) return other - def __eq__(self, other): - return self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) - # Why was I doing something so convoluted? - # It looks like the idea was to consider seeds up to simultaneous permutation of rows and columns, - # the relation P between the c-matrices determines if there could exist such a permutation, - # the remaining checks then ask about the remaining data + def __eq__(self, other): # READY + r""" + Test equality of two seeds. + + INPUT: - #P = self.c_matrix().inverse()*other.c_matrix() - #return frozenset(P.columns()) == frozenset(identity_matrix(self.parent().rk).columns()) and self.g_matrix()*P == other.g_matrix() and P.inverse()*self.b_matrix()*P == other.b_matrix() and self.parent() == other.parent() + - ``other`` -- a :class:`ClusterAlgebraSeed` - def _repr_(self): + ALGORITHM: + + ``self`` and ``other`` are deemed to be equal if they have the same + parent and their set of g-vectors coincide, i.e. this tests + equality of unlabelled seeds. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = copy(A.current_seed()) + sage: S.mutate([0,2,0]) + sage: S == A.current_seed() + False + sage: S.mutate(2) + sage: S == A.current_seed() + True + """ + return self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + + def _repr_(self): # READY + r""" + Return the string representation of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.current_seed(); S + The initial seed of Cluster Algebra of rank 3 + sage: S.mutate(0); S + The seed of Cluster Algebra of rank 3 obtained from the initial by mutating in direction 0 + sage: S.mutate(1); S + The seed of Cluster Algebra of rank 3 obtained from the initial by mutating along the sequence [0, 1] + """ if self._path == []: return "The initial seed of %s"%str(self.parent()) - elif self._path.__len__() == 1: + elif len(self._path) == 1: return "The seed of %s obtained from the initial by mutating in direction %s"%(str(self.parent()),str(self._path[0])) else: return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) - def depth(self): + def depth(self): # READY r""" Retun the length of a path from the initial seed of :meth:`parent` to ``self``. - .. WARNING:: - This is the length of the path returned by :meth:`path_from_initial_seed` which needs not be the shortest possible. + WARNING:: + This is the length of the path returned by + :meth:`path_from_initial_seed` which needs not be the shortest + possible. EXAMPLES:: sage: A = ClusterAlgebra(['A',2]) - sage: S = A.current_seed() - sage: S.depth() - 0 - sage: S.mutate([0,1]) - sage: S.depth() - 2 - + sage: S1 = A.initial_seed() + sage: S1.mutate([0,1,0,1]) + sage: S1.depth() + 4 + sage: S2 = A.initial_seed() + sage: S2.mutate(1) + sage: S2.depth() + 1 + sage: S1 == S2 + True """ return len(self._path) @@ -381,7 +462,7 @@ def mutate(self, k, mutating_F=True): bla bla ba """ - n = self.parent().rk + n = self.parent().rk() if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') @@ -431,7 +512,7 @@ def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) neg = alg._U(1) - for j in xrange(alg.rk): + for j in xrange(alg.rk()): if self._C[j,k] > 0: pos *= alg._U.gen(j)**self._C[j,k] else: @@ -483,7 +564,7 @@ def path_from_initial_seed(self): # True as an answer. def __contains__(self, element): if isinstance(element, ClusterAlgebraElement): - cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk) ] + cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk()) ] else: element = tuple(element) cluster = self.g_vectors() @@ -625,12 +706,11 @@ def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) def _repr_(self): - return "Cluster Algebra of rank %s"%self.rk + return "Cluster Algebra of rank %s"%self.rk() def _an_element_(self): return self.current_seed().cluster_variable(0) - @property def rk(self): r""" The rank of ``self`` i.e. the number of cluster variables in any seed of @@ -654,7 +734,7 @@ def set_current_seed(self, seed): raise ValueError("This is not a seed in this cluster algebra.") def contains_seed(self, seed): - computed_sd = self.initial_seed + computed_sd = self.initial_seed() computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed @@ -662,14 +742,13 @@ def reset_current_seed(self): r""" Reset the current seed to the initial one """ - self._seed = self.initial_seed + self._seed = self.initial_seed() - @property def initial_seed(self): r""" Return the initial seed """ - n = self.rk + n = self.rk() I = identity_matrix(n) return ClusterAlgebraSeed(self.b_matrix(), I, I, self) @@ -677,7 +756,7 @@ def b_matrix(self): r""" Return the initial exchange matrix of ``self``. """ - n = self.rk + n = self.rk() return copy(self._B0[:n,:]) def g_vectors_so_far(self): @@ -708,7 +787,7 @@ def cluster_variable(self, g_vector): # Should we let the self.F_polynomial below handle raising the exception? raise ValueError("This Cluster Variable has not been computed yet.") F_std = self.F_polynomial(g_vector).subs(self._yhat) - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field F_trop = self.ambient()(self.F_polynomial(g_vector).subs(self._y))._fraction_pair()[1] return self.retract(g_mon*F_std*F_trop) @@ -775,12 +854,12 @@ def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): if from_current_seed: seed = self.current_seed() else: - seed = self.initial_seed + seed = self.initial_seed() # add allowed_directions yield seed depth_counter = 0 - n = self.rk + n = self.rk() cl = frozenset(seed.g_vectors()) clusters = {} clusters[cl] = [ seed, range(n) ] @@ -821,17 +900,17 @@ def reset_exploring_iterator(self, mutating_F=True): def mutate_initial(self, k): # WARNING: at the moment this function does not behave well with respect to coefficients: # sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - # sage: A.initial_seed.b_matrix() + # sage: A.initial_seed().b_matrix() # [ 0 1] # [-2 0] - # sage: A.initial_seed.c_matrix() + # sage: A.initial_seed().c_matrix() # [1 0] # [0 1] # sage: A.mutate_initial(0) - # sage: A.initial_seed.b_matrix() + # sage: A.initial_seed().b_matrix() # [ 0 -1] # [ 2 0] - # sage: A.initial_seed.c_matrix() + # sage: A.initial_seed().c_matrix() # [1 0] # [0 1] # this creates several issues @@ -855,7 +934,7 @@ def mutate_initial(self, k): INPUT: - ``k`` -- integer in between 0 and ``self.rk`` """ - n = self.rk + n = self.rk() if k not in xrange(n): raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) @@ -900,7 +979,7 @@ def mutate_initial(self, k): self._B0.mutate(k) # keep the current seed were it was on the exchange graph - self._seed = self.initial_seed.mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) + self._seed = self.initial_seed().mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) def explore_to_depth(self, depth): while self._explored_depth <= depth: From c1dc7dabfbd4ff9e8039dd19f2314c7dd8006e7b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 13:41:42 +0200 Subject: [PATCH 100/191] Almost done with ClusterAlgebraSeed --- src/sage/algebras/cluster_algebra.py | 247 ++++++++++++++++++++++----- 1 file changed, 207 insertions(+), 40 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d747b804e4e..43327ac73a1 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -171,7 +171,10 @@ def _div_(self, other): # READY This method returns an element of ``self.parent().ambient()`` rather than an element of ``self.parent()`` because, a priori, - we cannot guarantee membership. + we cannot guarantee membership. + + You can force the result to be an element of ``self.parent()`` + by feeding it into ``self.parent().retract``. EXAMPLES:: @@ -363,6 +366,31 @@ def __eq__(self, other): # READY """ return self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + def __contains__(self, element): # READY + r""" + Test whether ``element`` belong to ``self`` + + INPUT: + + - ``element`` -- either a g-vector or and element of :meth:`parent` + + EXAMPLES:: + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: (1,0,0) in S + True + sage: (1,1,0) in S + False + sage: A.cluster_variable((1,0,0)) in S + True + """ + if isinstance(element, ClusterAlgebraElement): + cluster = self.cluster_variables() + else: + element = tuple(element) + cluster = self.g_vectors() + return element in cluster + def _repr_(self): # READY r""" Return the string representation of ``self``. @@ -384,12 +412,23 @@ def _repr_(self): # READY else: return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) + def parent(self): # READY + r""" + Return the parent of ``self``. + + EXAMPLES:: + sage: A = ClusterAlgebra(['B',3]) + sage: A.current_seed().parent() == A + True + """ + return self._parent + def depth(self): # READY r""" - Retun the length of a path from the initial seed of :meth:`parent` to ``self``. + Retun the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. - WARNING:: - This is the length of the path returned by + WARNING: + This is the length of the mutation sequence returned by :meth:`path_from_initial_seed` which needs not be the shortest possible. @@ -409,49 +448,195 @@ def depth(self): # READY """ return len(self._path) - def parent(self): + def path_from_initial_seed(self): # READY r""" - Return the parent of ``self``. + Return a mutation sequence from the initial seed of :meth:`parent` to ``self``. + + WARNING: + + This is the path used to compute ``self`` and it does not have to + be the shortest possible. EXAMPLES:: - sage: A = ClusterAlgebra(['B',3]) - sage: A.current_seed().parent() == A + + sage: A = ClusterAlgebra(['A',2]) + sage: S1 = A.initial_seed() + sage: S1.mutate([0,1,0,1]) + sage: S1.path_from_initial_seed() + [0, 1, 0, 1] + sage: S2 = A.initial_seed() + sage: S2.mutate(1) + sage: S2.path_from_initial_seed() + [1] + sage: S1 == S2 True """ - return self._parent + return copy(self._path) + + def b_matrix(self): # READY + r""" + Return the exchange matrix of ``self``. + + EXAMPLES:: - def b_matrix(self): + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.b_matrix() + [ 0 1 0] + [-1 0 -1] + [ 0 1 0] + """ return copy(self._B) - def c_matrix(self): + def c_matrix(self): # READY + r""" + Return the matrix whose columns are the c-vectors of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.c_matrix() + [1 0 0] + [0 1 0] + [0 0 1] + """ return copy(self._C) - def c_vector(self, j): + def c_vector(self, j): # READY + r""" + Return the j-th c-vector of ``self``. + + INPUT: + + - ``j`` -- an integer in ``range(self.parent().rk())`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.c_vector(0) + (1, 0, 0) + """ return tuple(self._C.column(j)) - def c_vectors(self): + def c_vectors(self): # READY + r""" + Return all the c-vectors of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.c_vectors() + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + """ return map(tuple, self._C.columns()) - def g_matrix(self): + def g_matrix(self): # READY + r""" + Return the matrix whose columns are the g-vectors of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.g_matrix() + [1 0 0] + [0 1 0] + [0 0 1] + """ return copy(self._G) - def g_vector(self, j): + def g_vector(self, j): # READY + r""" + Return the j-th g-vector of ``self``. + + INPUT: + + - ``j`` -- an integer in ``range(self.parent().rk())`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.g_vector(0) + (1, 0, 0) + """ return tuple(self._G.column(j)) - def g_vectors(self): + def g_vectors(self): # READY + r""" + Return all the g-vectors of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.g_vectors() + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + """ return map(tuple, self._G.columns()) - def F_polynomial(self, j): + def F_polynomial(self, j): # READY + r""" + Return the j-th F-polynomial of ``self``. + + INPUT: + + - ``j`` -- an integer in ``range(self.parent().rk())`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.F_polynomial(0) + 1 + """ return self.parent().F_polynomial(self.g_vector(j)) - def F_polynomials(self): - return (self.parent().F_polynomial(g) for g in self.g_vectors()) + def F_polynomials(self): # READY + r""" + Return all the F-polynomials of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.F_polynomials() + [1, 1, 1] + """ + return [self.parent().F_polynomial(g) for g in self.g_vectors()] + + def cluster_variable(self, j): # READY + r""" + Return the j-th cluster variable of ``self``. + + INPUT: + + - ``j`` -- an integer in ``range(self.parent().rk())`` + + EXAMPLES:: - def cluster_variable(self, j): + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.cluster_variable(0) + x0 + """ return self.parent().cluster_variable(self.g_vector(j)) - def cluster_variables(self): - return (self.parent().cluster_variable(g) for g in self.g_vectors()) + def cluster_variables(self): # READY + r""" + Return all the cluster variables of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: S.cluster_variables() + [x0, x1, x2] + """ + return [self.parent().cluster_variable(g) for g in self.g_vectors()] @mutation_parse def mutate(self, k, mutating_F=True): @@ -552,24 +737,6 @@ def _mutated_F(self, k, old_g_vector): # DR: Now I get the same computation time for / and //, 49.7s while simultaneously rebuiling sage return (pos+neg)/alg.F_polynomial(old_g_vector) - def path_from_initial_seed(self): - return copy(self._path) - - # TODO: ideally we should allow to mutate in direction "this g-vector" or - # "this cluster variable" or "sink", "urban renewal" and all the other - # options provided by Gregg et al. To do so I guess the best option is to - # have a generic function transforming all these into an index and use it as - # a decorator. In this way we can also use it in this __contains__ even - # though one may write weird things like "sink" in A.current_seed and get - # True as an answer. - def __contains__(self, element): - if isinstance(element, ClusterAlgebraElement): - cluster = [ self.cluster_variable(i) for i in xrange(self.parent().rk()) ] - else: - element = tuple(element) - cluster = self.g_vectors() - return element in cluster - ############################################################################## # Cluster algebras ############################################################################## From a5e60948e493895bd8cbd1eea6e63d7a7a573233 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 15:23:31 +0200 Subject: [PATCH 101/191] Done with ClusterAlgebraSeed! --- src/sage/algebras/cluster_algebra.py | 96 ++++++++++++++++------------ 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 43327ac73a1..1f4fa6f3246 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -639,27 +639,42 @@ def cluster_variables(self): # READY return [self.parent().cluster_variable(g) for g in self.g_vectors()] @mutation_parse - def mutate(self, k, mutating_F=True): + def mutate(self, k, mutating_F=True): # READY r""" - mutate seed + Mutate ``self``. INPUT: - bla bla ba + - ``mutating_F`` -- bool (default True) whether to compute also + F-polynomials. While knowing F-polynomials is essential to computing + cluster variables, the process of mutating them is quite slow. If you + care only about combinatorial data like g-vectors and c-vectors, + setting ``mutating_F=False`` yields significant benefits in terms of + speed. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: S = A.initial_seed() + sage: S.mutate(0); S + The seed of Cluster Algebra of rank 2 obtained from the initial by mutating in direction 0 + sage: S.mutate(5) + Traceback (most recent call last): + ... + ValueError: Cannot mutate in direction 5. """ n = self.parent().rk() if k not in xrange(n): raise ValueError('Cannot mutate in direction ' + str(k) + '.') - # store mutation path + # store new mutation path if self._path != [] and self._path[-1] == k: self._path.pop() else: self._path.append(k) # find sign of k-th c-vector - # Will this be used enough that it should be a built-in function? if any(x > 0 for x in self._C.column(k)): eps = +1 else: @@ -668,32 +683,62 @@ def mutate(self, k, mutating_F=True): # store the g-vector to be mutated in case we are mutating F-polynomials also old_g_vector = self.g_vector(k) - # G-matrix + # compute new G-matrix J = identity_matrix(n) for j in xrange(n): J[j,k] += max(0, -eps*self._B[j,k]) J[k,k] = -1 self._G = self._G*J - # g-vector path list + # path to new g-vector (we store the shortest encountered so far) g_vector = self.g_vector(k) if g_vector not in self.parent().g_vectors_so_far(): - self.parent()._path_dict[g_vector] = copy(self._path) + old_path_length = infinity # F-polynomials if mutating_F: self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) + else: + old_path_length = len(self.parent()._path_dict[g_vector]) + if len(self._path) < old_path_length: + self.parent()._path_dict[g_vector] = copy(self._path) - # C-matrix + # compute new C-matrix J = identity_matrix(n) for j in xrange(n): J[k,j] += max(0, eps*self._B[k,j]) J[k,k] = -1 self._C = self._C*J - # B-matrix + # compute new B-matrix self._B.mutate(k) - def _mutated_F(self, k, old_g_vector): + def _mutated_F(self, k, old_g_vector): # READY + r""" + Compute new F-polynomial obtained by mutating in direction ``k``. + + INPUT: + + - ``k`` -- an integer in ``range(self.parent().rk())``: the direction + in which we are mutating + + - ``old_g_vector`` -- tuple: the k-th g-vector of ``self`` before + mutating + + NOTE: + + This function is the bottleneck of :meth:`mutate`. The problem is + that operations on polynomials are slow. One can get a significant + speed boost by disabling this method calling :meth:`mutate` with + ``mutating_F=False``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: S = A.initial_seed() + sage: S.mutate(0) + sage: S._mutated_F(0,(1,0)) + u0 + 1 + """ alg = self.parent() pos = alg._U(1) neg = alg._U(1) @@ -706,35 +751,6 @@ def _mutated_F(self, k, old_g_vector): pos *= self.F_polynomial(j)**self._B[j,k] elif self._B[j,k] <0: neg *= self.F_polynomial(j)**(-self._B[j,k]) - # TODO: understand why using // instead of / here slows the code down by - # a factor of 3 but in the original experiments we made at sage days it - # was much faster with // (we were working with cluter variables at the - # time). - # By the way, as of August 28th 2015 we split in half the running time - # compared to sage days. With my laptop plugged in I get - # sage: A = ClusterAlgebra(['E',8]) - # sage: seeds = A.seeds() - # sage: %time void = list(seeds) - # CPU times: user 26.8 s, sys: 21 ms, total: 26.9 s - # Wall time: 26.8 s - ##### - # Bad news: as of 19/10/2015 we got a huge slowdown: - # right now it takes 150s with / and 100s with // - # what did we do wrong? - ## - # I figured it out: the problem is that casting the result to alg._U is - # quite slow: it amounts to run // instead of / :( - # do we need to perform this or can we be less precise here and allow - # F-polynomials to be rational funtions? - # I am partucularly unhappy about this, for the moment the correct and - # slow code is commented - #return alg._U((pos+neg)/alg.F_polynomial(old_g_vector)) - ## - # One more comment: apparently even without casting the result is a - # polynomial! This is really weird but I am not going to complain. I - # suppose we should not do the casting then - - # DR: Now I get the same computation time for / and //, 49.7s while simultaneously rebuiling sage return (pos+neg)/alg.F_polynomial(old_g_vector) ############################################################################## From ec9b6d9fdfa303d212ef2b36d19ef3dd92053593 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 17:29:23 +0200 Subject: [PATCH 102/191] First part on ClusterAlgebra --- src/sage/algebras/cluster_algebra.py | 204 +++++++++++++++++++-------- 1 file changed, 149 insertions(+), 55 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1f4fa6f3246..077cda4b2df 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -251,6 +251,11 @@ def homogeneous_components(self): #READY r""" Return a dictionary of the homogeneous components of ``self''. + OUTPUT: + + A dictionary whose keys are homogeneous degrees and whose values are the + summands of ``self`` of the given degree. + EXAMPLES:: sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) @@ -309,7 +314,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY sage: A = ClusterAlgebra(['F',4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) - The seed of Cluster Algebra of rank 4 obtained from the initial by mutating along the sequence [1, 2, 3] + The seed of A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] """ self._B = copy(B) @@ -364,7 +369,7 @@ def __eq__(self, other): # READY sage: S == A.current_seed() True """ - return self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + return type(self) == type(other) and self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) def __contains__(self, element): # READY r""" @@ -399,11 +404,11 @@ def _repr_(self): # READY sage: A = ClusterAlgebra(['A',3]) sage: S = A.current_seed(); S - The initial seed of Cluster Algebra of rank 3 + The initial seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring sage: S.mutate(0); S - The seed of Cluster Algebra of rank 3 obtained from the initial by mutating in direction 0 + The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(1); S - The seed of Cluster Algebra of rank 3 obtained from the initial by mutating along the sequence [0, 1] + The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] """ if self._path == []: return "The initial seed of %s"%str(self.parent()) @@ -657,7 +662,7 @@ def mutate(self, k, mutating_F=True): # READY sage: A = ClusterAlgebra(['A',2]) sage: S = A.initial_seed() sage: S.mutate(0); S - The seed of Cluster Algebra of rank 2 obtained from the initial by mutating in direction 0 + The seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(5) Traceback (most recent call last): ... @@ -758,33 +763,65 @@ def _mutated_F(self, k, old_g_vector): # READY ############################################################################## class ClusterAlgebra(Parent): - r""" - INPUT: - - ``data`` -- some data defining a cluster algebra. + Element = ClusterAlgebraElement - - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra - is defined. + def __init__(self, data, **kwargs): # READY + r""" + A cluster algebra. - - ``cluster_variables_prefix`` -- string (default 'x'). + INPUT: - - ``cluster_variables_names`` -- a list of strings. Superseedes - ``cluster_variables_prefix``. + - ``data`` -- some data defining a cluster algebra. It can be anything + that can be parsed by :class:`ClusterQuiver`. - - ``coefficients_prefix`` -- string (default 'y'). + - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra + is defined. - - ``coefficients_names`` -- a list of strings. Superseedes - ``cluster_variables_prefix``. + - ``cluster_variable_prefix`` -- string (default 'x'); it needs to be + a valid variable name. - - ``principal_coefficients`` -- bool (default: False). Superseedes any - coefficient defined by ``data``. - """ + - ``cluster_variable_names`` -- a list of strings. Superseedes + ``cluster_variable_prefix``. Each element needs to be a valid + variable name. - Element = ClusterAlgebraElement + - ``coefficient_prefix`` -- string (default 'y'); it needs to be + a valid variable name. - def __init__(self, data, **kwargs): - r""" - See :class:`ClusterAlgebra` for full documentation. + - ``coefficient_names`` -- a list of strings. Superseedes + ``cluster_variable_prefix``. Each element needs to be a valid + variable name. + + - ``principal_coefficients`` -- bool (default: False). Superseedes any + coefficient defined by ``data``. + + EXAMPLES:: + + sage: B = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) + sage: A = ClusterAlgebra(B); A + A Cluster Algebra with cluster variables x0, x1, x2, x3 and coefficients y0, y1 over Integer Ring + sage: A.gens() + [x0, x1, x2, x3, y0, y1] + sage: A = ClusterAlgebra(['A',2]); A + A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + sage: A = ClusterAlgebra(['A',2], principal_coefficients=True); A.gens() + [x0, x1, y0, y1] + sage: A = ClusterAlgebra(['A',2], principal_coefficients=True, coefficient_prefix='x'); A.gens() + [x0, x1, x2, x3] + + ALGORITHM: + + The implementation is mainly based on [FZ07]_ and [NZ12]_. + + REFERENCES: + + .. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. + Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. + + .. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in + cluster algebras', Algebraic groups and quantum groups, Contemp. + Math., vol. 565, Amer. Math. Soc., Providence, RI, 2012, pp. + 217–226. """ # Temporary variables Q = ClusterQuiver(data) @@ -808,24 +845,24 @@ def __init__(self, data, **kwargs): self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) # Determine the names of the initial cluster variables - variables_prefix = kwargs.get('cluster_variables_prefix','x') - variables = kwargs.get('cluster_variables_names', [variables_prefix+str(i) for i in xrange(n)]) + variables_prefix = kwargs.get('cluster_variable_prefix','x') + variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in xrange(n)])) if len(variables) != n: - raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) + raise ValueError("cluster_variable_names should be a list of %d valid variable names"%n) # Determine scalars scalars = kwargs.get('scalars', ZZ) # Determine coefficients and setup self._base if m>0: - coefficients_prefix = kwargs.get('coefficients_prefix', 'y') - if coefficients_prefix == variables_prefix: + coefficient_prefix = kwargs.get('coefficient_prefix', 'y') + if coefficient_prefix == variables_prefix: offset = n else: offset = 0 - coefficients = kwargs.get('coefficients_names', [coefficients_prefix+str(i) for i in xrange(offset,m+offset)]) + coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in xrange(offset,m+offset)])) if len(coefficients) != m: - raise ValueError("coefficients_names should be a list of %d valid variable names"%m) + raise ValueError("coefficient_names should be a list of %d valid variable names"%m) base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars @@ -836,9 +873,6 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions - # BUG WORKAROUND: if your sage installation does not have trac:`19538` - # merged uncomment the following line and comment the next - #self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n+m)])) for j in xrange(n)]) @@ -846,6 +880,8 @@ def __init__(self, data, **kwargs): self._is_principal = (M0 == I) # Store initial data + # NOTE: storing both _B0 as rectangular matrix and _yhat is redundant. + # We keep both around for speed purposes. self._B0 = copy(B0) self._n = n self.reset_current_seed() @@ -862,24 +898,88 @@ def __init__(self, data, **kwargs): embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) - def __copy__(self): - other = type(self).__new__(type(self)) - other._U = self._U - other._path_dict = copy(self._path_dict) + def __copy__(self): # READY + r""" + Return a copy of ``self``. + + EXAMPLES:: + + sage: A1 = ClusterAlgebra(['A',3]) + sage: A2 = copy(A1) + sage: A2 == A1 + True + sage: A2 is not A1 + True + """ + n = self.rk() + cv_names = self.variable_names()[:n] + coeff_names = self.variable_names()[n:] + other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, + coefficient_names=coeff_names, scalars=self.base().base()) other._F_poly_dict = copy(self._F_poly_dict) - other._ambient = self._ambient - other._y = copy(self._y) - other._yhat = copy(self._yhat) - other._is_principal = self._is_principal - other._B0 = copy(self._B0) - other._n = self._n - other._seed = copy(self._seed) - # We probably need to put n=2 initializations here also - # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ + other._path_dict = copy(self._path_dict) + S = self.current_seed() + S._parent = other + other.set_current_seed(S) return other - def __eq__(self, other): - return type(self) == type(other) and self._B0 == other._B0 and self._yhat == other._yhat and self.base() == other.base() + def __eq__(self, other): # READY + r""" + Test equality of two cluster algebras. + + INPUT: + + - ``other`` -- a :class:`ClusterAlgebra` + + ALGORITHM: + + ``self`` and ``other`` are deemed to be equal if they have the same + initial exchange matrix and their ambients coincide. In + particular we do not keep track of how much each algebra has been + explored. + + EXAMPLES:: + + sage: A1 = ClusterAlgebra(['A',3]) + sage: A2 = copy(A1) + sage: A1 is not A2 + True + sage: A1 == A2 + True + sage: A1.current_seed().mutate([0,1,2]) + sage: A1 == A2 + True + """ + return type(self) == type(other) and self._B0 == other._B0 and self.ambient() == other.ambient() + + def _repr_(self): # READY + r""" + Return the string representation of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix(1),principal_coefficients=True); A + A Cluster Algebra with cluster variable x0 and coefficient y0 over Integer Ring + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + """ + var_names = self.variable_names()[:self.rk()] + var_names = (" " if len(var_names)==1 else "s ") + ", ".join(var_names) + coeff_names = self.variable_names()[self.rk():] + coeff_prefix = " and" +(" " if len(coeff_names) >0 else " no ") + "coefficient" + coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) + return "A Cluster Algebra with cluster variable" + var_names + coeff + " over " + repr(self.base().base()) + + def _an_element_(self): # READY + r""" + Return an element of ``self``. + + EXAMPLES:: + sage: A = ClusterAlgebra(['A',2]) + sage: A.an_element() + x0 + """ + return self.current_seed().cluster_variable(0) def _coerce_map_from_(self, other): # if other is a cluster algebra allow inherit coercions from ambients @@ -888,12 +988,6 @@ def _coerce_map_from_(self, other): # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) - def _repr_(self): - return "Cluster Algebra of rank %s"%self.rk() - - def _an_element_(self): - return self.current_seed().cluster_variable(0) - def rk(self): r""" The rank of ``self`` i.e. the number of cluster variables in any seed of From 14ec659a8b608dfd6e8bb11b02259bc6cbb6d2eb Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 8 Aug 2016 19:13:24 +0200 Subject: [PATCH 103/191] Coerce like a pro! --- src/sage/algebras/cluster_algebra.py | 53 +++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 077cda4b2df..e5456e980d5 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -35,9 +35,10 @@ from sage.categories.quotient_fields import QuotientFields from sage.categories.rings import Rings from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver +from sage.combinat.permutation import Permutation from sage.functions.generalized import sign from sage.functions.other import binomial -from sage.matrix.constructor import identity_matrix +from sage.matrix.constructor import identity_matrix, matrix from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod @@ -817,7 +818,7 @@ def __init__(self, data, **kwargs): # READY .. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. - + .. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in cluster algebras', Algebraic groups and quantum groups, Contemp. Math., vol. 565, Amer. Math. Soc., Providence, RI, 2012, pp. @@ -981,10 +982,52 @@ def _an_element_(self): # READY """ return self.current_seed().cluster_variable(0) - def _coerce_map_from_(self, other): - # if other is a cluster algebra allow inherit coercions from ambients + def _coerce_map_from_(self, other): # READY + r""" + Test whether there is a coercion from ``other`` to ``self``. + + ALGORITHM: + + If ``other`` is an instance of :class:`ClusterAlgebra` then allow + coercion if ``other.ambient()`` can be coerced into + ``self.ambient()`` and other can be obtained from ``self`` + permuting variables and coefficients and/or freezing some initial + cluster variables. + + Otherwise allow anthing that coerces into ``self.base()`` to coerce + into ``self``. + + EXAMPLES:: + + sage: B1 = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) + sage: B2 = B1.matrix_from_columns([0,1,2]) + sage: A1 = ClusterAlgebra(B1, coefficient_prefix='x') + sage: A2 = ClusterAlgebra(B2, coefficient_prefix='x') + sage: A1.has_coerce_map_from(A2) + True + sage: A2.has_coerce_map_from(A1) + False + sage: f = A1.coerce_map_from(A2) + sage: A2.find_cluster_variable((-1, 1, -1)) + [0, 2, 1] + sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) + sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) + True + """ if isinstance(other, ClusterAlgebra): - return self.ambient().has_coerce_map_from(other.ambient()) + gen_s = self.gens() + gen_o = other.gens() + if len(gen_s) == len(gen_o): + f = self.ambient().coerce_map_from(other.ambient()) + if f is not None: + perm = Permutation([ gen_s.index(self(f(v)))+1 for v in gen_o ]).inverse() + n = self.rk() + m = len(perm) - n + M = self._B0[n:,:] + B = block_matrix([[self.b_matrix(),-M.transpose()],[M,matrix(m)]]) + B.permute_rows_and_columns(perm,perm) + return B.matrix_from_columns(range(other.rk())) == other._B0 + # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) From 29c7d86939cc4b7da56825a7dab8ef2c6fcbf343 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 01:25:17 +0200 Subject: [PATCH 104/191] Almost finished with ClusterAlgebra --- src/sage/algebras/cluster_algebra.py | 501 +++++++++++++++++++++------ 1 file changed, 393 insertions(+), 108 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e5456e980d5..fb48b1fefd0 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -38,6 +38,8 @@ from sage.combinat.permutation import Permutation from sage.functions.generalized import sign from sage.functions.other import binomial +from sage.geometry.cone import Cone +from sage.geometry.fan import Fan from sage.matrix.constructor import identity_matrix, matrix from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method @@ -46,7 +48,7 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing, LaurentPolynomialRing_generic from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ from sage.structure.element_wrapper import ElementWrapper @@ -698,16 +700,13 @@ def mutate(self, k, mutating_F=True): # READY # path to new g-vector (we store the shortest encountered so far) g_vector = self.g_vector(k) - if g_vector not in self.parent().g_vectors_so_far(): - old_path_length = infinity - # F-polynomials - if mutating_F: - self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) - else: - old_path_length = len(self.parent()._path_dict[g_vector]) - if len(self._path) < old_path_length: + if not self.parent()._path_dict.has_key(g_vector) or len(self.parent()._path_dict[g_vector]) > len(self._path): self.parent()._path_dict[g_vector] = copy(self._path) + # compute F-polynomials + if mutating_F and not self.parent()._F_poly_dict.has_key(g_vector): + self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) + # compute new C-matrix J = identity_matrix(n) for j in xrange(n): @@ -1008,7 +1007,7 @@ def _coerce_map_from_(self, other): # READY sage: A2.has_coerce_map_from(A1) False sage: f = A1.coerce_map_from(A2) - sage: A2.find_cluster_variable((-1, 1, -1)) + sage: A2.find_g_vector((-1, 1, -1)) [0, 2, 1] sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) @@ -1031,191 +1030,492 @@ def _coerce_map_from_(self, other): # READY # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) - def rk(self): + def rk(self): # READY r""" - The rank of ``self`` i.e. the number of cluster variables in any seed of - ``self``. + Return the rank of ``self`` i.e. the number of cluster variables in any seed. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + sage: A.rk() + 2 """ return self._n - def current_seed(self): + def current_seed(self): # READY r""" - The current seed of ``self``. + Return the current seed of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.current_seed() + The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ return self._seed - def set_current_seed(self, seed): + def set_current_seed(self, seed): # READY r""" - Set ``self._seed`` to ``seed`` if it makes sense. + Set the value reported by :meth:`current_seed` to ``seed`` if it makes sense. + + INPUT: + + - ``seed`` -- an instance of :class:`ClusterAlgebraSeed` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: S = copy(A.current_seed()) + sage: S.mutate([0,1,0]) + sage: A.current_seed() == S + False + sage: A.set_current_seed(S) + sage: A.current_seed() == S + True """ if self.contains_seed(seed): self._seed = seed else: raise ValueError("This is not a seed in this cluster algebra.") - def contains_seed(self, seed): + def contains_seed(self, seed): # READY + r""" + Test if ``seed`` is a seed in ``self``. + + INPUT: + + - ``seed`` -- an instance of :class:`ClusterAlgebraSeed` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + sage: S = copy(A.current_seed()) + sage: A.contains_seed(S) + True + """ computed_sd = self.initial_seed() computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed - def reset_current_seed(self): + def reset_current_seed(self): # READY r""" - Reset the current seed to the initial one + Reset the value reported by :meth:`current_seed` to :meth:`initial_seed`. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.current_seed().mutate([1,0]) + sage: A.current_seed() == A.initial_seed() + False + sage: A.reset_current_seed() + sage: A.current_seed() == A.initial_seed() + True """ self._seed = self.initial_seed() - def initial_seed(self): + def initial_seed(self): # READY r""" - Return the initial seed + Return the initial seed of ``self`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.initial_seed() + The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ n = self.rk() I = identity_matrix(n) return ClusterAlgebraSeed(self.b_matrix(), I, I, self) - def b_matrix(self): + def b_matrix(self): # READY r""" Return the initial exchange matrix of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.b_matrix() + [ 0 1] + [-1 0] """ n = self.rk() return copy(self._B0[:n,:]) - def g_vectors_so_far(self): + def g_vectors_so_far(self): # READY r""" - Return the g-vectors of cluster variables encountered so far. + Return a list of the g-vectors of cluster variables encountered so far. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.current_seed().mutate(0) + sage: A.g_vectors_so_far() + [(0, 1), (1, 0), (-1, 1)] """ return self._path_dict.keys() - def F_polynomial(self, g_vector): - g_vector= tuple(g_vector) + def cluster_variables_so_far(self): # READY + r""" + Return a list of the cluster variables encountered so far. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.current_seed().mutate(0) + sage: A.cluster_variables_so_far() + [x1, x0, (x1 + 1)/x0] + """ + return map(self.cluster_variable, self.g_vectors_so_far()) + + def F_polynomials_so_far(self): # READY + r""" + Return a list of the cluster variables encountered so far. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.current_seed().mutate(0) + sage: A.F_polynomials_so_far() + [1, 1, u0 + 1] + """ + return self._F_poly_dict.values() + + def F_polynomial(self, g_vector): # READY + r""" + Return the F-polynomial with g-vector ``g_vector`` if it has been found. + + INPUT: + + - ``g_vector`` -- a tuple: the g-vector of the F-polynomial to return. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.F_polynomial((-1, 1)) + Traceback (most recent call last): + ... + KeyError: 'The g-vector (-1, 1) has not been found yet.' + sage: A.initial_seed().mutate(0,mutating_F=False) + sage: A.F_polynomial((-1, 1)) + Traceback (most recent call last): + ... + KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet. + You can compute it by mutating from the initial seed along the sequence [0].' + sage: A.initial_seed().mutate(0) + sage: A.F_polynomial((-1, 1)) + u0 + 1 + """ + g_vector = tuple(g_vector) try: return self._F_poly_dict[g_vector] - except: - # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? - # Yes, perhaps with the a prompt first, something like: - #comp = raw_input("This F-polynomial has not been computed yet. It can be found using %s mutations. Continue? (y or n):"%str(directions.__len__())) - #if comp == 'y': - # ...compute the F-polynomial... + except KeyError: if g_vector in self._path_dict: - raise ValueError("The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s."%(str(g_vector),str(self._path_dict[g_vector]))) + msg = "The F-polynomial with g-vector %s has not been computed yet. "%str(g_vector) + msg += "You can compute it by mutating from the initial seed along the sequence " + msg += str(self._path_dict[g_vector]) + "." + raise KeyError(msg) else: - raise ValueError("The F-polynomial with g-vector %s has not been computed yet."%str(g_vector)) + raise KeyError("The g-vector %s has not been found yet."%str(g_vector)) @cached_method(key=lambda a,b: tuple(b) ) - def cluster_variable(self, g_vector): + def cluster_variable(self, g_vector): # READY + r""" + Return the cluster variable with g-vector ``g_vector`` if it has been found. + + INPUT: + + - ``g_vector`` -- a tuple: the g-vector of the cluster variable to return. + + ALGORITHM: + + This function computes cluster variables from their g-vectors and + and F-polynomials using the "separation of additions" formula of + Theorem 3.7 in [FZ07]_. + + EXAMPLE:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.initial_seed().mutate(0) + sage: A.cluster_variable((-1,1)) + (x1 + 1)/x0 + """ g_vector = tuple(g_vector) - if not g_vector in self.g_vectors_so_far(): - # Should we let the self.F_polynomial below handle raising the exception? - raise ValueError("This Cluster Variable has not been computed yet.") - F_std = self.F_polynomial(g_vector).subs(self._yhat) + F = self.F_polynomial(g_vector) + F_std = F.subs(self._yhat) g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) - # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field - F_trop = self.ambient()(self.F_polynomial(g_vector).subs(self._y))._fraction_pair()[1] + F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] return self.retract(g_mon*F_std*F_trop) - def find_cluster_variable(self, g_vector, depth=infinity): + def find_g_vector(self, g_vector, depth=infinity): # READY r""" - Returns the shortest mutation path to obtain the cluster variable with - g-vector ``g_vector`` from the initial seed. + Return a mutation sequence to obtain a seed containing the g-vector ``g_vector`` from the initial seed. + + INPUT: - ``depth``: maximum distance from ``self.current_seed`` to reach. + - ``g_vector`` -- a tuple: the g-vector to find. - WARNING: if this method is interrupted then ``self._sd_iter`` is left in - an unusable state. To use again this method it is then necessary to - reset ``self._sd_iter`` via self.reset_exploring_iterator() + - ``depth`` -- a positive integer: the maximum distance from ``self.current_seed`` to reach. + + OUTPUT: + + This function returns a list of integers if it can find + ``g_vector`` otherwise it returns ``None``. If the exploring + iterator stops it means that the algebra is of finite type and + ``g_vector`` is not the g-vector of any cluster variable. In this + case the fuction resets the iterator and raises an error. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['G',2],principal_coefficients=True) + sage: A.find_g_vector((-2, 3), depth=2) + sage: A.find_g_vector((-2, 3), depth=3) + [0, 1, 0] """ g_vector = tuple(g_vector) - mutation_counter = 0 while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) self._explored_depth = seed.depth() - except: - raise ValueError("Could not find a cluster variable with g-vector %s up to mutation depth %s after performing %s mutations."%(str(g_vector),str(depth),str(mutation_counter))) - - # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, - # the following code would allow the user to continue searching the exchange graph - #cont = raw_input("Could not find a cluster variable with g-vector %s up to mutation depth %s."%(str(g_vector),str(depth))+" Continue searching? (y or n):") - #if cont == 'y': - # new_depth = 0 - # while new_depth <= depth: - # new_depth = raw_input("Please enter a new mutation search depth greater than %s:"%str(depth)) - # seeds.send(new_depth) - #else: - # raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) - - mutation_counter += 1 - return copy(self._path_dict[g_vector]) - - def ambient(self): + except StopIteration: + # Unless self._sd_iter has been manually altered, we checked + # all the seeds of self and did not find g_vector. + # Do some house cleaning before failing + self.reset_exploring_iterator() + raise ValueError("%s is not the g-vector of any cluster variable of %s."%(str(g_vector),str(self))) + return copy(self._path_dict.get(g_vector,None)) + + def ambient(self): # READY + r""" + Return the Laurent polynomial ring containing ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.ambient() + Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring + """ return self._ambient - def lift(self, x): + def lift(self, x): # READY r""" - Return x as an element of self._ambient + Return ``x`` as an element of :meth:`ambient`. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: x = A.cluster_variable((1,0)) + sage: A.lift(x).parent() + Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring """ return self.ambient()(x.value) - def retract(self, x): + def retract(self, x): # READY + r""" + Return ``x`` as an element of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: L = A.ambient() + sage: x = L.gen(0) + sage: A.retract(x).parent() + A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + """ return self(x) - def gens(self): + def gens(self): # READY r""" - Return the generators of :meth:`self.ambient` + Return the list of initial cluster variables and coefficients of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.gens() + [x0, x1, y0, y1] """ return map(self.retract, self.ambient().gens()) - def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): + def coefficients(self): # READY + r""" + Return the list of coefficients of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.coefficients() + [y0, y1] + """ + if isinstance(self.base(), LaurentPolynomialRing_generic): + return map(self.retract, self.base().gens()) + else: + return [] + + def initial_cluster_variables(self): # READY r""" - Return an iterator producing all seeds of ``self`` up to distance - ``depth`` from ``self.initial_seed`` or ``self.current_seed``. + Return the list of initial cluster variables of ``self``. + + EXAMPLES:: - If ``mutating_F`` is set to false it does not compute F_polynomials + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.initial_cluster_variables() + [x0, x1] """ - if from_current_seed: - seed = self.current_seed() + return map(self.retract, self.ambient().gens()[:self.rk()]) + + def seeds(self, **kwargs): # READY + r""" + Return an iterator running over seeds of ``self``. + + INPUT: + + - ``from_current_seed`` -- bool (default False): whether to start the + iterator from :meth:`current_seed` or :meth:`initial_seed`. + + - ``mutating_F`` -- bool (default True): wheter to compute also + F-polynomials; for speed considerations you may want to disable this. + + - ``allowed_directions`` -- a tuple of integers (default + ``range(self.rk())``: the directions in which to mutate. + + - ``depth`` -- the maximum depth at which to stop searching. + + ALGORITHM: + + This function traverses the exchange graph in a breadth-first search. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',4]) + sage: seeds = A.seeds(allowed_directions=[3,0,1]) + sage: _ = list(seeds) + sage: A.g_vectors_so_far() + [(-1, 0, 0, 0), + (1, 0, 0, 0), + (0, 0, 0, 1), + (0, -1, 0, 0), + (0, 0, 1, 0), + (0, 1, 0, 0), + (-1, 1, 0, 0), + (0, 0, 0, -1)] + """ + # should we begin from the current seed? + if kwargs.get('from_current_seed', False): + seed = copy(self.current_seed()) else: seed = self.initial_seed() - # add allowed_directions + # yield first seed yield seed + + # some initialization depth_counter = 0 n = self.rk() + + # do we mutate F-polynomials? + mutating_F = kwargs.get('mutating_F', True) + + # which directions are we allowed to mutate into + allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(n)))) + + # setup seeds storage cl = frozenset(seed.g_vectors()) clusters = {} - clusters[cl] = [ seed, range(n) ] + clusters[cl] = [ seed, copy(allowed_dirs) ] + + # ready, set, go! gets_bigger = True - while gets_bigger and depth_counter < depth: + while gets_bigger and depth_counter < kwargs.get('depth', infinity): + # remember if we got a new seed gets_bigger = False - keys = clusters.keys() - for key in keys: + + for key in clusters.keys(): sd, directions = clusters[key] while directions: + # we can mutate in some direction i = directions.pop() new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: + # we already had new_sd, make sure it does not mutate to sd during next round j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) - except: + except ValueError: pass else: + # we got a new seed gets_bigger = True - # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed - new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] + # next round do not mutate back to sd and do commuting mutations only in directions j > i + new_directions = [ j for j in copy(allowed_dirs) if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] - # Use this if we want to have the user pass info to the - # iterator - #new_depth = yield new_sd - #if new_depth > depth: - # depth = new_depth yield new_sd + # we went one step deeper depth_counter += 1 - def reset_exploring_iterator(self, mutating_F=True): + def reset_exploring_iterator(self, mutating_F=True): # READY + r""" + Reset the iterator used to explore ``self``. + + INPUT: + + - ``mutating_F`` -- bool (default True): wheter to compute also + F-polynomials; for speed considerations you may want to disable this. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',4]) + sage: A.reset_exploring_iterator(mutating_F=False) + sage: A.explore_to_depth(infinity) + sage: len(A.g_vectors_so_far()) + 14 + sage: len(A.F_polynomials_so_far()) + 4 + """ self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 + def explore_to_depth(self, depth): # READY + r""" + Explore the exchange graph of ``self`` up to distance ``depth`` from the initial seed. + + INPUT: + + - ``depth`` -- the maximum depth at which to stop searching. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',4]) + sage: A.explore_to_depth(infinity) + sage: len(A.g_vectors_so_far()) + 14 + """ + while self._explored_depth <= depth: + try: + seed = next(self._sd_iter) + self._explored_depth = seed.depth() + except: + break + + def cluster_fan(self, depth=infinity): + r""" + Return the cluster fan (the fan of g-vectors) of ``self``. + + INPUT: + + - ``depth`` -- the maximum depth at which to comute. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.cluster_fan() + Rational polyhedral fan in 2-d lattice N + """ + seeds = self.seeds(depth=depth, mutating_F=False) + cones = map(lambda s: Cone(s.g_vectors()), seeds) + return Fan(cones) + @mutation_parse def mutate_initial(self, k): # WARNING: at the moment this function does not behave well with respect to coefficients: @@ -1301,21 +1601,6 @@ def mutate_initial(self, k): # keep the current seed were it was on the exchange graph self._seed = self.initial_seed().mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) - def explore_to_depth(self, depth): - while self._explored_depth <= depth: - try: - seed = next(self._sd_iter) - self._explored_depth = seed.depth() - except: - break - - def cluster_fan(self, depth=infinity): - from sage.geometry.cone import Cone - from sage.geometry.fan import Fan - seeds = self.seeds(depth=depth, mutating_F=False) - cones = map(lambda s: Cone(s.g_vectors()), seeds) - return Fan(cones) - # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass From 1d773cf6cb3ba4d3c2e26c612da907e59a17a16b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 01:34:08 +0200 Subject: [PATCH 105/191] Minor polish --- src/sage/algebras/cluster_algebra.py | 36 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index fb48b1fefd0..42d022a76ba 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1534,19 +1534,6 @@ def mutate_initial(self, k): # [1 0] # [0 1] # this creates several issues - # BTW: mutating the initial seed in place creates several issues with elements of the algebra: for example: - # sage: A = ClusterAlgebra(['B',4],principal_coefficients=True) - # sage: A.explore_to_depth(infinity) - # sage: x = A.cluster_variable((-1, 1, -2, 2)) - # sage: x.is_homogeneous() - # True - # sage: A.mutate_initial([0,3]) - # sage: x in A - # True - # sage: (-1, 1, -2, 2) in A.g_vectors_so_far() - # False - # sage: x.is_homogeneous() - # False r""" Mutate ``self`` in direction `k` at the initial cluster. @@ -1601,18 +1588,31 @@ def mutate_initial(self, k): # keep the current seed were it was on the exchange graph self._seed = self.initial_seed().mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) - # DESIDERATA. Some of these are probably unrealistic + # DESIDERATA + # Some of these are probably unrealistic def upper_cluster_algebra(self): - pass + r""" + Return the upper cluster algebra associated to ``self``. + """ + raise NotImplementedError("Not implemented yet.") def upper_bound(self): - pass + r""" + Return the upper bound associated to ``self``. + """ + raise NotImplementedError("Not implemented yet.") def lower_bound(self): - pass + r""" + Return the lower bound associated to ``self``. + """ + raise NotImplementedError("Not implemented yet.") def theta_basis_element(self, g_vector): - pass + r""" + Return the element of the theta basis with g-vector ``g_vetor``. + """ + raise NotImplementedError("Not implemented yet.") #### # Methods only defined for special cases From 1c26e7c67e5513b94d9f105f01d171b4cec208fa Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 01:49:28 +0200 Subject: [PATCH 106/191] Minor polish 2 --- src/sage/algebras/cluster_algebra.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 42d022a76ba..1ad159ce571 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1518,43 +1518,25 @@ def cluster_fan(self, depth=infinity): @mutation_parse def mutate_initial(self, k): - # WARNING: at the moment this function does not behave well with respect to coefficients: - # sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - # sage: A.initial_seed().b_matrix() - # [ 0 1] - # [-2 0] - # sage: A.initial_seed().c_matrix() - # [1 0] - # [0 1] - # sage: A.mutate_initial(0) - # sage: A.initial_seed().b_matrix() - # [ 0 -1] - # [ 2 0] - # sage: A.initial_seed().c_matrix() - # [1 0] - # [0 1] - # this creates several issues - r""" Mutate ``self`` in direction `k` at the initial cluster. INPUT: - - ``k`` -- integer in between 0 and ``self.rk`` + + EXAMPLES:: + """ n = self.rk() if k not in xrange(n): raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) - #store current seed location - path_to_current = self.current_seed().path_from_initial_seed() #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() new_path_dict[tuple(identity_matrix(n).column(k))] = [] new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) - poly_ring = PolynomialRing(ZZ,'u') F_subs_tuple = tuple([self._U.gen(k)**(-1) if j==k else self._U.gen(j)*self._U.gen(k)**max(-self._B0[k][j],0)*(1+self._U.gen(k))**(self._B0[k][j]) for j in xrange(n)]) for g_vect in self._path_dict: From dee777b3755c106da5a4d1176559ef0f63016399 Mon Sep 17 00:00:00 2001 From: drupel Date: Mon, 8 Aug 2016 21:55:10 -0400 Subject: [PATCH 107/191] Minor typo fixed --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1ad159ce571..16cc409ad0d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -993,7 +993,7 @@ def _coerce_map_from_(self, other): # READY permuting variables and coefficients and/or freezing some initial cluster variables. - Otherwise allow anthing that coerces into ``self.base()`` to coerce + Otherwise allow anything that coerces into ``self.base()`` to coerce into ``self``. EXAMPLES:: From 28c46e953f2cab749983efa2f0500fecf870b346 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 15:52:32 +0200 Subject: [PATCH 108/191] Rewrote completely mutate_initial --- src/sage/algebras/cluster_algebra.py | 159 +++++++++++++++++++-------- 1 file changed, 113 insertions(+), 46 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 16cc409ad0d..2dbc17c550e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -91,7 +91,7 @@ def mutation_parse(mutate): # READY mutate.__doc__ = doc[0] + doc[1] @wraps(mutate) - def mutate_wrapper(self, direction, *args, **kwargs): + def mutate_wrapper(self, direction, **kwargs): inplace = kwargs.pop('inplace', True) and mutate.__name__ != "mutate_initial" if inplace: to_mutate = self @@ -111,7 +111,7 @@ def mutate_wrapper(self, direction, *args, **kwargs): seq = iter((direction,)) for k in seq: - mutate(to_mutate, k, *args, **kwargs) + mutate(to_mutate, k, **kwargs) if not inplace: return to_mutate @@ -912,10 +912,10 @@ def __copy__(self): # READY True """ n = self.rk() - cv_names = self.variable_names()[:n] - coeff_names = self.variable_names()[n:] + cv_names = self.initial_cluster_variable_names() + coeff_names = self.coefficient_names() other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, - coefficient_names=coeff_names, scalars=self.base().base()) + coefficient_names=coeff_names, scalars=self.scalars()) other._F_poly_dict = copy(self._F_poly_dict) other._path_dict = copy(self._path_dict) S = self.current_seed() @@ -963,12 +963,12 @@ def _repr_(self): # READY sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring """ - var_names = self.variable_names()[:self.rk()] + var_names = self.initial_cluster_variable_names() var_names = (" " if len(var_names)==1 else "s ") + ", ".join(var_names) - coeff_names = self.variable_names()[self.rk():] + coeff_names = self.coefficient_names() coeff_prefix = " and" +(" " if len(coeff_names) >0 else " no ") + "coefficient" coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) - return "A Cluster Algebra with cluster variable" + var_names + coeff + " over " + repr(self.base().base()) + return "A Cluster Algebra with cluster variable" + var_names + coeff + " over " + repr(self.scalars()) def _an_element_(self): # READY r""" @@ -1298,6 +1298,18 @@ def ambient(self): # READY """ return self._ambient + def scalars(self): # READY + r""" + Return the scalars on which ``self`` is defined. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.scalars() + Integer Ring + """ + return self.base().base() + def lift(self, x): # READY r""" Return ``x`` as an element of :meth:`ambient`. @@ -1352,6 +1364,19 @@ def coefficients(self): # READY else: return [] + def coefficient_names(self): # READY + r""" + Return the list of coefficient names. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A.coefficient_names() + ('y0', 'y1') + """ + return self.variable_names()[self.rk():] + + def initial_cluster_variables(self): # READY r""" Return the list of initial cluster variables of ``self``. @@ -1364,6 +1389,18 @@ def initial_cluster_variables(self): # READY """ return map(self.retract, self.ambient().gens()[:self.rk()]) + def initial_cluster_variable_names(self): # READY + r""" + Return the list of initial variable names. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A.initial_cluster_variable_names() + ('x0', 'x1') + """ + return self.variable_names()[:self.rk()] + def seeds(self, **kwargs): # READY r""" Return an iterator running over seeds of ``self``. @@ -1519,56 +1556,86 @@ def cluster_fan(self, depth=infinity): @mutation_parse def mutate_initial(self, k): r""" - Mutate ``self`` in direction `k` at the initial cluster. + Return the cluster algebra obtained by mutating ``self`` at the initial seed. INPUT: + + ALGORITHM: + + This function computes data for the new algebra from known data for + the old algebra using [NZ12]_ equation (4.2) for g-vectors, and + [FZ07]_ equation (6.21) for F-polynomials. The exponent h in the + formula for F-polynomials is -min(0,old_g_vect[k]) due to [NZ07]_ + Proposition 4.2. EXAMPLES:: + sage: A = ClusterAlgebra(['F',4]) + sage: A.explore_to_depth(infinity) + sage: B = A.b_matrix() + sage: B.mutate(0) + sage: A1 = ClusterAlgebra(B) + sage: A1.explore_to_depth(infinity) + sage: A2 = A1.mutate_initial(0) + sage: A2._F_poly_dict == A._F_poly_dict + True """ n = self.rk() - if k not in xrange(n): - raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) - - #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) - new_path_dict = dict() - new_F_dict = dict() - new_path_dict[tuple(identity_matrix(n).column(k))] = [] - new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) - - F_subs_tuple = tuple([self._U.gen(k)**(-1) if j==k else self._U.gen(j)*self._U.gen(k)**max(-self._B0[k][j],0)*(1+self._U.gen(k))**(self._B0[k][j]) for j in xrange(n)]) + raise ValueError('Cannot mutate in direction ' + str(k) + '.') - for g_vect in self._path_dict: + # save computed data + old_F_poly_dict = copy(self._F_poly_dict) + old_path_dict = copy(self._path_dict) + old_path_to_current = copy(self.current_seed().path_from_initial_seed()) + + # mutate initial exchange matrix + B0 = copy(self._B0) + B0.mutate(k) + + # HACK: pretend there is no embedding of self into self.parent(): we + # will recreate this in a moment. The problem is that there can be + # only one embedding per object and it is set up in __init__. + self._unset_embedding() + + # update algebra + cv_names = self.initial_cluster_variable_names() + coeff_names = self.coefficient_names() + scalars = self.scalars() + self.__init__(B0, cluster_variable_names=cv_names, + coefficient_names=coeff_names, scalars=scalars) + + # substitution data to compute new F-polynomials + Ugen = self._U.gens() + # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 + F_subs = tuple(Ugen[k]**(-1) if j==k else Ugen[j]*Ugen[k]**max(B0[k,j],0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)) + + # restore computed data + for old_g_vect in old_path_dict: #compute new path - path = self._path_dict[g_vect] - if g_vect == tuple(identity_matrix(n).column(k)): - new_path = [k] - elif path != []: - if path[0] != k: - new_path = [k] + path - else: - new_path = path[1:] - else: - new_path = [] - - #compute new g-vector - new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) - for i in xrange(n): - new_g_vect += max(sign(g_vect[k])*self._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) - new_path_dict[tuple(new_g_vect)] = new_path + new_path = old_path_dict[old_g_vect] + new_path = ([k]+new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] + + # compute new g-vector + J = identity_matrix(n) + eps = sign(old_g_vect[k]) + for j in xrange(n): + # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 + J[j,k] += max(0, -eps*B0[j,k]) + J[k,k] = -1 + new_g_vect = tuple(J*vector(old_g_vect)) + self._path_dict[new_g_vect] = new_path #compute new F-polynomial - h = -min(0,g_vect[k]) - new_F_dict[tuple(new_g_vect)] = self._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)**h*(self._U.gen(k)+1)**g_vect[k] - - self._path_dict = new_path_dict - self._F_poly_dict = new_F_dict - - self._B0.mutate(k) - - # keep the current seed were it was on the exchange graph - self._seed = self.initial_seed().mutate([k]+self.current_seed().path_from_initial_seed(), mutating_F=False, inplace=False) + if old_F_poly_dict.has_key(old_g_vect): + h = -min(0,old_g_vect[k]) + new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] + self._F_poly_dict[new_g_vect] = new_F_poly + + # reset self.current_seed() to the previous location + S = self.initial_seed() + S.mutate([k]+old_path_to_current, mutating_F=False) + self.set_current_seed(S) # DESIDERATA # Some of these are probably unrealistic From c67acc21ecb4f19e364c156fbe7414c2e5d360e7 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 16:13:30 +0200 Subject: [PATCH 109/191] Moved things around because it makes more sense --- src/sage/algebras/cluster_algebra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 2dbc17c550e..0dda2b60e64 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1608,14 +1608,10 @@ def mutate_initial(self, k): # substitution data to compute new F-polynomials Ugen = self._U.gens() # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = tuple(Ugen[k]**(-1) if j==k else Ugen[j]*Ugen[k]**max(B0[k,j],0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)) + F_subs = [Ugen[k]**(-1) if j==k else Ugen[j]*Ugen[k]**max(B0[k,j],0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)] # restore computed data for old_g_vect in old_path_dict: - #compute new path - new_path = old_path_dict[old_g_vect] - new_path = ([k]+new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] - # compute new g-vector J = identity_matrix(n) eps = sign(old_g_vect[k]) @@ -1624,6 +1620,10 @@ def mutate_initial(self, k): J[j,k] += max(0, -eps*B0[j,k]) J[k,k] = -1 new_g_vect = tuple(J*vector(old_g_vect)) + + #compute new path + new_path = old_path_dict[old_g_vect] + new_path = ([k]+new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] self._path_dict[new_g_vect] = new_path #compute new F-polynomial From 08be7cefe2c3b4115aebfbdfde50431bb145486e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 16:56:38 +0200 Subject: [PATCH 110/191] Finished with functions!! --- src/sage/algebras/cluster_algebra.py | 111 ++++++++++++++++----------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 0dda2b60e64..5626609f850 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -892,7 +892,7 @@ def __init__(self, data, **kwargs): # READY # Add methods that are defined only for special cases if n == 2 and m == 0: self.greedy_element = MethodType(greedy_element, self, self.__class__) - self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) + self._greedy_coefficient = MethodType(_greedy_coefficient, self, self.__class__) # Register embedding into self.ambient() embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) @@ -1667,58 +1667,79 @@ def theta_basis_element(self, g_vector): # Methods only defined for special cases #### -# Greedy elements exist only in rank 2 -# Does not yet take into account coefficients, this can probably be done by -# using the greedy coefficients to write down the F-polynomials -def greedy_element(self, d_vector): - b = abs(self._B0[0,1]) - c = abs(self._B0[1,0]) - a1 = d_vector[0] - a2 = d_vector[1] - # TODO: we need to have something like initial_cluster_variables so that we - # do not have to use the generators of the ambient field. (this would also - # make it better behaved when allowing different names) - # Warning: there might be issues with coercions, make sure there are not - x1 = self._ambient.gens()[0] - x2 = self._ambient.gens()[1] +def greedy_element(self, d_vector): + r""" + Return the greedy element with d-vector ``d_vector``. + + INPUT + + - ``d_vector`` -- tuple of 2 integers: the d-vector of the element to compute. + + ALGORITHM: + + This implements bla from [LLZ??]_ ???? + + REFERENCES: + + .. [LLZ??] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. + Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. + CHANGETHIS!!!!! + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',[1,1],1]) + sage: A.greedy_element((1,1)) + (x0^2 + x1^2 + 1)/(x0*x1) + """ + b = abs(self.b_matrix()[0,1]) + c = abs(self.b_matrix()[1,0]) + (a1, a2) = d_vector + # here we use the generators of self.ambient() because cluster variables do not have an inverse. + (x1, x2) = self.ambient().gens() if a1 < 0: if a2 < 0: - return self.retract(x1**(-a1)*x2**(-a2)) + return self.retract(x1**(-a1) * x2**(-a2)) else: - return self.retract(x1**(-a1)*((1+x2**c)/x1)**a2) + return self.retract(x1**(-a1) * ((1+x2**c)/x1)**a2) elif a2 < 0: - return self.retract(((1+x1**b)/x2)**a1*x2**(-a2)) + return self.retract(((1+x1**b)/x2)**a1 * x2**(-a2)) output = 0 - for p in xrange(0,a2+1): - for q in xrange(0,a1+1): - output += self.greedy_coefficient(d_vector,p,q)*x1**(b*p)*x2**(c*q) - return self.retract(x1**(-a1)*x2**(-a2)*output) - -# Is this function something we want to make public or do we want to make this a -# private method changing it to _greedy_coefficient ? -def greedy_coefficient(self,d_vector,p,q): - b = abs(self._B0[0,1]) - c = abs(self._B0[1,0]) - a1 = d_vector[0] - a2 = d_vector[1] + for p in xrange(0, a2+1): + for q in xrange(0, a1+1): + output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) + return self.retract(x1**(-a1) * x2**(-a2) * output) + +def _greedy_coefficient(self,d_vector,p,q): # READY + r""" + Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with d-vector ``d_vector``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',[1,1],1]) + sage: A.greedy_element((1,1)) + (x0^2 + x1^2 + 1)/(x0*x1) + sage: A._greedy_coefficient((1,1),0,0) + 1 + sage: A._greedy_coefficient((1,1),1,0) + 1 + """ + b = abs(self.b_matrix()[0,1]) + c = abs(self.b_matrix()[1,0]) + (a1, a2) = d_vector p = Integer(p) q = Integer(q) if p == 0 and q == 0: - return 1 + return Integer(1) sum1 = 0 for k in range(1,p+1): - bin = 0 - if a2-c*q+k-1 >= k: - bin = binomial(a2-c*q+k-1,k) - sum1 += (-1)**(k-1)*self.greedy_coefficient(d_vector,p-k,q)*bin + bino = 0 + if a2 - c*q + k - 1 >= k: + bino = binomial(a2 - c*q + k - 1, k) + sum1 += (-1)**(k-1) * self._greedy_coefficient(d_vector, p-k, q) * bino sum2 = 0 - for l in range(1,q+1): - bin = 0 - if a1-b*p+l-1 >= l: - bin = binomial(a1-b*p+l-1,l) - sum2 += (-1)**(l-1)*self.greedy_coefficient(d_vector,p,q-l)*bin - #print "sum1=",sum1,"sum2=",sum2 - return max(sum1,sum2) - - - + for l in range(1, q+1): + bino = 0 + if a1 - b*p + l - 1 >= l: + bino = binomial(a1 - b*p + l - 1, l) + sum2 += (-1)**(l-1) * self._greedy_coefficient(d_vector, p, q-l) * bino + return Integer(max(sum1, sum2)) From d18bf4ccfa399c5557c24c5735efe8e0d510cf9b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 9 Aug 2016 17:05:08 +0200 Subject: [PATCH 111/191] Cleaned up imports --- src/sage/algebras/cluster_algebra.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 5626609f850..90c20516f4a 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -26,13 +26,10 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -# TODO: check that we import all we need and possibly move some import used -# rarely close to where needed from copy import copy from functools import wraps from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism -from sage.categories.quotient_fields import QuotientFields from sage.categories.rings import Rings from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver from sage.combinat.permutation import Permutation From 887c6ac7506244392ad5e8815c55e03be15c6574 Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 9 Aug 2016 11:20:05 -0400 Subject: [PATCH 112/191] Added basic example to preamble (most likely more to come) and fixed small spacing issue in _repr_ of ClusterAlgebra --- src/sage/algebras/cluster_algebra.py | 36 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 90c20516f4a..8780aba3009 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -13,7 +13,27 @@ EXAMPLES:: - TODO: we need to write a complete example of usage here + sage: A = ClusterAlgebra(['A',2]) + sage: A.b_matrix() + [ 0 1] + [-1 0] + sage: A.explore_to_depth(2) + sage: A.g_vectors_so_far() + [(0, 1), (0, -1), (1, 0), (-1, 1), (-1, 0)] + sage: A.F_polynomials_so_far() + [1, u1 + 1, 1, u0 + 1, u0*u1 + u0 + 1] + sage: A.cluster_variables_so_far() + [x1, (x0 + 1)/x1, x0, (x1 + 1)/x0, (x0 + x1 + 1)/(x0*x1)] + sage: B = A.mutate_initial(0) + sage: B.b_matrix() + [ 0 -1] + [ 1 0] + sage: B.g_vectors_so_far() + [(0, 1), (0, -1), (1, 0), (1, -1), (-1, 0)] + sage: B.F_polynomials_so_far() + [1, u0*u1 + u1 + 1, 1, u1 + 1, u0 + 1] + sage: B.cluster_variables_so_far() + [x1, (x0 + x1 + 1)/(x0*x1), x0, (x0 + 1)/x1, (x1 + 1)/x0] """ #***************************************************************************** @@ -965,7 +985,7 @@ def _repr_(self): # READY coeff_names = self.coefficient_names() coeff_prefix = " and" +(" " if len(coeff_names) >0 else " no ") + "coefficient" coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) - return "A Cluster Algebra with cluster variable" + var_names + coeff + " over " + repr(self.scalars()) + return "A Cluster Algebra with cluster variable" + var_names + coeff + (" " if len(coeff_names)>0 else "") + "over " + repr(self.scalars()) def _an_element_(self): # READY r""" @@ -986,15 +1006,15 @@ def _coerce_map_from_(self, other): # READY If ``other`` is an instance of :class:`ClusterAlgebra` then allow coercion if ``other.ambient()`` can be coerced into - ``self.ambient()`` and other can be obtained from ``self`` + ``self.ambient()`` and other can be obtained from ``self`` by permuting variables and coefficients and/or freezing some initial - cluster variables. - + cluster variables. + Otherwise allow anything that coerces into ``self.base()`` to coerce into ``self``. EXAMPLES:: - + sage: B1 = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) sage: B2 = B1.matrix_from_columns([0,1,2]) sage: A1 = ClusterAlgebra(B1, coefficient_prefix='x') @@ -1009,7 +1029,7 @@ def _coerce_map_from_(self, other): # READY sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) True - """ + """ if isinstance(other, ClusterAlgebra): gen_s = self.gens() gen_o = other.gens() @@ -1494,7 +1514,7 @@ def reset_exploring_iterator(self, mutating_F=True): # READY INPUT: - - ``mutating_F`` -- bool (default True): wheter to compute also + - ``mutating_F`` -- bool (default True): whether to also compute F-polynomials; for speed considerations you may want to disable this. EXAMPLES:: From b21dace8167a5637e06b67992bf7d1217390bb03 Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 9 Aug 2016 11:30:08 -0400 Subject: [PATCH 113/191] Removed double spaces from _repr_ examples --- src/sage/algebras/cluster_algebra.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8780aba3009..f398f0fd192 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -334,7 +334,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY sage: A = ClusterAlgebra(['F',4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) - The seed of A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] + The seed of A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] """ self._B = copy(B) @@ -424,11 +424,11 @@ def _repr_(self): # READY sage: A = ClusterAlgebra(['A',3]) sage: S = A.current_seed(); S - The initial seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring + The initial seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring sage: S.mutate(0); S - The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(1); S - The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] + The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] """ if self._path == []: return "The initial seed of %s"%str(self.parent()) @@ -682,7 +682,7 @@ def mutate(self, k, mutating_F=True): # READY sage: A = ClusterAlgebra(['A',2]) sage: S = A.initial_seed() sage: S.mutate(0); S - The seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(5) Traceback (most recent call last): ... @@ -820,7 +820,7 @@ def __init__(self, data, **kwargs): # READY sage: A.gens() [x0, x1, x2, x3, y0, y1] sage: A = ClusterAlgebra(['A',2]); A - A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring sage: A = ClusterAlgebra(['A',2], principal_coefficients=True); A.gens() [x0, x1, y0, y1] sage: A = ClusterAlgebra(['A',2], principal_coefficients=True, coefficient_prefix='x'); A.gens() @@ -1068,7 +1068,7 @@ def current_seed(self): # READY sage: A = ClusterAlgebra(['A',2]) sage: A.current_seed() - The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ return self._seed @@ -1140,7 +1140,7 @@ def initial_seed(self): # READY sage: A = ClusterAlgebra(['A',2]) sage: A.initial_seed() - The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ n = self.rk() I = identity_matrix(n) From 98d1b2024f0592da352809c0e0f79ab9af8caddc Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 9 Aug 2016 12:20:09 -0400 Subject: [PATCH 114/191] removed extra spaces and added LLZ reference --- src/sage/algebras/cluster_algebra.py | 149 +++++++++++++-------------- 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index f398f0fd192..6fdda0cff74 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -79,12 +79,12 @@ def mutation_parse(mutate): # READY r""" Preparse input for mutation functions. - + This wrapper provides: - inplace (only for seeds) - mutate along sequence - mutate at all sinks/sources - + Possible things to implement later include: - mutate at a cluster variariable - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) @@ -95,11 +95,11 @@ def mutation_parse(mutate): # READY doc[0] += "INPUT:" if mutate.__name__ == "mutate": doc[0] += r""" - + - ``inplace`` -- bool (default True) whether to mutate in place or to return a new object """ doc[0] += r""" - + - ``direction`` -- in which direction(s) to mutate. It can be - an integer in ``range(self.rk())`` to mutate in one direction only; - an iterable of such integers to mutate along a sequence; @@ -114,7 +114,7 @@ def mutate_wrapper(self, direction, **kwargs): to_mutate = self else: to_mutate = copy(self) - + if direction == "sinks": B = self.b_matrix() seq = [ i for i in range(B.ncols()) if all( x<=0 for x in B.column(i) ) ] @@ -176,7 +176,7 @@ def _add_(self, other): # READY - ``other`` - an element of ``self.parent()`` EXAMPLES:: - + sage: A = ClusterAlgebra(['F',4]) sage: A.an_element() + A.an_element() 2*x0 @@ -188,11 +188,11 @@ def _div_(self, other): # READY Return the quotient of ``self`` and ``other``. WARNING:: - + This method returns an element of ``self.parent().ambient()`` rather than an element of ``self.parent()`` because, a priori, - we cannot guarantee membership. - + we cannot guarantee membership. + You can force the result to be an element of ``self.parent()`` by feeding it into ``self.parent().retract``. @@ -233,7 +233,7 @@ def _repr_(self): # READY """ numer, denom = self.lift()._fraction_pair() return repr(numer/denom) - + #### # Methods not always defined #### @@ -257,9 +257,9 @@ def g_vector(self): #READY def is_homogeneous(self): #READY r""" Return ``True`` if ``self`` is an homogeneous element of ``self.parent()``. - + EXAMPLES:: - + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) sage: x.is_homogeneous() @@ -272,12 +272,12 @@ def homogeneous_components(self): #READY Return a dictionary of the homogeneous components of ``self''. OUTPUT: - + A dictionary whose keys are homogeneous degrees and whose values are the summands of ``self`` of the given degree. EXAMPLES:: - + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) sage: x.homogeneous_components() @@ -307,7 +307,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY A seed in a cluster algebra. INPUT: - + - ``B`` -- a skew-symmetrizable integer matrix; - ``C`` -- the matrix of c-vectors of ``self``; @@ -321,12 +321,12 @@ def __init__(self, B, C, G, parent, **kwargs): # READY seed of ``parent`` to `self`` WARNING: - + Seeds should **not** be created manually: no test is performed to assert that they are built from consistent data nor that they really are seeds of ``parent``. If you create seeds with inconsistent data all sort of things can go wrong, even - :meth:`__eq__` is no longer guaranteed to give correct answers. + :meth:`__eq__` is no longer guaranteed to give correct answers. Use at your ouwn risk. EXAMPLES:: @@ -345,10 +345,10 @@ def __init__(self, B, C, G, parent, **kwargs): # READY def __copy__(self): # READY r""" - Return a copy of ``self``. - + Return a copy of ``self``. + EXAMPLES:: - + sage: A = ClusterAlgebra(['A',3]) sage: S = copy(A.current_seed()) sage: S == A.current_seed() @@ -373,7 +373,7 @@ def __eq__(self, other): # READY - ``other`` -- a :class:`ClusterAlgebraSeed` ALGORITHM: - + ``self`` and ``other`` are deemed to be equal if they have the same parent and their set of g-vectors coincide, i.e. this tests equality of unlabelled seeds. @@ -419,7 +419,7 @@ def __contains__(self, element): # READY def _repr_(self): # READY r""" Return the string representation of ``self``. - + EXAMPLES:: sage: A = ClusterAlgebra(['A',3]) @@ -483,7 +483,7 @@ def path_from_initial_seed(self): # READY be the shortest possible. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2]) sage: S1 = A.initial_seed() sage: S1.mutate([0,1,0,1]) @@ -608,7 +608,7 @@ def F_polynomial(self, j): # READY Return the j-th F-polynomial of ``self``. INPUT: - + - ``j`` -- an integer in ``range(self.parent().rk())`` EXAMPLES:: @@ -638,7 +638,7 @@ def cluster_variable(self, j): # READY Return the j-th cluster variable of ``self``. INPUT: - + - ``j`` -- an integer in ``range(self.parent().rk())`` EXAMPLES:: @@ -667,7 +667,7 @@ def cluster_variables(self): # READY def mutate(self, k, mutating_F=True): # READY r""" Mutate ``self``. - + INPUT: - ``mutating_F`` -- bool (default True) whether to compute also @@ -754,7 +754,7 @@ def _mutated_F(self, k, old_g_vector): # READY ``mutating_F=False``. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2]) sage: S = A.initial_seed() sage: S.mutate(0) @@ -802,12 +802,12 @@ def __init__(self, data, **kwargs): # READY ``cluster_variable_prefix``. Each element needs to be a valid variable name. - - ``coefficient_prefix`` -- string (default 'y'); it needs to be - a valid variable name. + - ``coefficient_prefix`` -- string (default 'y'); it needs to be + a valid variable name. - ``coefficient_names`` -- a list of strings. Superseedes - ``cluster_variable_prefix``. Each element needs to be a valid - variable name. + ``cluster_variable_prefix``. Each element needs to be a valid + variable name. - ``principal_coefficients`` -- bool (default: False). Superseedes any coefficient defined by ``data``. @@ -827,14 +827,14 @@ def __init__(self, data, **kwargs): # READY [x0, x1, x2, x3] ALGORITHM: - + The implementation is mainly based on [FZ07]_ and [NZ12]_. REFERENCES: - + .. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. - + .. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in cluster algebras', Algebraic groups and quantum groups, Contemp. Math., vol. 565, Amer. Math. Soc., Providence, RI, 2012, pp. @@ -917,10 +917,10 @@ def __init__(self, data, **kwargs): # READY def __copy__(self): # READY r""" - Return a copy of ``self``. - + Return a copy of ``self``. + EXAMPLES:: - + sage: A1 = ClusterAlgebra(['A',3]) sage: A2 = copy(A1) sage: A2 == A1 @@ -931,7 +931,7 @@ def __copy__(self): # READY n = self.rk() cv_names = self.initial_cluster_variable_names() coeff_names = self.coefficient_names() - other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, + other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, coefficient_names=coeff_names, scalars=self.scalars()) other._F_poly_dict = copy(self._F_poly_dict) other._path_dict = copy(self._path_dict) @@ -945,18 +945,18 @@ def __eq__(self, other): # READY Test equality of two cluster algebras. INPUT: - + - ``other`` -- a :class:`ClusterAlgebra` - + ALGORITHM: - + ``self`` and ``other`` are deemed to be equal if they have the same initial exchange matrix and their ambients coincide. In particular we do not keep track of how much each algebra has been explored. - + EXAMPLES:: - + sage: A1 = ClusterAlgebra(['A',3]) sage: A2 = copy(A1) sage: A1 is not A2 @@ -984,9 +984,9 @@ def _repr_(self): # READY var_names = (" " if len(var_names)==1 else "s ") + ", ".join(var_names) coeff_names = self.coefficient_names() coeff_prefix = " and" +(" " if len(coeff_names) >0 else " no ") + "coefficient" - coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) - return "A Cluster Algebra with cluster variable" + var_names + coeff + (" " if len(coeff_names)>0 else "") + "over " + repr(self.scalars()) - + coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names)>0 else "") + return "A Cluster Algebra with cluster variable" + var_names + coeff + "over " + repr(self.scalars()) + def _an_element_(self): # READY r""" Return an element of ``self``. @@ -1065,7 +1065,7 @@ def current_seed(self): # READY Return the current seed of ``self``. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2]) sage: A.current_seed() The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring @@ -1079,7 +1079,7 @@ def set_current_seed(self, seed): # READY INPUT: - ``seed`` -- an instance of :class:`ClusterAlgebraSeed` - + EXAMPLES:: sage: A = ClusterAlgebra(['A',2]) @@ -1137,7 +1137,7 @@ def initial_seed(self): # READY Return the initial seed of ``self`` EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2]) sage: A.initial_seed() The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring @@ -1208,7 +1208,7 @@ def F_polynomial(self, g_vector): # READY - ``g_vector`` -- a tuple: the g-vector of the F-polynomial to return. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2]) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): @@ -1218,7 +1218,7 @@ def F_polynomial(self, g_vector): # READY sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... - KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet. + KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet. You can compute it by mutating from the initial seed along the sequence [0].' sage: A.initial_seed().mutate(0) sage: A.F_polynomial((-1, 1)) @@ -1242,9 +1242,9 @@ def cluster_variable(self, g_vector): # READY Return the cluster variable with g-vector ``g_vector`` if it has been found. INPUT: - + - ``g_vector`` -- a tuple: the g-vector of the cluster variable to return. - + ALGORITHM: This function computes cluster variables from their g-vectors and @@ -1276,7 +1276,7 @@ def find_g_vector(self, g_vector, depth=infinity): # READY - ``depth`` -- a positive integer: the maximum distance from ``self.current_seed`` to reach. OUTPUT: - + This function returns a list of integers if it can find ``g_vector`` otherwise it returns ``None``. If the exploring iterator stops it means that the algebra is of finite type and @@ -1306,7 +1306,7 @@ def find_g_vector(self, g_vector, depth=infinity): # READY def ambient(self): # READY r""" Return the Laurent polynomial ring containing ``self``. - + EXAMPLES:: sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) @@ -1345,7 +1345,7 @@ def retract(self, x): # READY Return ``x`` as an element of ``self``. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) sage: L = A.ambient() sage: x = L.gen(0) @@ -1382,15 +1382,15 @@ def coefficients(self): # READY return [] def coefficient_names(self): # READY - r""" + r""" Return the list of coefficient names. - - EXAMPLES:: - + + EXAMPLES:: + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) sage: A.coefficient_names() - ('y0', 'y1') - """ + ('y0', 'y1') + """ return self.variable_names()[self.rk():] @@ -1472,7 +1472,7 @@ def seeds(self, **kwargs): # READY # which directions are we allowed to mutate into allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(n)))) - + # setup seeds storage cl = frozenset(seed.g_vectors()) clusters = {} @@ -1483,7 +1483,7 @@ def seeds(self, **kwargs): # READY while gets_bigger and depth_counter < kwargs.get('depth', infinity): # remember if we got a new seed gets_bigger = False - + for key in clusters.keys(): sd, directions = clusters[key] while directions: @@ -1518,7 +1518,7 @@ def reset_exploring_iterator(self, mutating_F=True): # READY F-polynomials; for speed considerations you may want to disable this. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',4]) sage: A.reset_exploring_iterator(mutating_F=False) sage: A.explore_to_depth(infinity) @@ -1539,10 +1539,10 @@ def explore_to_depth(self, depth): # READY - ``depth`` -- the maximum depth at which to stop searching. EXAMPLES:: - + sage: A = ClusterAlgebra(['A',4]) sage: A.explore_to_depth(infinity) - sage: len(A.g_vectors_so_far()) + sage: len(A.g_vectors_so_far()) 14 """ while self._explored_depth <= depth: @@ -1576,7 +1576,7 @@ def mutate_initial(self, k): Return the cluster algebra obtained by mutating ``self`` at the initial seed. INPUT: - + ALGORITHM: This function computes data for the new algebra from known data for @@ -1684,7 +1684,7 @@ def theta_basis_element(self, g_vector): # Methods only defined for special cases #### -def greedy_element(self, d_vector): +def greedy_element(self, d_vector): r""" Return the greedy element with d-vector ``d_vector``. @@ -1694,13 +1694,12 @@ def greedy_element(self, d_vector): ALGORITHM: - This implements bla from [LLZ??]_ ???? + This implements greedy elements of a rank 2 cluster algebra from [LLZ14]_ equation (1.5). REFERENCES: - - .. [LLZ??] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. - Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. - CHANGETHIS!!!!! + + .. [LLZ??] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 + cluster algebras", Selecta Math. 20 (2014), 57-82. EXAMPLES:: @@ -1728,7 +1727,7 @@ def greedy_element(self, d_vector): def _greedy_coefficient(self,d_vector,p,q): # READY r""" - Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with d-vector ``d_vector``. + Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with denominator vector ``d_vector``. EXAMPLES:: From 3d0a7a64000057e8bdd4e85b48335930441e1f85 Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 9 Aug 2016 13:24:38 -0400 Subject: [PATCH 115/191] Fixed [LLZ??] --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 6fdda0cff74..d1299774ac8 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -327,7 +327,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY really are seeds of ``parent``. If you create seeds with inconsistent data all sort of things can go wrong, even :meth:`__eq__` is no longer guaranteed to give correct answers. - Use at your ouwn risk. + Use at your own risk. EXAMPLES:: @@ -1698,7 +1698,7 @@ def greedy_element(self, d_vector): REFERENCES: - .. [LLZ??] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 + .. [LLZ14] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 cluster algebras", Selecta Math. 20 (2014), 57-82. EXAMPLES:: From 0d5e473e3e0c3dd65acdfb28501dc39bc0201cb3 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 01:47:41 +0200 Subject: [PATCH 116/191] Configured sage to build documentation for cluster_algebra.py. Some fixes here and there to the code. --- src/doc/en/reference/algebras/index.rst | 1 + src/sage/algebras/cluster_algebra.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 70ee13c190c..411fcac0f90 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -42,6 +42,7 @@ Named associative algebras sage/algebras/affine_nil_temperley_lieb sage/combinat/diagram_algebras sage/algebras/clifford_algebra + sage/algebras/cluster_algebra sage/combinat/descent_algebra sage/algebras/hall_algebra sage/algebras/iwahori_hecke_algebra diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d1299774ac8..bd8f944ac0b 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -76,7 +76,7 @@ ############################################################################## # Helper functions ############################################################################## -def mutation_parse(mutate): # READY +def _mutation_parse(mutate): # READY r""" Preparse input for mutation functions. @@ -269,7 +269,7 @@ def is_homogeneous(self): #READY def homogeneous_components(self): #READY r""" - Return a dictionary of the homogeneous components of ``self''. + Return a dictionary of the homogeneous components of ``self``. OUTPUT: @@ -442,6 +442,7 @@ def parent(self): # READY Return the parent of ``self``. EXAMPLES:: + sage: A = ClusterAlgebra(['B',3]) sage: A.current_seed().parent() == A True @@ -663,7 +664,7 @@ def cluster_variables(self): # READY """ return [self.parent().cluster_variable(g) for g in self.g_vectors()] - @mutation_parse + @_mutation_parse def mutate(self, k, mutating_F=True): # READY r""" Mutate ``self``. @@ -833,12 +834,12 @@ def __init__(self, data, **kwargs): # READY REFERENCES: .. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. - Coefficients", Compos. Math. 143 (2007), no. 1, 112–164. + Coefficients", Compos. Math. 143 (2007), no. 1, 112-164. .. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in cluster algebras', Algebraic groups and quantum groups, Contemp. Math., vol. 565, Amer. Math. Soc., Providence, RI, 2012, pp. - 217–226. + 217-226. """ # Temporary variables Q = ClusterQuiver(data) @@ -1558,7 +1559,7 @@ def cluster_fan(self, depth=infinity): INPUT: - - ``depth`` -- the maximum depth at which to comute. + - ``depth`` -- (default ``infinity``): the maximum depth at which to comute. EXAMPLES:: @@ -1570,7 +1571,7 @@ def cluster_fan(self, depth=infinity): cones = map(lambda s: Cone(s.g_vectors()), seeds) return Fan(cones) - @mutation_parse + @_mutation_parse def mutate_initial(self, k): r""" Return the cluster algebra obtained by mutating ``self`` at the initial seed. @@ -1582,7 +1583,7 @@ def mutate_initial(self, k): This function computes data for the new algebra from known data for the old algebra using [NZ12]_ equation (4.2) for g-vectors, and [FZ07]_ equation (6.21) for F-polynomials. The exponent h in the - formula for F-polynomials is -min(0,old_g_vect[k]) due to [NZ07]_ + formula for F-polynomials is -min(0,old_g_vect[k]) due to [NZ12]_ Proposition 4.2. EXAMPLES:: From 82ccaefb65aaaf5e9b8260dcca5bfb3b403becb7 Mon Sep 17 00:00:00 2001 From: drupel Date: Tue, 9 Aug 2016 23:47:07 -0400 Subject: [PATCH 117/191] Fixed _repr_ of ClusterAlgebraSeed to have a rather than A --- src/sage/algebras/cluster_algebra.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index bd8f944ac0b..3221be3264e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -334,7 +334,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY sage: A = ClusterAlgebra(['F',4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) - The seed of A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] """ self._B = copy(B) @@ -424,18 +424,18 @@ def _repr_(self): # READY sage: A = ClusterAlgebra(['A',3]) sage: S = A.current_seed(); S - The initial seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring sage: S.mutate(0); S - The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(1); S - The seed of A Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] + The seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] """ if self._path == []: - return "The initial seed of %s"%str(self.parent()) + return "The initial seed of a %s"%str(self.parent())[2:] elif len(self._path) == 1: - return "The seed of %s obtained from the initial by mutating in direction %s"%(str(self.parent()),str(self._path[0])) + return "The seed of a %s obtained from the initial by mutating in direction %s"%(str(self.parent())[2:],str(self._path[0])) else: - return "The seed of %s obtained from the initial by mutating along the sequence %s"%(str(self.parent()),str(self._path)) + return "The seed of a %s obtained from the initial by mutating along the sequence %s"%(str(self.parent())[2:],str(self._path)) def parent(self): # READY r""" @@ -683,7 +683,7 @@ def mutate(self, k, mutating_F=True): # READY sage: A = ClusterAlgebra(['A',2]) sage: S = A.initial_seed() sage: S.mutate(0); S - The seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 sage: S.mutate(5) Traceback (most recent call last): ... @@ -1069,7 +1069,7 @@ def current_seed(self): # READY sage: A = ClusterAlgebra(['A',2]) sage: A.current_seed() - The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ return self._seed @@ -1141,7 +1141,7 @@ def initial_seed(self): # READY sage: A = ClusterAlgebra(['A',2]) sage: A.initial_seed() - The initial seed of A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ n = self.rk() I = identity_matrix(n) From 52b157eed44383f62a81a278d6ec8d8a543c04b3 Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 10 Aug 2016 01:48:58 -0400 Subject: [PATCH 118/191] Doc edits and added an initial_cluster_variable function --- src/sage/algebras/cluster_algebra.py | 120 +++++++++++++++++---------- 1 file changed, 76 insertions(+), 44 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3221be3264e..9a3f38299bc 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -76,7 +76,7 @@ ############################################################################## # Helper functions ############################################################################## -def _mutation_parse(mutate): # READY +def _mutation_parse(mutate): r""" Preparse input for mutation functions. @@ -139,11 +139,11 @@ def mutate_wrapper(self, direction, **kwargs): # Elements of a cluster algebra ############################################################################## -class ClusterAlgebraElement(ElementWrapper): # READY +class ClusterAlgebraElement(ElementWrapper): - def __init__(self, parent, value): # READY + def __init__(self, parent, value): r""" - An element of a cluster algebra. + An element of a Cluster Algebra. INPUT: @@ -167,7 +167,7 @@ def __init__(self, parent, value): # READY self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) # AdditiveMagmas.Subobjects currently does not implements _add_ - def _add_(self, other): # READY + def _add_(self, other): r""" Return the sum of ``self`` and ``other``. @@ -183,7 +183,7 @@ def _add_(self, other): # READY """ return self.parent().retract(self.lift() + other.lift()) - def _div_(self, other): # READY + def _div_(self, other): r""" Return the quotient of ``self`` and ``other``. @@ -204,9 +204,9 @@ def _div_(self, other): # READY """ return self.lift()/other.lift() - def d_vector(self): # READY + def d_vector(self): r""" - Return the d-vector of ``self`` as a tuple of integers. + Return the denominator vector of ``self`` as a tuple of integers. EXAMPLES:: @@ -220,7 +220,7 @@ def d_vector(self): # READY minimal = map(min, zip(*monomials)) return tuple(-vector(minimal))[:self.parent().rk()] - def _repr_(self): # READY + def _repr_(self): r""" Return the string representation of ``self``. @@ -238,7 +238,7 @@ def _repr_(self): # READY # Methods not always defined #### -def g_vector(self): #READY +def g_vector(self): #READY r""" Return the g-vector of ``self``. @@ -248,26 +248,27 @@ def g_vector(self): #READY sage: A.cluster_variable((1,0)).g_vector() == (1,0) True """ - components = self.homogeneous_components() - if len(components) == 1: - return components.keys()[0] + if self.is_homogeneous(): + return self.homogeneous_components().keys()[0] else: raise ValueError("This element is not homogeneous.") -def is_homogeneous(self): #READY +def is_homogeneous(self): r""" Return ``True`` if ``self`` is an homogeneous element of ``self.parent()``. EXAMPLES:: sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A.cluster_variable((1,0)).is_homogeneous() + True sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) sage: x.is_homogeneous() False """ return len(self.homogeneous_components()) == 1 -def homogeneous_components(self): #READY +def homogeneous_components(self): r""" Return a dictionary of the homogeneous components of ``self``. @@ -302,9 +303,9 @@ def homogeneous_components(self): #READY class ClusterAlgebraSeed(SageObject): - def __init__(self, B, C, G, parent, **kwargs): # READY + def __init__(self, B, C, G, parent, **kwargs): r""" - A seed in a cluster algebra. + A seed in a Cluster Algebra. INPUT: @@ -335,7 +336,6 @@ def __init__(self, B, C, G, parent, **kwargs): # READY sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] - """ self._B = copy(B) self._C = copy(C) @@ -343,7 +343,7 @@ def __init__(self, B, C, G, parent, **kwargs): # READY self._parent = parent self._path = kwargs.get('path', []) - def __copy__(self): # READY + def __copy__(self): r""" Return a copy of ``self``. @@ -364,7 +364,7 @@ def __copy__(self): # READY other._path = copy(self._path) return other - def __eq__(self, other): # READY + def __eq__(self, other): r""" Test equality of two seeds. @@ -388,16 +388,22 @@ def __eq__(self, other): # READY sage: S.mutate(2) sage: S == A.current_seed() True + + sage: B = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: S = B.current_seed() + sage: S.mutate(0) + sage: S == B.current_seed() + True """ return type(self) == type(other) and self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) - def __contains__(self, element): # READY + def __contains__(self, element): r""" Test whether ``element`` belong to ``self`` INPUT: - - ``element`` -- either a g-vector or and element of :meth:`parent` + - ``element`` -- either a g-vector or an element of :meth:`parent` EXAMPLES:: sage: A = ClusterAlgebra(['A',3]) @@ -416,7 +422,7 @@ def __contains__(self, element): # READY cluster = self.g_vectors() return element in cluster - def _repr_(self): # READY + def _repr_(self): r""" Return the string representation of ``self``. @@ -437,7 +443,7 @@ def _repr_(self): # READY else: return "The seed of a %s obtained from the initial by mutating along the sequence %s"%(str(self.parent())[2:],str(self._path)) - def parent(self): # READY + def parent(self): r""" Return the parent of ``self``. @@ -449,13 +455,13 @@ def parent(self): # READY """ return self._parent - def depth(self): # READY + def depth(self): r""" - Retun the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. + Return the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. WARNING: This is the length of the mutation sequence returned by - :meth:`path_from_initial_seed` which needs not be the shortest + :meth:`path_from_initial_seed` which need not be the shortest possible. EXAMPLES:: @@ -474,7 +480,7 @@ def depth(self): # READY """ return len(self._path) - def path_from_initial_seed(self): # READY + def path_from_initial_seed(self): r""" Return a mutation sequence from the initial seed of :meth:`parent` to ``self``. @@ -499,7 +505,7 @@ def path_from_initial_seed(self): # READY """ return copy(self._path) - def b_matrix(self): # READY + def b_matrix(self): r""" Return the exchange matrix of ``self``. @@ -514,7 +520,7 @@ def b_matrix(self): # READY """ return copy(self._B) - def c_matrix(self): # READY + def c_matrix(self): r""" Return the matrix whose columns are the c-vectors of ``self``. @@ -529,7 +535,7 @@ def c_matrix(self): # READY """ return copy(self._C) - def c_vector(self, j): # READY + def c_vector(self, j): r""" Return the j-th c-vector of ``self``. @@ -543,10 +549,15 @@ def c_vector(self, j): # READY sage: S = A.initial_seed() sage: S.c_vector(0) (1, 0, 0) + sage: S.mutate(0) + sage: S.c_vector(0) + (-1, 0, 0) + sage: S.c_vector(1) + (1, 1, 0) """ return tuple(self._C.column(j)) - def c_vectors(self): # READY + def c_vectors(self): r""" Return all the c-vectors of ``self``. @@ -559,7 +570,7 @@ def c_vectors(self): # READY """ return map(tuple, self._C.columns()) - def g_matrix(self): # READY + def g_matrix(self): r""" Return the matrix whose columns are the g-vectors of ``self``. @@ -574,7 +585,7 @@ def g_matrix(self): # READY """ return copy(self._G) - def g_vector(self, j): # READY + def g_vector(self, j): r""" Return the j-th g-vector of ``self``. @@ -591,7 +602,7 @@ def g_vector(self, j): # READY """ return tuple(self._G.column(j)) - def g_vectors(self): # READY + def g_vectors(self): r""" Return all the g-vectors of ``self``. @@ -604,7 +615,7 @@ def g_vectors(self): # READY """ return map(tuple, self._G.columns()) - def F_polynomial(self, j): # READY + def F_polynomial(self, j): r""" Return the j-th F-polynomial of ``self``. @@ -621,7 +632,7 @@ def F_polynomial(self, j): # READY """ return self.parent().F_polynomial(self.g_vector(j)) - def F_polynomials(self): # READY + def F_polynomials(self): r""" Return all the F-polynomials of ``self``. @@ -634,7 +645,7 @@ def F_polynomials(self): # READY """ return [self.parent().F_polynomial(g) for g in self.g_vectors()] - def cluster_variable(self, j): # READY + def cluster_variable(self, j): r""" Return the j-th cluster variable of ``self``. @@ -648,10 +659,13 @@ def cluster_variable(self, j): # READY sage: S = A.initial_seed() sage: S.cluster_variable(0) x0 + sage: S.mutate(0) + sage: S.cluster_variable(0) + (x1 + 1)/x0 """ return self.parent().cluster_variable(self.g_vector(j)) - def cluster_variables(self): # READY + def cluster_variables(self): r""" Return all the cluster variables of ``self``. @@ -665,14 +679,14 @@ def cluster_variables(self): # READY return [self.parent().cluster_variable(g) for g in self.g_vectors()] @_mutation_parse - def mutate(self, k, mutating_F=True): # READY + def mutate(self, k, mutating_F=True): r""" Mutate ``self``. INPUT: - - ``mutating_F`` -- bool (default True) whether to compute also - F-polynomials. While knowing F-polynomials is essential to computing + - ``mutating_F`` -- bool (default True) whether to compute F-polynomials + also. While knowing F-polynomials is essential to computing cluster variables, the process of mutating them is quite slow. If you care only about combinatorial data like g-vectors and c-vectors, setting ``mutating_F=False`` yields significant benefits in terms of @@ -735,7 +749,7 @@ def mutate(self, k, mutating_F=True): # READY # compute new B-matrix self._B.mutate(k) - def _mutated_F(self, k, old_g_vector): # READY + def _mutated_F(self, k, old_g_vector): r""" Compute new F-polynomial obtained by mutating in direction ``k``. @@ -1388,12 +1402,30 @@ def coefficient_names(self): # READY EXAMPLES:: + sage: A = ClusterAlgebra(['A',3]) + sage: A.coefficient_names() + () sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) sage: A.coefficient_names() ('y0', 'y1') """ return self.variable_names()[self.rk():] + def initial_cluster_variable(self, j): + r""" + Return the j-th initial cluster variable of ``self``. + + INPUT: + + - ``j`` -- an integer in ``range(self.parent().rk())`` + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.initial_cluster_variable(0) + x0 + """ + return self.retract(self.ambient().gen(j)) def initial_cluster_variables(self): # READY r""" @@ -1559,7 +1591,7 @@ def cluster_fan(self, depth=infinity): INPUT: - - ``depth`` -- (default ``infinity``): the maximum depth at which to comute. + - ``depth`` -- (default ``infinity``): the maximum depth at which to compute. EXAMPLES:: From 15064c35a6df346539d6f69b974731f247f81e73 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 10:19:49 +0200 Subject: [PATCH 119/191] More documentation edits --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 9a3f38299bc..78e48d0f321 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1614,8 +1614,8 @@ def mutate_initial(self, k): This function computes data for the new algebra from known data for the old algebra using [NZ12]_ equation (4.2) for g-vectors, and - [FZ07]_ equation (6.21) for F-polynomials. The exponent h in the - formula for F-polynomials is -min(0,old_g_vect[k]) due to [NZ12]_ + [FZ07]_ equation (6.21) for F-polynomials. The exponent ``h`` in the + formula for F-polynomials is ``-min(0,old_g_vect[k])`` due to [NZ12]_ Proposition 4.2. EXAMPLES:: From c1fdef325d0ddb552d79491441a1e52657110c8a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 10:43:50 +0200 Subject: [PATCH 120/191] Added coefficient method. Reverted changes to g_vector for speed considerations --- src/sage/algebras/cluster_algebra.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 78e48d0f321..02e2f8a47c6 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -248,8 +248,9 @@ def g_vector(self): #READY sage: A.cluster_variable((1,0)).g_vector() == (1,0) True """ - if self.is_homogeneous(): - return self.homogeneous_components().keys()[0] + components = self.homogeneous_components() + if len(components) == 1: + return components.keys()[0] else: raise ValueError("This element is not homogeneous.") @@ -1381,6 +1382,25 @@ def gens(self): # READY """ return map(self.retract, self.ambient().gens()) + def coefficient(self, j): # READY + r""" + Return the j-th coefficient of ``self``. + + INPUT: + + - ``j`` -- an integer: the index of the coefficient to return. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A.coefficient(0) + y0 + """ + if isinstance(self.base(), LaurentPolynomialRing_generic): + return self.retract(self.base().gen(j)) + else: + raise ValueError("generator not defined") + def coefficients(self): # READY r""" Return the list of coefficients of ``self``. From f5eae26543ab70e89c721e5799911ee63657d34e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 11:11:22 +0200 Subject: [PATCH 121/191] Removed useless copy from seed --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 02e2f8a47c6..7bfabbb68ce 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1555,7 +1555,7 @@ def seeds(self, **kwargs): # READY # we got a new seed gets_bigger = True # next round do not mutate back to sd and do commuting mutations only in directions j > i - new_directions = [ j for j in copy(allowed_dirs) if j > i or new_sd.b_matrix()[j,i] != 0 ] + new_directions = [ j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] yield new_sd # we went one step deeper From 11c25d73a778146e4a8455b8882c601c11d06e2e Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 16:15:00 +0200 Subject: [PATCH 122/191] More docs fixes --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7bfabbb68ce..c5602eb7b5e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1484,9 +1484,9 @@ def seeds(self, **kwargs): # READY F-polynomials; for speed considerations you may want to disable this. - ``allowed_directions`` -- a tuple of integers (default - ``range(self.rk())``: the directions in which to mutate. + ``range(self.rk())``): the directions in which to mutate. - - ``depth`` -- the maximum depth at which to stop searching. + - ``depth`` -- (defaulf ``infinity``): the maximum depth at which to stop searching. ALGORITHM: From 8bac8aff2f0ca6fea50ee45455cd55073331671d Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 10 Aug 2016 11:22:54 -0400 Subject: [PATCH 123/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c5602eb7b5e..54ae8cf1312 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -319,7 +319,7 @@ def __init__(self, B, C, G, parent, **kwargs): - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the seed belongs; - - ``path`` -- list (default: []) the mutation sequence from the initial + - ``path`` -- list (default []) the mutation sequence from the initial seed of ``parent`` to `self`` WARNING: @@ -686,6 +686,9 @@ def mutate(self, k, mutating_F=True): INPUT: + - ``k`` -- an integer in ``range(self.parent().rk())``: the direction + in which we are mutating + - ``mutating_F`` -- bool (default True) whether to compute F-polynomials also. While knowing F-polynomials is essential to computing cluster variables, the process of mutating them is quite slow. If you @@ -801,7 +804,7 @@ class ClusterAlgebra(Parent): def __init__(self, data, **kwargs): # READY r""" - A cluster algebra. + A Cluster Algebra. INPUT: @@ -814,18 +817,18 @@ def __init__(self, data, **kwargs): # READY - ``cluster_variable_prefix`` -- string (default 'x'); it needs to be a valid variable name. - - ``cluster_variable_names`` -- a list of strings. Superseedes + - ``cluster_variable_names`` -- a list of strings. Supersedes ``cluster_variable_prefix``. Each element needs to be a valid variable name. - ``coefficient_prefix`` -- string (default 'y'); it needs to be a valid variable name. - - ``coefficient_names`` -- a list of strings. Superseedes + - ``coefficient_names`` -- a list of strings. Supersedes ``cluster_variable_prefix``. Each element needs to be a valid variable name. - - ``principal_coefficients`` -- bool (default: False). Superseedes any + - ``principal_coefficients`` -- bool (default False). Supersedes any coefficient defined by ``data``. EXAMPLES:: @@ -841,10 +844,16 @@ def __init__(self, data, **kwargs): # READY [x0, x1, y0, y1] sage: A = ClusterAlgebra(['A',2], principal_coefficients=True, coefficient_prefix='x'); A.gens() [x0, x1, x2, x3] + sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, cluster_variable_names=['a','b','c']); A.gens() + [a, b, c, y0, y1, y2] + sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, cluster_variable_names=['a','b']) + Traceback (most recent call last): + ... + ValueError: cluster_variable_names should be a list of 3 valid variable names ALGORITHM: - The implementation is mainly based on [FZ07]_ and [NZ12]_. + The implementation is mainly based on [FZ07]_ and [NZ12]_. REFERENCES: @@ -1630,6 +1639,9 @@ def mutate_initial(self, k): INPUT: + - ``k`` -- an integer in ``range(self.parent().rk())``: the direction + in which we are mutating + ALGORITHM: This function computes data for the new algebra from known data for From 39ec64b03f985063d96c590a36cbae673547d9ef Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 10 Aug 2016 11:43:49 -0400 Subject: [PATCH 124/191] Doc edits, fixed __copy__ for ClusterAlgebra --- src/sage/algebras/cluster_algebra.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 54ae8cf1312..c803fcac4ad 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -802,7 +802,7 @@ class ClusterAlgebra(Parent): Element = ClusterAlgebraElement - def __init__(self, data, **kwargs): # READY + def __init__(self, data, **kwargs): r""" A Cluster Algebra. @@ -850,6 +850,12 @@ def __init__(self, data, **kwargs): # READY Traceback (most recent call last): ... ValueError: cluster_variable_names should be a list of 3 valid variable names + sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b','c']); A.gens() + [x0, x1, x2, a, b, c] + sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b']); A.gens() + Traceback (most recent call last): + ... + ValueError: coefficient_names should be a list of 3 valid variable names ALGORITHM: @@ -940,7 +946,7 @@ def __init__(self, data, **kwargs): # READY embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) - def __copy__(self): # READY + def __copy__(self): r""" Return a copy of ``self``. @@ -952,6 +958,14 @@ def __copy__(self): # READY True sage: A2 is not A1 True + + sage: S1 = A1.current_seed() + sage: S2 = A2.current_seed() + sage: S1 == S2 + True + sage: S1.mutate(0) + sage: S1 == S2 + False """ n = self.rk() cv_names = self.initial_cluster_variable_names() @@ -960,7 +974,7 @@ def __copy__(self): # READY coefficient_names=coeff_names, scalars=self.scalars()) other._F_poly_dict = copy(self._F_poly_dict) other._path_dict = copy(self._path_dict) - S = self.current_seed() + S = copy(self.current_seed()) S._parent = other other.set_current_seed(S) return other From 1ee2d46b23ae70834cb7cb9426fc4c7ec33c53a3 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 17:57:55 +0200 Subject: [PATCH 125/191] Reverting change to docs of mutate\* --- src/sage/algebras/cluster_algebra.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c803fcac4ad..5e0930823bc 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -686,9 +686,6 @@ def mutate(self, k, mutating_F=True): INPUT: - - ``k`` -- an integer in ``range(self.parent().rk())``: the direction - in which we are mutating - - ``mutating_F`` -- bool (default True) whether to compute F-polynomials also. While knowing F-polynomials is essential to computing cluster variables, the process of mutating them is quite slow. If you @@ -1653,9 +1650,6 @@ def mutate_initial(self, k): INPUT: - - ``k`` -- an integer in ``range(self.parent().rk())``: the direction - in which we are mutating - ALGORITHM: This function computes data for the new algebra from known data for From 051e5f1c1bcb91206e195334b6ac32cfa84badfd Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 18:41:47 +0200 Subject: [PATCH 126/191] Shuffled references to top --- src/sage/algebras/cluster_algebra.py | 37 +++++++++++++--------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 5e0930823bc..752cc439e69 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,10 +1,22 @@ r""" Cluster algebras -Implementation of cluster algebras as an algebra using mainly structural theorems from CA IV +Implementation of cluster algebras as an algebra using mainly structural theorems from [FZ07]_ TODO: We should write a nice paragraph here. +REFERENCES: + +.. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. Coefficients", + Compos. Math. 143 (2007), no. 1, 112-164. + +.. [LLZ14] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 + cluster algebras", Selecta Math. 20 (2014), 57-82. + +.. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in cluster + algebras', Algebraic groups and quantum groups, Contemp. Math., vol. 565, + Amer. Math. Soc., Providence, RI, 2012, pp. 217-226. + AUTHORS: - Dylan Rupel (2015-06-15): initial version @@ -828,6 +840,10 @@ def __init__(self, data, **kwargs): - ``principal_coefficients`` -- bool (default False). Supersedes any coefficient defined by ``data``. + ALGORITHM: + + The implementation is mainly based on [FZ07]_ and [NZ12]_. + EXAMPLES:: sage: B = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) @@ -853,20 +869,6 @@ def __init__(self, data, **kwargs): Traceback (most recent call last): ... ValueError: coefficient_names should be a list of 3 valid variable names - - ALGORITHM: - - The implementation is mainly based on [FZ07]_ and [NZ12]_. - - REFERENCES: - - .. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. - Coefficients", Compos. Math. 143 (2007), no. 1, 112-164. - - .. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in - cluster algebras', Algebraic groups and quantum groups, Contemp. - Math., vol. 565, Amer. Math. Soc., Providence, RI, 2012, pp. - 217-226. """ # Temporary variables Q = ClusterQuiver(data) @@ -1769,11 +1771,6 @@ def greedy_element(self, d_vector): This implements greedy elements of a rank 2 cluster algebra from [LLZ14]_ equation (1.5). - REFERENCES: - - .. [LLZ14] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 - cluster algebras", Selecta Math. 20 (2014), 57-82. - EXAMPLES:: sage: A = ClusterAlgebra(['A',[1,1],1]) From 18383c78dc9ad6f28e531f926dacbba9d5c34e68 Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 10 Aug 2016 13:00:27 -0400 Subject: [PATCH 127/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 752cc439e69..358f2abbb5c 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -978,7 +978,7 @@ def __copy__(self): other.set_current_seed(S) return other - def __eq__(self, other): # READY + def __eq__(self, other): r""" Test equality of two cluster algebras. @@ -1007,7 +1007,7 @@ def __eq__(self, other): # READY """ return type(self) == type(other) and self._B0 == other._B0 and self.ambient() == other.ambient() - def _repr_(self): # READY + def _repr_(self): r""" Return the string representation of ``self``. @@ -1025,7 +1025,7 @@ def _repr_(self): # READY coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names)>0 else "") return "A Cluster Algebra with cluster variable" + var_names + coeff + "over " + repr(self.scalars()) - def _an_element_(self): # READY + def _an_element_(self): r""" Return an element of ``self``. @@ -1044,7 +1044,7 @@ def _coerce_map_from_(self, other): # READY If ``other`` is an instance of :class:`ClusterAlgebra` then allow coercion if ``other.ambient()`` can be coerced into - ``self.ambient()`` and other can be obtained from ``self`` by + ``self.ambient()`` and ``other`` can be obtained from ``self`` by permuting variables and coefficients and/or freezing some initial cluster variables. @@ -1067,6 +1067,19 @@ def _coerce_map_from_(self, other): # READY sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) True + + sage: B3 = B1.matrix_from_columns([1,2,3]) + sage: G = PermutationGroup(['(1,4,3,2)']) + sage: B3.permute_rows(G.gen(0)) + sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1','x2','x3'], coefficient_names=['x0','x4','x5']) + sage: A1.has_coerce_map_from(A3) + True + sage: g = A1.coerce_map_from(A3) + sage: A3.find_g_vector((1,-2,2)) + [1, 2, 1, 0] + sage: S = A1.initial_seed(); S.mutate([1, 2, 1,0]) + sage: S.cluster_variable(2) == g(A3.cluster_variable((1,-2,2)) + True """ if isinstance(other, ClusterAlgebra): gen_s = self.gens() From 43232ff9ffca246b735fdb8d952c4007bb32b82f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 19:54:46 +0200 Subject: [PATCH 128/191] Fixed bug in _coerce_map_from_ ???? --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 358f2abbb5c..fb16338c173 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1087,7 +1087,7 @@ def _coerce_map_from_(self, other): # READY if len(gen_s) == len(gen_o): f = self.ambient().coerce_map_from(other.ambient()) if f is not None: - perm = Permutation([ gen_s.index(self(f(v)))+1 for v in gen_o ]).inverse() + perm = Permutation([ gen_s.index(self(f(v)))+1 for v in gen_o ]) n = self.rk() m = len(perm) - n M = self._B0[n:,:] From 515c59b118c4cb7f703599ea75f6547c424ec147 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 10 Aug 2016 20:10:25 +0200 Subject: [PATCH 129/191] Added example to _coerce_map_from_ --- src/sage/algebras/cluster_algebra.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index fb16338c173..d8ceaff9ff8 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1067,9 +1067,8 @@ def _coerce_map_from_(self, other): # READY sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) True - sage: B3 = B1.matrix_from_columns([1,2,3]) - sage: G = PermutationGroup(['(1,4,3,2)']) + sage: G = PermutationGroup(['(1,2,3,4)']) sage: B3.permute_rows(G.gen(0)) sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1','x2','x3'], coefficient_names=['x0','x4','x5']) sage: A1.has_coerce_map_from(A3) @@ -1077,8 +1076,10 @@ def _coerce_map_from_(self, other): # READY sage: g = A1.coerce_map_from(A3) sage: A3.find_g_vector((1,-2,2)) [1, 2, 1, 0] - sage: S = A1.initial_seed(); S.mutate([1, 2, 1,0]) - sage: S.cluster_variable(2) == g(A3.cluster_variable((1,-2,2)) + sage: map(lambda x: x-1,map(G.gen(0),map(lambda x: x+1,[1, 2, 1, 0]))) + [2, 3, 2, 1] + sage: S = A1.initial_seed(); S.mutate([2, 3, 2, 1]) + sage: S.cluster_variable(1) == g(A3.cluster_variable((1,-2,2))) True """ if isinstance(other, ClusterAlgebra): From 679a70932b518952512b19077e7ee2117f836c6f Mon Sep 17 00:00:00 2001 From: drupel Date: Wed, 10 Aug 2016 21:59:33 -0400 Subject: [PATCH 130/191] Doc edits; removed all ready-s --- src/sage/algebras/cluster_algebra.py | 161 +++++++++++++++------------ 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d8ceaff9ff8..6da9bda99da 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -250,7 +250,7 @@ def _repr_(self): # Methods not always defined #### -def g_vector(self): #READY +def g_vector(self): r""" Return the g-vector of ``self``. @@ -1004,6 +1004,12 @@ def __eq__(self, other): sage: A1.current_seed().mutate([0,1,2]) sage: A1 == A2 True + sage: A3 = ClusterAlgebra(['A',3],principal_coefficients=True) + sage: A1 == A3 + False + sage: B = ClusterAlgebra(['B',3]) + sage: A1 == B + False """ return type(self) == type(other) and self._B0 == other._B0 and self.ambient() == other.ambient() @@ -1036,7 +1042,7 @@ def _an_element_(self): """ return self.current_seed().cluster_variable(0) - def _coerce_map_from_(self, other): # READY + def _coerce_map_from_(self, other): r""" Test whether there is a coercion from ``other`` to ``self``. @@ -1099,7 +1105,7 @@ def _coerce_map_from_(self, other): # READY # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) - def rk(self): # READY + def rk(self): r""" Return the rank of ``self`` i.e. the number of cluster variables in any seed. @@ -1112,7 +1118,7 @@ def rk(self): # READY """ return self._n - def current_seed(self): # READY + def current_seed(self): r""" Return the current seed of ``self``. @@ -1124,7 +1130,7 @@ def current_seed(self): # READY """ return self._seed - def set_current_seed(self, seed): # READY + def set_current_seed(self, seed): r""" Set the value reported by :meth:`current_seed` to ``seed`` if it makes sense. @@ -1148,9 +1154,9 @@ def set_current_seed(self, seed): # READY else: raise ValueError("This is not a seed in this cluster algebra.") - def contains_seed(self, seed): # READY + def contains_seed(self, seed): r""" - Test if ``seed`` is a seed in ``self``. + Test if ``seed`` is a seed of ``self``. INPUT: @@ -1168,7 +1174,7 @@ def contains_seed(self, seed): # READY computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed - def reset_current_seed(self): # READY + def reset_current_seed(self): r""" Reset the value reported by :meth:`current_seed` to :meth:`initial_seed`. @@ -1184,7 +1190,7 @@ def reset_current_seed(self): # READY """ self._seed = self.initial_seed() - def initial_seed(self): # READY + def initial_seed(self): r""" Return the initial seed of ``self`` @@ -1198,7 +1204,7 @@ def initial_seed(self): # READY I = identity_matrix(n) return ClusterAlgebraSeed(self.b_matrix(), I, I, self) - def b_matrix(self): # READY + def b_matrix(self): r""" Return the initial exchange matrix of ``self``. @@ -1212,7 +1218,7 @@ def b_matrix(self): # READY n = self.rk() return copy(self._B0[:n,:]) - def g_vectors_so_far(self): # READY + def g_vectors_so_far(self): r""" Return a list of the g-vectors of cluster variables encountered so far. @@ -1225,7 +1231,7 @@ def g_vectors_so_far(self): # READY """ return self._path_dict.keys() - def cluster_variables_so_far(self): # READY + def cluster_variables_so_far(self): r""" Return a list of the cluster variables encountered so far. @@ -1238,9 +1244,38 @@ def cluster_variables_so_far(self): # READY """ return map(self.cluster_variable, self.g_vectors_so_far()) - def F_polynomials_so_far(self): # READY + @cached_method(key=lambda a,b: tuple(b)) + def cluster_variable(self, g_vector): r""" - Return a list of the cluster variables encountered so far. + Return the cluster variable with g-vector ``g_vector`` if it has been found. + + INPUT: + + - ``g_vector`` -- a tuple: the g-vector of the cluster variable to return. + + ALGORITHM: + + This function computes cluster variables from their g-vectors and + and F-polynomials using the "separation of additions" formula of + Theorem 3.7 in [FZ07]_. + + EXAMPLE:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.initial_seed().mutate(0) + sage: A.cluster_variable((-1,1)) + (x1 + 1)/x0 + """ + g_vector = tuple(g_vector) + F = self.F_polynomial(g_vector) + F_std = F.subs(self._yhat) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) + F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] + return self.retract(g_mon*F_std*F_trop) + + def F_polynomials_so_far(self): + r""" + Return a list of the F-polynomials encountered so far. EXAMPLES:: @@ -1251,7 +1286,7 @@ def F_polynomials_so_far(self): # READY """ return self._F_poly_dict.values() - def F_polynomial(self, g_vector): # READY + def F_polynomial(self, g_vector): r""" Return the F-polynomial with g-vector ``g_vector`` if it has been found. @@ -1288,36 +1323,7 @@ def F_polynomial(self, g_vector): # READY else: raise KeyError("The g-vector %s has not been found yet."%str(g_vector)) - @cached_method(key=lambda a,b: tuple(b) ) - def cluster_variable(self, g_vector): # READY - r""" - Return the cluster variable with g-vector ``g_vector`` if it has been found. - - INPUT: - - - ``g_vector`` -- a tuple: the g-vector of the cluster variable to return. - - ALGORITHM: - - This function computes cluster variables from their g-vectors and - and F-polynomials using the "separation of additions" formula of - Theorem 3.7 in [FZ07]_. - - EXAMPLE:: - - sage: A = ClusterAlgebra(['A',2]) - sage: A.initial_seed().mutate(0) - sage: A.cluster_variable((-1,1)) - (x1 + 1)/x0 - """ - g_vector = tuple(g_vector) - F = self.F_polynomial(g_vector) - F_std = F.subs(self._yhat) - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) - F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] - return self.retract(g_mon*F_std*F_trop) - - def find_g_vector(self, g_vector, depth=infinity): # READY + def find_g_vector(self, g_vector, depth=infinity): r""" Return a mutation sequence to obtain a seed containing the g-vector ``g_vector`` from the initial seed. @@ -1333,7 +1339,7 @@ def find_g_vector(self, g_vector, depth=infinity): # READY ``g_vector`` otherwise it returns ``None``. If the exploring iterator stops it means that the algebra is of finite type and ``g_vector`` is not the g-vector of any cluster variable. In this - case the fuction resets the iterator and raises an error. + case the function resets the iterator and raises an error. EXAMPLES:: @@ -1341,6 +1347,11 @@ def find_g_vector(self, g_vector, depth=infinity): # READY sage: A.find_g_vector((-2, 3), depth=2) sage: A.find_g_vector((-2, 3), depth=3) [0, 1, 0] + sage: A.find_g_vector((1, 1), depth=3) + sage: A.find_g_vector((1, 1), depth=4) + Traceback (most recent call last): + ... + ValueError: (1, 1) is not the g-vector of any cluster variable of a Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring. """ g_vector = tuple(g_vector) while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: @@ -1352,10 +1363,10 @@ def find_g_vector(self, g_vector, depth=infinity): # READY # all the seeds of self and did not find g_vector. # Do some house cleaning before failing self.reset_exploring_iterator() - raise ValueError("%s is not the g-vector of any cluster variable of %s."%(str(g_vector),str(self))) + raise ValueError("%s is not the g-vector of any cluster variable of a %s."%(str(g_vector),str(self)[2:])) return copy(self._path_dict.get(g_vector,None)) - def ambient(self): # READY + def ambient(self): r""" Return the Laurent polynomial ring containing ``self``. @@ -1367,9 +1378,9 @@ def ambient(self): # READY """ return self._ambient - def scalars(self): # READY + def scalars(self): r""" - Return the scalars on which ``self`` is defined. + Return the scalars over which ``self`` is defined. EXAMPLES:: @@ -1379,7 +1390,7 @@ def scalars(self): # READY """ return self.base().base() - def lift(self, x): # READY + def lift(self, x): r""" Return ``x`` as an element of :meth:`ambient`. @@ -1392,7 +1403,7 @@ def lift(self, x): # READY """ return self.ambient()(x.value) - def retract(self, x): # READY + def retract(self, x): r""" Return ``x`` as an element of ``self``. @@ -1406,7 +1417,7 @@ def retract(self, x): # READY """ return self(x) - def gens(self): # READY + def gens(self): r""" Return the list of initial cluster variables and coefficients of ``self``. @@ -1415,10 +1426,13 @@ def gens(self): # READY sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) sage: A.gens() [x0, x1, y0, y1] + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True,coefficient_prefix='x') + sage: A.gens() + [x0, x1, x2, x3] """ return map(self.retract, self.ambient().gens()) - def coefficient(self, j): # READY + def coefficient(self, j): r""" Return the j-th coefficient of ``self``. @@ -1437,7 +1451,7 @@ def coefficient(self, j): # READY else: raise ValueError("generator not defined") - def coefficients(self): # READY + def coefficients(self): r""" Return the list of coefficients of ``self``. @@ -1446,13 +1460,16 @@ def coefficients(self): # READY sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) sage: A.coefficients() [y0, y1] + sage: B = ClusterAlgebra(['B',2]) + sage: B.coefficients() + [] """ if isinstance(self.base(), LaurentPolynomialRing_generic): return map(self.retract, self.base().gens()) else: return [] - def coefficient_names(self): # READY + def coefficient_names(self): r""" Return the list of coefficient names. @@ -1461,9 +1478,12 @@ def coefficient_names(self): # READY sage: A = ClusterAlgebra(['A',3]) sage: A.coefficient_names() () - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - sage: A.coefficient_names() + sage: B = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: B.coefficient_names() ('y0', 'y1') + sage: C = ClusterAlgebra(['C',3],coefficient_prefix='x') + sage: C.coefficient_names() + ('x3', 'x4', 'x5') """ return self.variable_names()[self.rk():] @@ -1483,7 +1503,7 @@ def initial_cluster_variable(self, j): """ return self.retract(self.ambient().gen(j)) - def initial_cluster_variables(self): # READY + def initial_cluster_variables(self): r""" Return the list of initial cluster variables of ``self``. @@ -1495,19 +1515,22 @@ def initial_cluster_variables(self): # READY """ return map(self.retract, self.ambient().gens()[:self.rk()]) - def initial_cluster_variable_names(self): # READY + def initial_cluster_variable_names(self): r""" Return the list of initial variable names. EXAMPLES:: - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) sage: A.initial_cluster_variable_names() ('x0', 'x1') + sage: B = ClusterAlgebra(['B',2],cluster_variable_prefix='a') + sage: B.initial_cluster_variable_names() + ('a0','a1') """ return self.variable_names()[:self.rk()] - def seeds(self, **kwargs): # READY + def seeds(self, **kwargs): r""" Return an iterator running over seeds of ``self``. @@ -1516,8 +1539,8 @@ def seeds(self, **kwargs): # READY - ``from_current_seed`` -- bool (default False): whether to start the iterator from :meth:`current_seed` or :meth:`initial_seed`. - - ``mutating_F`` -- bool (default True): wheter to compute also - F-polynomials; for speed considerations you may want to disable this. + - ``mutating_F`` -- bool (default True): whether to compute F-polynomials also; + for speed considerations you may want to disable this. - ``allowed_directions`` -- a tuple of integers (default ``range(self.rk())``): the directions in which to mutate. @@ -1597,7 +1620,7 @@ def seeds(self, **kwargs): # READY # we went one step deeper depth_counter += 1 - def reset_exploring_iterator(self, mutating_F=True): # READY + def reset_exploring_iterator(self, mutating_F=True): r""" Reset the iterator used to explore ``self``. @@ -1619,7 +1642,7 @@ def reset_exploring_iterator(self, mutating_F=True): # READY self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 - def explore_to_depth(self, depth): # READY + def explore_to_depth(self, depth): r""" Explore the exchange graph of ``self`` up to distance ``depth`` from the initial seed. @@ -1775,11 +1798,11 @@ def theta_basis_element(self, g_vector): def greedy_element(self, d_vector): r""" - Return the greedy element with d-vector ``d_vector``. + Return the greedy element with denominator vector ``d_vector``. INPUT - - ``d_vector`` -- tuple of 2 integers: the d-vector of the element to compute. + - ``d_vector`` -- tuple of 2 integers: the denominator vector of the element to compute. ALGORITHM: @@ -1809,7 +1832,7 @@ def greedy_element(self, d_vector): output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) return self.retract(x1**(-a1) * x2**(-a2) * output) -def _greedy_coefficient(self,d_vector,p,q): # READY +def _greedy_coefficient(self,d_vector,p,q): r""" Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with denominator vector ``d_vector``. From 2f97a1d9a1cf74e6a4b2e55d5f2f7f4027657bf0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 11 Aug 2016 15:18:05 +0200 Subject: [PATCH 131/191] Missing quote --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 6da9bda99da..c5c4c60157a 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -332,7 +332,7 @@ def __init__(self, B, C, G, parent, **kwargs): seed belongs; - ``path`` -- list (default []) the mutation sequence from the initial - seed of ``parent`` to `self`` + seed of ``parent`` to ``self``. WARNING: From ce92496f344aa17affba87973897855c021fe0e7 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 11 Aug 2016 22:01:48 -0400 Subject: [PATCH 132/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c5c4c60157a..ec685a04cca 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -980,7 +980,7 @@ def __copy__(self): def __eq__(self, other): r""" - Test equality of two cluster algebras. + Test equality of two Cluster Algebras. INPUT: @@ -990,8 +990,8 @@ def __eq__(self, other): ``self`` and ``other`` are deemed to be equal if they have the same initial exchange matrix and their ambients coincide. In - particular we do not keep track of how much each algebra has been - explored. + particular we do not keep track of how much each Cluster Algebra has + been explored. EXAMPLES:: @@ -1331,7 +1331,7 @@ def find_g_vector(self, g_vector, depth=infinity): - ``g_vector`` -- a tuple: the g-vector to find. - - ``depth`` -- a positive integer: the maximum distance from ``self.current_seed`` to reach. + - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum distance from ``self.current_seed`` to reach. OUTPUT: @@ -1517,7 +1517,7 @@ def initial_cluster_variables(self): def initial_cluster_variable_names(self): r""" - Return the list of initial variable names. + Return the list of initial cluster variable names. EXAMPLES:: @@ -1542,10 +1542,10 @@ def seeds(self, **kwargs): - ``mutating_F`` -- bool (default True): whether to compute F-polynomials also; for speed considerations you may want to disable this. - - ``allowed_directions`` -- a tuple of integers (default - ``range(self.rk())``): the directions in which to mutate. + - ``allowed_directions`` -- a tuple of integers (default ``range(self.rk())``): the + directions in which to mutate. - - ``depth`` -- (defaulf ``infinity``): the maximum depth at which to stop searching. + - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum depth at which to stop searching. ALGORITHM: @@ -1648,7 +1648,7 @@ def explore_to_depth(self, depth): INPUT: - - ``depth`` -- the maximum depth at which to stop searching. + - ``depth`` -- a positive integer or infinity: the maximum depth at which to stop searching. EXAMPLES:: @@ -1670,7 +1670,7 @@ def cluster_fan(self, depth=infinity): INPUT: - - ``depth`` -- (default ``infinity``): the maximum depth at which to compute. + - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum depth at which to compute. EXAMPLES:: From afcddbad49f6f6a6e4a6a064656743be2859ea40 Mon Sep 17 00:00:00 2001 From: drupel Date: Thu, 11 Aug 2016 22:36:28 -0400 Subject: [PATCH 133/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 41 ++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ec685a04cca..c1c99cdfb10 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,7 +1,7 @@ r""" Cluster algebras -Implementation of cluster algebras as an algebra using mainly structural theorems from [FZ07]_ +Implementation of cluster algebras as an algebra mainly using structural theorems from [FZ07]_ TODO: We should write a nice paragraph here. @@ -199,7 +199,7 @@ def _div_(self, other): r""" Return the quotient of ``self`` and ``other``. - WARNING:: + WARNING: This method returns an element of ``self.parent().ambient()`` rather than an element of ``self.parent()`` because, a priori, @@ -331,7 +331,7 @@ def __init__(self, B, C, G, parent, **kwargs): - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the seed belongs; - - ``path`` -- list (default []) the mutation sequence from the initial + - ``path`` -- a list (default []): the mutation sequence from the initial seed of ``parent`` to ``self``. WARNING: @@ -473,6 +473,7 @@ def depth(self): Return the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. WARNING: + This is the length of the mutation sequence returned by :meth:`path_from_initial_seed` which need not be the shortest possible. @@ -698,7 +699,7 @@ def mutate(self, k, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default True) whether to compute F-polynomials + - ``mutating_F`` -- bool (default True): whether to compute F-polynomials also. While knowing F-polynomials is essential to computing cluster variables, the process of mutating them is quite slow. If you care only about combinatorial data like g-vectors and c-vectors, @@ -817,27 +818,27 @@ def __init__(self, data, **kwargs): INPUT: - - ``data`` -- some data defining a cluster algebra. It can be anything + - ``data`` -- some data defining a cluster algebra: it can be anything that can be parsed by :class:`ClusterQuiver`. - - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra + - ``scalars`` -- a ring (default ZZ): the scalars over which the cluster algebra is defined. - - ``cluster_variable_prefix`` -- string (default 'x'); it needs to be + - ``cluster_variable_prefix`` -- string (default 'x'): it needs to be a valid variable name. - - ``cluster_variable_names`` -- a list of strings. Supersedes + - ``cluster_variable_names`` -- a list of strings: supersedes ``cluster_variable_prefix``. Each element needs to be a valid variable name. - - ``coefficient_prefix`` -- string (default 'y'); it needs to be + - ``coefficient_prefix`` -- string (default 'y'): it needs to be a valid variable name. - - ``coefficient_names`` -- a list of strings. Supersedes + - ``coefficient_names`` -- a list of strings: supersedes ``cluster_variable_prefix``. Each element needs to be a valid variable name. - - ``principal_coefficients`` -- bool (default False). Supersedes any + - ``principal_coefficients`` -- bool (default False): supersedes any coefficient defined by ``data``. ALGORITHM: @@ -865,7 +866,7 @@ def __init__(self, data, **kwargs): ValueError: cluster_variable_names should be a list of 3 valid variable names sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b','c']); A.gens() [x0, x1, x2, a, b, c] - sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b']); A.gens() + sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b']) Traceback (most recent call last): ... ValueError: coefficient_names should be a list of 3 valid variable names @@ -1073,9 +1074,21 @@ def _coerce_map_from_(self, other): sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) True - sage: B3 = B1.matrix_from_columns([1,2,3]) + sage: B3 = B1.matrix_from_columns([1,2,3]); B3 + [ 1 0 0] + [ 0 -1 0] + [ 1 0 1] + [ 0 -2 0] + [ 0 0 0] + [-1 0 0] sage: G = PermutationGroup(['(1,2,3,4)']) - sage: B3.permute_rows(G.gen(0)) + sage: B3.permute_rows(G.gen(0)); B3 + [ 0 -1 0] + [ 1 0 1] + [ 0 -2 0] + [ 1 0 0] + [ 0 0 0] + [-1 0 0] sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1','x2','x3'], coefficient_names=['x0','x4','x5']) sage: A1.has_coerce_map_from(A3) True From 5c1f9341eb3209542ad4991cb083cc2fbc317d5b Mon Sep 17 00:00:00 2001 From: drupel Date: Fri, 12 Aug 2016 00:21:33 -0400 Subject: [PATCH 134/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 88 ++++++++++++++-------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c1c99cdfb10..dcc4a6f0287 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -108,11 +108,11 @@ def _mutation_parse(mutate): if mutate.__name__ == "mutate": doc[0] += r""" - - ``inplace`` -- bool (default True) whether to mutate in place or to return a new object + - ``inplace`` -- bool (default ``True``): whether to mutate in place or to return a new object. """ doc[0] += r""" - - ``direction`` -- in which direction(s) to mutate. It can be + - ``direction`` -- in which direction(s) to mutate, it can be: - an integer in ``range(self.rk())`` to mutate in one direction only; - an iterable of such integers to mutate along a sequence; - a string "sinks" or "sources" to mutate at all sinks or sources simultaneously. @@ -159,7 +159,7 @@ def __init__(self, parent, value): INPUT: - - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the element belongs; + - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the element belongs. - ``value`` -- the value of the element. @@ -185,7 +185,7 @@ def _add_(self, other): INPUT: - - ``other`` - an element of ``self.parent()`` + - ``other`` - an element of ``self.parent()``. EXAMPLES:: @@ -268,7 +268,7 @@ def g_vector(self): def is_homogeneous(self): r""" - Return ``True`` if ``self`` is an homogeneous element of ``self.parent()``. + Return ``True`` if ``self`` is a homogeneous element of ``self.parent()``. EXAMPLES:: @@ -322,14 +322,14 @@ def __init__(self, B, C, G, parent, **kwargs): INPUT: - - ``B`` -- a skew-symmetrizable integer matrix; + - ``B`` -- a skew-symmetrizable integer matrix. - - ``C`` -- the matrix of c-vectors of ``self``; + - ``C`` -- the matrix of c-vectors of ``self``. - - ``G`` -- the matrix of g-vectors of ``self``; + - ``G`` -- the matrix of g-vectors of ``self``. - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the - seed belongs; + seed belongs. - ``path`` -- a list (default []): the mutation sequence from the initial seed of ``parent`` to ``self``. @@ -383,7 +383,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a :class:`ClusterAlgebraSeed` + - ``other`` -- a :class:`ClusterAlgebraSeed`. ALGORITHM: @@ -412,11 +412,11 @@ def __eq__(self, other): def __contains__(self, element): r""" - Test whether ``element`` belong to ``self`` + Test whether ``element`` belong to ``self``. INPUT: - - ``element`` -- either a g-vector or an element of :meth:`parent` + - ``element`` -- either a g-vector or an element of :meth:`parent`. EXAMPLES:: sage: A = ClusterAlgebra(['A',3]) @@ -555,7 +555,7 @@ def c_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())`` + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the c-vector to return. EXAMPLES:: @@ -605,7 +605,7 @@ def g_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())`` + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the g-vector to return. EXAMPLES:: @@ -635,7 +635,7 @@ def F_polynomial(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())`` + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the F-polynomial to return. EXAMPLES:: @@ -665,7 +665,7 @@ def cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())`` + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the cluster variable to return. EXAMPLES:: @@ -699,7 +699,7 @@ def mutate(self, k, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default True): whether to compute F-polynomials + - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also. While knowing F-polynomials is essential to computing cluster variables, the process of mutating them is quite slow. If you care only about combinatorial data like g-vectors and c-vectors, @@ -770,10 +770,10 @@ def _mutated_F(self, k, old_g_vector): INPUT: - ``k`` -- an integer in ``range(self.parent().rk())``: the direction - in which we are mutating + in which we are mutating. - ``old_g_vector`` -- tuple: the k-th g-vector of ``self`` before - mutating + mutating. NOTE: @@ -821,24 +821,22 @@ def __init__(self, data, **kwargs): - ``data`` -- some data defining a cluster algebra: it can be anything that can be parsed by :class:`ClusterQuiver`. - - ``scalars`` -- a ring (default ZZ): the scalars over which the cluster algebra + - ``scalars`` -- a ring (default ``ZZ``): the scalars over which the cluster algebra is defined. - ``cluster_variable_prefix`` -- string (default 'x'): it needs to be a valid variable name. - - ``cluster_variable_names`` -- a list of strings: supersedes - ``cluster_variable_prefix``. Each element needs to be a valid - variable name. + - ``cluster_variable_names`` -- a list of strings: each element needs + to be a valid variable name; supersedes ``cluster_variable_prefix``. - ``coefficient_prefix`` -- string (default 'y'): it needs to be a valid variable name. - - ``coefficient_names`` -- a list of strings: supersedes - ``cluster_variable_prefix``. Each element needs to be a valid - variable name. + - ``coefficient_names`` -- a list of strings: each element needs + to be a valid variable name; supersedes ``cluster_variable_prefix``. - - ``principal_coefficients`` -- bool (default False): supersedes any + - ``principal_coefficients`` -- bool (default ``False``): supersedes any coefficient defined by ``data``. ALGORITHM: @@ -958,7 +956,6 @@ def __copy__(self): True sage: A2 is not A1 True - sage: S1 = A1.current_seed() sage: S2 = A2.current_seed() sage: S1 == S2 @@ -985,7 +982,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a :class:`ClusterAlgebra` + - ``other`` -- a :class:`ClusterAlgebra`. ALGORITHM: @@ -1120,7 +1117,7 @@ def _coerce_map_from_(self, other): def rk(self): r""" - Return the rank of ``self`` i.e. the number of cluster variables in any seed. + Return the rank of ``self``, i.e. the number of cluster variables in any seed. EXAMPLES:: @@ -1145,11 +1142,11 @@ def current_seed(self): def set_current_seed(self, seed): r""" - Set the value reported by :meth:`current_seed` to ``seed`` if it makes sense. + Set the value reported by :meth:`current_seed` to ``seed``, if it makes sense. INPUT: - - ``seed`` -- an instance of :class:`ClusterAlgebraSeed` + - ``seed`` -- an instance of :class:`ClusterAlgebraSeed`. EXAMPLES:: @@ -1161,6 +1158,11 @@ def set_current_seed(self, seed): sage: A.set_current_seed(S) sage: A.current_seed() == S True + sage: B = ClusterAlgebra(['B',2]) + sage: A.set_current_seed(B.initial_seed()) + Traceback (most recent call last): + ... + ValueError: This is not a seed in this cluster algebra. """ if self.contains_seed(seed): self._seed = seed @@ -1173,7 +1175,7 @@ def contains_seed(self, seed): INPUT: - - ``seed`` -- an instance of :class:`ClusterAlgebraSeed` + - ``seed`` -- an instance of :class:`ClusterAlgebraSeed`. EXAMPLES:: @@ -1205,7 +1207,7 @@ def reset_current_seed(self): def initial_seed(self): r""" - Return the initial seed of ``self`` + Return the initial seed of ``self``. EXAMPLES:: @@ -1269,7 +1271,7 @@ def cluster_variable(self, g_vector): ALGORITHM: This function computes cluster variables from their g-vectors and - and F-polynomials using the "separation of additions" formula of + F-polynomials using the "separation of additions" formula of Theorem 3.7 in [FZ07]_. EXAMPLE:: @@ -1349,8 +1351,8 @@ def find_g_vector(self, g_vector, depth=infinity): OUTPUT: This function returns a list of integers if it can find - ``g_vector`` otherwise it returns ``None``. If the exploring - iterator stops it means that the algebra is of finite type and + ``g_vector``, otherwise it returns ``None``. If the exploring + iterator stops, it means that the algebra is of finite type and ``g_vector`` is not the g-vector of any cluster variable. In this case the function resets the iterator and raises an error. @@ -1393,7 +1395,7 @@ def ambient(self): def scalars(self): r""" - Return the scalars over which ``self`` is defined. + Return the ring of scalars over which ``self`` is defined. EXAMPLES:: @@ -1451,7 +1453,7 @@ def coefficient(self, j): INPUT: - - ``j`` -- an integer: the index of the coefficient to return. + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the coefficient to return. EXAMPLES:: @@ -1506,7 +1508,7 @@ def initial_cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())`` + - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the cluster variable to return. EXAMPLES:: @@ -1549,10 +1551,10 @@ def seeds(self, **kwargs): INPUT: - - ``from_current_seed`` -- bool (default False): whether to start the + - ``from_current_seed`` -- bool (default ``False``): whether to start the iterator from :meth:`current_seed` or :meth:`initial_seed`. - - ``mutating_F`` -- bool (default True): whether to compute F-polynomials also; + - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also; for speed considerations you may want to disable this. - ``allowed_directions`` -- a tuple of integers (default ``range(self.rk())``): the @@ -1639,7 +1641,7 @@ def reset_exploring_iterator(self, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default True): whether to also compute + - ``mutating_F`` -- bool (default ``True``): whether to also compute F-polynomials; for speed considerations you may want to disable this. EXAMPLES:: From 8f66458f69217c0829326788bfadba93fd83bf1b Mon Sep 17 00:00:00 2001 From: drupel Date: Fri, 12 Aug 2016 00:45:21 -0400 Subject: [PATCH 135/191] Doc edits --- src/sage/algebras/cluster_algebra.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index dcc4a6f0287..a71e5c20a7b 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -331,7 +331,7 @@ def __init__(self, B, C, G, parent, **kwargs): - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the seed belongs. - - ``path`` -- a list (default []): the mutation sequence from the initial + - ``path`` -- a list (default ``[]``): the mutation sequence from the initial seed of ``parent`` to ``self``. WARNING: @@ -551,7 +551,7 @@ def c_matrix(self): def c_vector(self, j): r""" - Return the j-th c-vector of ``self``. + Return the ``j``-th c-vector of ``self``. INPUT: @@ -601,7 +601,7 @@ def g_matrix(self): def g_vector(self, j): r""" - Return the j-th g-vector of ``self``. + Return the ``j``-th g-vector of ``self``. INPUT: @@ -631,7 +631,7 @@ def g_vectors(self): def F_polynomial(self, j): r""" - Return the j-th F-polynomial of ``self``. + Return the ``j``-th F-polynomial of ``self``. INPUT: @@ -661,7 +661,7 @@ def F_polynomials(self): def cluster_variable(self, j): r""" - Return the j-th cluster variable of ``self``. + Return the ``j``-th cluster variable of ``self``. INPUT: @@ -824,13 +824,13 @@ def __init__(self, data, **kwargs): - ``scalars`` -- a ring (default ``ZZ``): the scalars over which the cluster algebra is defined. - - ``cluster_variable_prefix`` -- string (default 'x'): it needs to be + - ``cluster_variable_prefix`` -- string (default ``'x'``): it needs to be a valid variable name. - ``cluster_variable_names`` -- a list of strings: each element needs to be a valid variable name; supersedes ``cluster_variable_prefix``. - - ``coefficient_prefix`` -- string (default 'y'): it needs to be + - ``coefficient_prefix`` -- string (default ``'y'``): it needs to be a valid variable name. - ``coefficient_names`` -- a list of strings: each element needs @@ -1449,7 +1449,7 @@ def gens(self): def coefficient(self, j): r""" - Return the j-th coefficient of ``self``. + Return the ``j``-th coefficient of ``self``. INPUT: @@ -1504,7 +1504,7 @@ def coefficient_names(self): def initial_cluster_variable(self, j): r""" - Return the j-th initial cluster variable of ``self``. + Return the ``j``-th initial cluster variable of ``self``. INPUT: From ab97ea87d638025cc0f9821560c4bc103f85428b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 12 Aug 2016 12:36:06 +0200 Subject: [PATCH 136/191] Change how we compute m --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index a71e5c20a7b..1820262817e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1106,8 +1106,8 @@ def _coerce_map_from_(self, other): if f is not None: perm = Permutation([ gen_s.index(self(f(v)))+1 for v in gen_o ]) n = self.rk() - m = len(perm) - n M = self._B0[n:,:] + m = M.nrows() B = block_matrix([[self.b_matrix(),-M.transpose()],[M,matrix(m)]]) B.permute_rows_and_columns(perm,perm) return B.matrix_from_columns(range(other.rk())) == other._B0 From 9e6a89432a0b8351d7052b17560758111d61be27 Mon Sep 17 00:00:00 2001 From: drupel Date: Sat, 13 Aug 2016 00:08:08 -0400 Subject: [PATCH 137/191] Added introductory paragraphs --- src/sage/algebras/cluster_algebra.py | 43 ++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1820262817e..2192f74121e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,9 +1,38 @@ r""" Cluster algebras -Implementation of cluster algebras as an algebra mainly using structural theorems from [FZ07]_ - -TODO: We should write a nice paragraph here. +This file implements cluster algebras using the algebra-element framework. +This implementation mainly utilizes structural theorems from [FZ07]_. + +The class structure has been chosen to closely mirror the mutation process +in the theory of cluster algebras. Thus a cluster algebra is determined by +an initial seed from which all other seeds are computed. Seeds are a separate +class whose parent is an instance of the cluster algebra class. + +The task of mutating seeds is delegated to the seeds themselves since they +carry all the data for mutation, seeds also track a mutation path by which +they can be obtained from the initial seed of their parent. Although cluster +algebras themselves are independent of the choice of initial seed, in order to +maintain consistency of mutation path data in seeds, cluster algebras in this +implementation are considered to be equal only if their initial seed data +coincides. Following this, the task of mutating the initial cluster is +delegated to the cluster algebra and this process returns a new cluster +algebra. + +Seeds are considered equal if they have the same parent cluster +algebra and they can be obtained from each other by a permutation of their +data. Cluster algebras whose initial seeds are equal in the above sense are +not considered equal but are endowed with coercion maps to each other. +More generally, a cluster algebra is endowed with coercion maps from any +cluster algebra which is obtained by freezing a collection of cluster variables +and possibly permuting the remaining cluster variables and coefficients. + +The cluster algebra keeps track of all cluster variable data obtained so far, +in particular all g-vectors and all F-polynomials of known cluster variables +as well as a mutation path by which they can be obtained. When the cluster +algebra has principal coefficients, any of its homogeneous elements know their +own g-vector and F-polynomial. Following the Laurent Phenomenon, each element +of a cluster algebra also knows its own denominator vector. REFERENCES: @@ -266,6 +295,14 @@ def g_vector(self): else: raise ValueError("This element is not homogeneous.") +def F_polynomoial(self): + r""" + Return the F-polynomial of ``self``. + # A homogeneous element of a cluster algebra with principal coefficients + # should know its own F-polynomial + """ + pass + def is_homogeneous(self): r""" Return ``True`` if ``self`` is a homogeneous element of ``self.parent()``. From e7aa13c7ae23e0c33c7eff76016db548e9dab5c2 Mon Sep 17 00:00:00 2001 From: drupel Date: Sat, 13 Aug 2016 00:53:34 -0400 Subject: [PATCH 138/191] Added F_polynomial method for homogeneous elements --- src/sage/algebras/cluster_algebra.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 2192f74121e..05985545d3f 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -204,6 +204,7 @@ def __init__(self, parent, value): # setup methods defined only in special cases if parent._is_principal: self.g_vector = MethodType(g_vector, self, self.__class__) + self.F_polynomial = MethodType(F_polynomial, self, self.__class__) self.is_homogeneous = MethodType(is_homogeneous, self, self.__class__) self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) @@ -295,13 +296,22 @@ def g_vector(self): else: raise ValueError("This element is not homogeneous.") -def F_polynomoial(self): +def F_polynomial(self): r""" Return the F-polynomial of ``self``. - # A homogeneous element of a cluster algebra with principal coefficients - # should know its own F-polynomial + + EXAMPLES:: + sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A.cluster_variable((1,0)).F_polynomial() == A.F_polynomial((1,0)) + True """ - pass + subs_dict = dict() + A = self.parent() + for x in A.initial_cluster_variables(): + subs_dict[x.lift()] = A._U(1) + for i in xrange(A.rk()): + subs_dict[A.coefficient(i).lift()] = A._U.gen(i) + return self.lift().substitute(subs_dict) def is_homogeneous(self): r""" From 62a7e3a48fb8cd719e9c76d610dbd044a0f4a3f9 Mon Sep 17 00:00:00 2001 From: drupel Date: Sat, 13 Aug 2016 01:02:25 -0400 Subject: [PATCH 139/191] Added better F_polynomial example --- src/sage/algebras/cluster_algebra.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 05985545d3f..794313061f9 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -302,7 +302,9 @@ def F_polynomial(self): EXAMPLES:: sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - sage: A.cluster_variable((1,0)).F_polynomial() == A.F_polynomial((1,0)) + sage: S = A.initial_seed() + sage: S.mutate([0,1,0]) + sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) True """ subs_dict = dict() From 77a84ab2607cb4f23673c39319a69eefad52259f Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 15:57:02 +0200 Subject: [PATCH 140/191] Modified Introduction --- src/sage/algebras/cluster_algebra.py | 92 ++++++++++++++++++---------- 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 794313061f9..6f11b19c75e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1,38 +1,66 @@ r""" Cluster algebras -This file implements cluster algebras using the algebra-element framework. -This implementation mainly utilizes structural theorems from [FZ07]_. - -The class structure has been chosen to closely mirror the mutation process -in the theory of cluster algebras. Thus a cluster algebra is determined by -an initial seed from which all other seeds are computed. Seeds are a separate -class whose parent is an instance of the cluster algebra class. - -The task of mutating seeds is delegated to the seeds themselves since they -carry all the data for mutation, seeds also track a mutation path by which -they can be obtained from the initial seed of their parent. Although cluster -algebras themselves are independent of the choice of initial seed, in order to -maintain consistency of mutation path data in seeds, cluster algebras in this -implementation are considered to be equal only if their initial seed data -coincides. Following this, the task of mutating the initial cluster is -delegated to the cluster algebra and this process returns a new cluster -algebra. - +This file constructs cluster algebras using the Parent-Element framework. +The implementation mainly utilizes structural theorems from [FZ07]_. + +The key points being used here are these: + + * cluster variables are parametrized by their g-vector; + + * g-vectors (together with c-vectors) provide a self-standing model for the + combinatorics behind any cluster algebra; + + * cluster variables in any cluster algebra can be computed, by the + separation of additions formula, from their g-vector and F-polynomial. + +Accordingly this file provides three classes: + + * :class:`ClusterAlgebra` + + * :class:`ClusterAlgebraSeed` + + * :class:`ClusterAlgebraElement` + +:class:`ClusterAlgebra`, constructed as a subobject of +:class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`, +is the frontend of this implementation. It provides all the algebraic features +(like ring morphisms), it computes cluster variables, it is responsible for +controlling the exploration of the exchage graph and serves as repository for all +the data recursively computed so far. +In particular all g-vectors and all F-polynomials of known cluster variables as +well as a mutation path by which they can be obtained are recorded. In the optic +of efficiency, this implementationd does not store directly the exchange graph +nor the exchange relations. Both of these could be added to +:class:`ClusterAlgebra` with minimal effort. + +:class:`ClusterAlgebraSeed` provides the combinatorial backbone for :class:`ClusterAlgebra`. +It is an auxiliary class and therefore its instances should **not** be directly +created by the user. Rather it should be accessed via +:meth:`ClusterAlgebra.current_seed` and :meth:`ClusterAlgebra.initial_seed`. +To this class it is delegated the task of performing current seed mutations. Seeds are considered equal if they have the same parent cluster algebra and they can be obtained from each other by a permutation of their -data. Cluster algebras whose initial seeds are equal in the above sense are -not considered equal but are endowed with coercion maps to each other. -More generally, a cluster algebra is endowed with coercion maps from any -cluster algebra which is obtained by freezing a collection of cluster variables -and possibly permuting the remaining cluster variables and coefficients. - -The cluster algebra keeps track of all cluster variable data obtained so far, -in particular all g-vectors and all F-polynomials of known cluster variables -as well as a mutation path by which they can be obtained. When the cluster -algebra has principal coefficients, any of its homogeneous elements know their -own g-vector and F-polynomial. Following the Laurent Phenomenon, each element -of a cluster algebra also knows its own denominator vector. +data (i.e. if they coincide as unlabelled seeds). Cluster algebras whose +initial seeds are equal in the above sense are not considered equal but are +endowed with coercion maps to each other. More generally, a cluster algebra is +endowed with coercion maps from any cluster algebra which is obtained by +freezing a collection of initial cluster variables and/or permuting both +cluster variables and coefficients. + +:class:`ClusterAlgebraElement` is a thin wrapper around +:class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair` +providing all the fucntions specific to cluster variables. + +One more remark about this implementation. Instances of +:class:`ClusterAlgebra` are built by identifying the initial cluster variables +with the generators of :meth:`ClusterAlgebra.ambient`. In particular this +forces a specific embedding into the ambient field of rational expressions. In +view of this, although cluster algebras themselves are independent of the +choice of initial seed, :meth:`ClusterAlgebra.mutate_initial` is forced to +return a different instance of :class:`ClusterAlgebra`. At the moment there is +no coercion implemented among the two instances but this could be in principle +added to :meth:`ClusterAlgebra.mutate_initial`. REFERENCES: @@ -870,8 +898,8 @@ def __init__(self, data, **kwargs): - ``data`` -- some data defining a cluster algebra: it can be anything that can be parsed by :class:`ClusterQuiver`. - - ``scalars`` -- a ring (default ``ZZ``): the scalars over which the cluster algebra - is defined. + - ``scalars`` -- a ring (default `\ZZ`): the scalars over + which the cluster algebra is defined. - ``cluster_variable_prefix`` -- string (default ``'x'``): it needs to be a valid variable name. From b08771bd3f51587e09cae69b4b26c30b50245b5b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 16:06:38 +0200 Subject: [PATCH 141/191] Spelling and spaces --- src/sage/algebras/cluster_algebra.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 6f11b19c75e..965665d4b87 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -5,7 +5,7 @@ The implementation mainly utilizes structural theorems from [FZ07]_. The key points being used here are these: - + * cluster variables are parametrized by their g-vector; * g-vectors (together with c-vectors) provide a self-standing model for the @@ -13,7 +13,7 @@ * cluster variables in any cluster algebra can be computed, by the separation of additions formula, from their g-vector and F-polynomial. - + Accordingly this file provides three classes: * :class:`ClusterAlgebra` @@ -24,13 +24,13 @@ :class:`ClusterAlgebra`, constructed as a subobject of :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`, -is the frontend of this implementation. It provides all the algebraic features +is the fronted of this implementation. It provides all the algebraic features (like ring morphisms), it computes cluster variables, it is responsible for -controlling the exploration of the exchage graph and serves as repository for all -the data recursively computed so far. +controlling the exploration of the exchange graph and serves as repository for all +the data recursively computed so far. In particular all g-vectors and all F-polynomials of known cluster variables as well as a mutation path by which they can be obtained are recorded. In the optic -of efficiency, this implementationd does not store directly the exchange graph +of efficiency, this implementation does not store directly the exchange graph nor the exchange relations. Both of these could be added to :class:`ClusterAlgebra` with minimal effort. @@ -50,7 +50,7 @@ :class:`ClusterAlgebraElement` is a thin wrapper around :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair` -providing all the fucntions specific to cluster variables. +providing all the functions specific to cluster variables. One more remark about this implementation. Instances of :class:`ClusterAlgebra` are built by identifying the initial cluster variables From 0bc5fd55d66497291827366ba900c699e68a2f93 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 18:02:31 +0200 Subject: [PATCH 142/191] Added long example at the beginning --- src/sage/algebras/cluster_algebra.py | 249 +++++++++++++++++++++++++-- 1 file changed, 233 insertions(+), 16 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 965665d4b87..80105fd4eab 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -80,29 +80,234 @@ - Salvatore Stella (2015-06-15): initial version -EXAMPLES:: +EXAMPLES: - sage: A = ClusterAlgebra(['A',2]) +We begin by creating a simple cluster algebra and printing its initial exchange +matrix:: + + sage: A = ClusterAlgebra(['A',2]); A + A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring sage: A.b_matrix() [ 0 1] [-1 0] - sage: A.explore_to_depth(2) + +``A`` is of finite type so we can explore all its exchange graph:: + + sage: A.explore_to_depth(infinity) + +and get all its g-vectors, F-polynomials, and cluster variables:: + sage: A.g_vectors_so_far() [(0, 1), (0, -1), (1, 0), (-1, 1), (-1, 0)] sage: A.F_polynomials_so_far() [1, u1 + 1, 1, u0 + 1, u0*u1 + u0 + 1] sage: A.cluster_variables_so_far() [x1, (x0 + 1)/x1, x0, (x1 + 1)/x0, (x0 + x1 + 1)/(x0*x1)] - sage: B = A.mutate_initial(0) - sage: B.b_matrix() - [ 0 -1] - [ 1 0] - sage: B.g_vectors_so_far() - [(0, 1), (0, -1), (1, 0), (1, -1), (-1, 0)] - sage: B.F_polynomials_so_far() - [1, u0*u1 + u1 + 1, 1, u1 + 1, u0 + 1] - sage: B.cluster_variables_so_far() - [x1, (x0 + x1 + 1)/(x0*x1), x0, (x0 + 1)/x1, (x1 + 1)/x0] + +Simple operations among cluster variables behave as expected:: + + sage: s = A.cluster_variable((0,-1)); s + (x0 + 1)/x1 + sage: t = A.cluster_variable((-1,1)); t + (x1 + 1)/x0 + sage: t + s + (x0^2 + x1^2 + x0 + x1)/(x0*x1) + sage: _.parent() == A + True + sage: t - s + (-x0^2 + x1^2 - x0 + x1)/(x0*x1) + sage: _.parent() == A + True + sage: t*s + (x0*x1 + x0 + x1 + 1)/(x0*x1) + sage: _.parent() == A + True + sage: t/s + (x1^2 + x1)/(x0^2 + x0) + sage: _.parent() == A + False + +Division is not guaranteed to yield an element of ``A`` so it returns an +element of ``A.ambient().fraction_field()`` instead:: + + sage: (t/s).parent() == A.ambient().fraction_field() + True + +We can compute denominator vectors of any element of ``A``:: + + sage: (t*s).d_vector() + (1, 1) + +and since we are in rank 2 and we do not have coefficients we can compute the +greedy element associated to any denominator vector:: + + sage: A.rk() == 2 and A.coefficients() == [] + True + sage: A.greedy_element((1,1)) + (x0 + x1 + 1)/(x0*x1) + sage: _ == t*s + False + +... not surprising since there is no cluster in ``A`` containing both ``t`` and +``s``:: + + sage: seeds = A.seeds(mutating_F=false) + sage: [ S for S in seeds if (0,-1) in S and (-1,1) in S ] + [] + +indeed:: + + sage: A.greedy_element((1,1)) == A.cluster_variable((-1,0)) + True + +Disabling F-polynomials in the computation just done was redundant because we +already explored the whole exchange graph before. In different circumstances it +could have saved us a long time though. + +g-vectors and F-polynomials can be computed from elements of ``A`` only if +``A`` has principal coefficients at the initial seed:: + + sage: (t*s).g_vector() + Traceback (most recent call last): + ... + AttributeError: 'ClusterAlgebra_with_category.element_class' object has no attribute 'g_vector' + sage: A = ClusterAlgebra(['A',2], principal_coefficients=True) + sage: A.explore_to_depth(infinity) + sage: s = A.cluster_variable((0,-1)); s + (x0*y1 + 1)/x1 + sage: t = A.cluster_variable((-1,1)); t + (x1 + y0)/x0 + sage: (t*s).g_vector() + (-1, 0) + sage: (t*s).F_polynomial() + u0*u1 + u0 + u1 + 1 + sage: (t*s).is_homogeneous() + True + sage: (t+s).is_homogeneous() + False + sage: (t+s).homogeneous_components() + {(-1, 1): (x1 + y0)/x0, (0, -1): (x0*y1 + 1)/x1} + +Each cluster algebra is endowed with a reference to a current seed; it could be +useful to assign a name to it:: + + sage: A = ClusterAlgebra(['F',4]) + sage: len(A.g_vectors_so_far()) + 4 + sage: A.current_seed() + The initial seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + sage: A.current_seed() == A.initial_seed() + True + sage: S = A.current_seed() + sage: S.b_matrix() + [ 0 1 0 0] + [-1 0 -1 0] + [ 0 2 0 1] + [ 0 0 -1 0] + sage: S.g_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + sage: S.cluster_variables() + [x0, x1, x2, x3] + +and use ``S`` to walk around the exchange graph of ``A``:: + + sage: S.mutate(0); S + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + sage: S.b_matrix() + [ 0 -1 0 0] + [ 1 0 -1 0] + [ 0 2 0 1] + [ 0 0 -1 0] + sage: S.g_matrix() + [-1 0 0 0] + [ 1 1 0 0] + [ 0 0 1 0] + [ 0 0 0 1] + sage: S.cluster_variables() + [(x1 + 1)/x0, x1, x2, x3] + sage: S.mutate('sinks'); S + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2] + sage: S.mutate([2,3,2,1,0]); S + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 3, 2, 1, 0] + sage: S.g_vectors() + [(0, 1, -2, 0), (-1, 2, -2, 0), (0, 1, -1, 0), (0, 0, 0, -1)] + sage: S.cluster_variable(3) + (x2 + 1)/x3 + +Walking around by mutating ``S`` updates the informations stored in ``A``:: + + sage: len(A.g_vectors_so_far()) + 10 + sage: A.current_seed().path_from_initial_seed() + [0, 3, 2, 1, 0] + sage: A.current_seed() == S + True + +Starting from ``A.initial_seed()`` still records data in ``A`` but does not +uptate ``A.current_seed()``:: + + sage: S1 = A.initial_seed() + sage: S1.mutate([2,1,3]) + sage: len(A.g_vectors_so_far()) + 11 + sage: S1 == A.current_seed() + False + +Given a cluster algebra ``A`` we may be looking for a specific cluster +variable:: + + sage: A = ClusterAlgebra(['E', 8, 1]) + sage: A.find_g_vector((-1, 1, -1, 1, -1, 1, 0, 0, 1), depth=2) + sage: A.find_g_vector((-1, 1, -1, 1, -1, 1, 0, 0, 1)) + [0, 1, 2, 4, 3] + +This also performs mutations of F-polynomials:: + + sage: A.F_polynomial((-1, 1, -1, 1, -1, 1, 0, 0, 1)) + u0*u1*u2*u3*u4 + u0*u1*u2*u4 + u0*u2*u3*u4 + u0*u1*u2 + u0*u2*u4 + u2*u3*u4 + u0*u2 + u0*u4 + u2*u4 + u0 + u2 + u4 + 1 + +which might not be a good idea in algebras that are too big. One workaround is +to first disable F-polynomials and then recompute only the desired mutations:: + + sage: A.reset_exploring_iterator(mutating_F=False) # long time + sage: A.find_g_vector((-1, 1, -2, 2, -1, 1, -1, 1, 1)) # long time + [1, 0, 2, 6, 5, 4, 3, 8, 1] + sage: A.current_seed().mutate(_) # long time + sage: A.F_polynomial((-1, 1, -2, 2, -1, 1, -1, 1, 1)) # long time + u0*u1^2*u2^2*u3*u4*u5*u6*u8 + + ... + 2*u2 + u4 + u6 + 1 + +We can manually freeze cluster variables and get coercions in between the two +algebras:: + + sage: A = ClusterAlgebra(['F',4]); A + A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + sage: B = ClusterAlgebra(A.b_matrix().matrix_from_columns([0,1,2]),coefficient_prefix='x'); B + A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3 over Integer Ring + sage: A.has_coerce_map_from(B) + True + +and we also have an immersion of ``A.base()`` into ``A`` and of ``A`` into +``A.ambient()``:: + + sage: A.has_coerce_map_from(A.base()) + True + sage: A.ambient().has_coerce_map_from(A) + True + +but there is currently no coercion in between algebras obtained by mutating at +the initial seed:: + + sage: B = A.mutate_initial(0); B + A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + sage: A.b_matrix() == B.b_matrix() + False + sage: map(lambda (X,Y): X.has_coerce_map_from(Y), [(A,B),(B,A)]) + [False, False] """ #***************************************************************************** @@ -253,6 +458,18 @@ def _add_(self, other): """ return self.parent().retract(self.lift() + other.lift()) + def _neg_(self): + r""" + Return the negative of ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: -A.an_element() + -x0 + """ + return self.parent().retract(-self.lift()) + def _div_(self, other): r""" Return the quotient of ``self`` and ``other``. @@ -1570,10 +1787,10 @@ def coefficient_names(self): sage: A = ClusterAlgebra(['A',3]) sage: A.coefficient_names() () - sage: B = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: B = ClusterAlgebra(['B',2], principal_coefficients=True) sage: B.coefficient_names() ('y0', 'y1') - sage: C = ClusterAlgebra(['C',3],coefficient_prefix='x') + sage: C = ClusterAlgebra(['C',3], principal_coefficients=True, coefficient_prefix='x') sage: C.coefficient_names() ('x3', 'x4', 'x5') """ @@ -1618,7 +1835,7 @@ def initial_cluster_variable_names(self): ('x0', 'x1') sage: B = ClusterAlgebra(['B',2],cluster_variable_prefix='a') sage: B.initial_cluster_variable_names() - ('a0','a1') + ('a0', 'a1') """ return self.variable_names()[:self.rk()] From 3d2193db1c7a3bb5e3ec23d36ee7998d9c48ab9c Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 18:43:00 +0200 Subject: [PATCH 143/191] Fixed link --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 80105fd4eab..6ea103534ff 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -49,7 +49,7 @@ cluster variables and coefficients. :class:`ClusterAlgebraElement` is a thin wrapper around -:class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair` +:class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_generic` providing all the functions specific to cluster variables. One more remark about this implementation. Instances of From 711edcc782784b27256202424c27fda7f882860b Mon Sep 17 00:00:00 2001 From: drupel Date: Sat, 13 Aug 2016 14:05:53 -0400 Subject: [PATCH 144/191] Minor intro doc edits --- src/sage/algebras/cluster_algebra.py | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 6ea103534ff..9efc46b54e8 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -6,13 +6,13 @@ The key points being used here are these: - * cluster variables are parametrized by their g-vector; + * cluster variables are parametrized by their g-vectors; * g-vectors (together with c-vectors) provide a self-standing model for the combinatorics behind any cluster algebra; - * cluster variables in any cluster algebra can be computed, by the - separation of additions formula, from their g-vector and F-polynomial. + * each cluster variable in any cluster algebra can be computed, by the + separation of additions formula, from its g-vector and F-polynomial. Accordingly this file provides three classes: @@ -24,21 +24,21 @@ :class:`ClusterAlgebra`, constructed as a subobject of :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`, -is the fronted of this implementation. It provides all the algebraic features +is the frontend of this implementation. It provides all the algebraic features (like ring morphisms), it computes cluster variables, it is responsible for -controlling the exploration of the exchange graph and serves as repository for all -the data recursively computed so far. -In particular all g-vectors and all F-polynomials of known cluster variables as +controlling the exploration of the exchange graph and serves as the repository for +all the data recursively computed so far. +In particular, all g-vectors and all F-polynomials of known cluster variables as well as a mutation path by which they can be obtained are recorded. In the optic of efficiency, this implementation does not store directly the exchange graph nor the exchange relations. Both of these could be added to :class:`ClusterAlgebra` with minimal effort. :class:`ClusterAlgebraSeed` provides the combinatorial backbone for :class:`ClusterAlgebra`. -It is an auxiliary class and therefore its instances should **not** be directly +It is an auxiliary class and therefore its instances should **not** be directly created by the user. Rather it should be accessed via :meth:`ClusterAlgebra.current_seed` and :meth:`ClusterAlgebra.initial_seed`. -To this class it is delegated the task of performing current seed mutations. +The task of performing current seed mutations is delegated to this class. Seeds are considered equal if they have the same parent cluster algebra and they can be obtained from each other by a permutation of their data (i.e. if they coincide as unlabelled seeds). Cluster algebras whose @@ -54,12 +54,12 @@ One more remark about this implementation. Instances of :class:`ClusterAlgebra` are built by identifying the initial cluster variables -with the generators of :meth:`ClusterAlgebra.ambient`. In particular this +with the generators of :meth:`ClusterAlgebra.ambient`. In particular, this forces a specific embedding into the ambient field of rational expressions. In view of this, although cluster algebras themselves are independent of the choice of initial seed, :meth:`ClusterAlgebra.mutate_initial` is forced to return a different instance of :class:`ClusterAlgebra`. At the moment there is -no coercion implemented among the two instances but this could be in principle +no coercion implemented among the two instances but this could in principle be added to :meth:`ClusterAlgebra.mutate_initial`. REFERENCES: @@ -138,7 +138,7 @@ sage: (t*s).d_vector() (1, 1) -and since we are in rank 2 and we do not have coefficients we can compute the +Since we are in rank 2 and we do not have coefficients we can compute the greedy element associated to any denominator vector:: sage: A.rk() == 2 and A.coefficients() == [] @@ -161,8 +161,8 @@ True Disabling F-polynomials in the computation just done was redundant because we -already explored the whole exchange graph before. In different circumstances it -could have saved us a long time though. +already explored the whole exchange graph before. Though in different circumstances it +could have saved us considerable time. g-vectors and F-polynomials can be computed from elements of ``A`` only if ``A`` has principal coefficients at the initial seed:: @@ -246,8 +246,8 @@ sage: A.current_seed() == S True -Starting from ``A.initial_seed()`` still records data in ``A`` but does not -uptate ``A.current_seed()``:: +Starting from ``A.initial_seed()`` still records data in ``A`` but does not +update ``A.current_seed()``:: sage: S1 = A.initial_seed() sage: S1.mutate([2,1,3]) From 4dacc2b0e54328330a86c4385a2a3134a46ef449 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 23:56:09 +0200 Subject: [PATCH 145/191] F-polynomials only for homogeneous --- src/sage/algebras/cluster_algebra.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 9efc46b54e8..5ed0d59b32f 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -534,6 +534,10 @@ def g_vector(self): sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) sage: A.cluster_variable((1,0)).g_vector() == (1,0) True + sage: sum(A.initial_cluster_variables()).g_vector() + Traceback (most recent call last): + ... + ValueError: This element is not homogeneous. """ components = self.homogeneous_components() if len(components) == 1: @@ -551,14 +555,21 @@ def F_polynomial(self): sage: S.mutate([0,1,0]) sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) True + sage: sum(A.initial_cluster_variables()).F_polynomial() + Traceback (most recent call last): + ... + ValueError: This element is not homogeneous. """ - subs_dict = dict() - A = self.parent() - for x in A.initial_cluster_variables(): - subs_dict[x.lift()] = A._U(1) - for i in xrange(A.rk()): - subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) + if self.is_homogeneous(): + subs_dict = dict() + A = self.parent() + for x in A.initial_cluster_variables(): + subs_dict[x.lift()] = A._U(1) + for i in xrange(A.rk()): + subs_dict[A.coefficient(i).lift()] = A._U.gen(i) + return self.lift().substitute(subs_dict) + else: + raise ValueError("This element is not homogeneous.") def is_homogeneous(self): r""" From ff51a0bddc8f0416b38105cfd4a8af3e98c8c123 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 13 Aug 2016 23:57:46 +0200 Subject: [PATCH 146/191] Spaaaaaaaaaaaaaaaaaaaaaaaaaaace --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 5ed0d59b32f..7f04ad21a50 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -535,7 +535,7 @@ def g_vector(self): sage: A.cluster_variable((1,0)).g_vector() == (1,0) True sage: sum(A.initial_cluster_variables()).g_vector() - Traceback (most recent call last): + Traceback (most recent call last): ... ValueError: This element is not homogeneous. """ @@ -556,7 +556,7 @@ def F_polynomial(self): sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) True sage: sum(A.initial_cluster_variables()).F_polynomial() - Traceback (most recent call last): + Traceback (most recent call last): ... ValueError: This element is not homogeneous. """ From 709fa03c8de68cde3730260f514a3f5500dcc1f6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 21 Aug 2016 01:42:06 +0200 Subject: [PATCH 147/191] Added warning to functions depending on _sd_iter --- src/sage/algebras/cluster_algebra.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7f04ad21a50..d2a6c111900 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1661,6 +1661,17 @@ def find_g_vector(self, g_vector, depth=infinity): ``g_vector`` is not the g-vector of any cluster variable. In this case the function resets the iterator and raises an error. + WARNING: + + This method, like some other in this class, manipulate an internal + data structure to minimize computation time. Should it be + interrupted before it returns, this data will be left in a + corrupted state that will cripple future executions of all methods + that depend on it. + + If you interrupt this method remember to sanitize the internal data + structure by calling :meth:`reset_exploring_iterator`. + EXAMPLES:: sage: A = ClusterAlgebra(['G',2],principal_coefficients=True) @@ -1970,6 +1981,17 @@ def explore_to_depth(self, depth): - ``depth`` -- a positive integer or infinity: the maximum depth at which to stop searching. + WARNING: + + This method, like some other in this class, manipulate an internal + data structure to minimize computation time. Should it be + interrupted before it returns, this data will be left in a + corrupted state that will cripple future executions of all methods + that depend on it. + + If you interrupt this method remember to sanitize the internal data + structure by calling :meth:`reset_exploring_iterator`. + EXAMPLES:: sage: A = ClusterAlgebra(['A',4]) From da0b1e7abdc96cc5440f51f419adc46d8efdbac7 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 21 Aug 2016 11:44:52 +0200 Subject: [PATCH 148/191] Spaaaaaace (again) --- src/sage/algebras/cluster_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d2a6c111900..eb64acda28d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1661,8 +1661,8 @@ def find_g_vector(self, g_vector, depth=infinity): ``g_vector`` is not the g-vector of any cluster variable. In this case the function resets the iterator and raises an error. - WARNING: - + WARNING: + This method, like some other in this class, manipulate an internal data structure to minimize computation time. Should it be interrupted before it returns, this data will be left in a @@ -1981,8 +1981,8 @@ def explore_to_depth(self, depth): - ``depth`` -- a positive integer or infinity: the maximum depth at which to stop searching. - WARNING: - + WARNING: + This method, like some other in this class, manipulate an internal data structure to minimize computation time. Should it be interrupted before it returns, this data will be left in a From 3e26d2c77d890d482f1e3dfbb5e55255dd2d5e4a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 22 Aug 2016 09:17:59 +0200 Subject: [PATCH 149/191] Catch only StopIteration --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index eb64acda28d..bd26a3eea30 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2003,7 +2003,7 @@ def explore_to_depth(self, depth): try: seed = next(self._sd_iter) self._explored_depth = seed.depth() - except: + except StopIteration: break def cluster_fan(self, depth=infinity): From e9ddd627be077e2d668127064a6885b194f1853a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Tue, 23 Aug 2016 10:55:18 +0200 Subject: [PATCH 150/191] Added hash function to ClusterAlgebraSeed --- src/sage/algebras/cluster_algebra.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index bd26a3eea30..0556a18a6e3 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -740,6 +740,26 @@ def __contains__(self, element): cluster = self.g_vectors() return element in cluster + def __hash__(self): + r""" + Return the hash of ``self``. + + ALGORITHM: + + For speed purposes the hash is computed on :meth:`self.g_vectors`. + In particular it is guaranteed to be unique only within a given + instance of :class:`ClusterAlgebra`. Moreover unlabelled seeds that + have the same set of g-vectors have the same hash. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: S = A.initial_seed() + sage: hash(S) + 610855963840905253 + """ + return hash(frozenset(self.g_vectors())) + def _repr_(self): r""" Return the string representation of ``self``. From 84a5cee37d43ba78bd87bbecffe624b54bd9e977 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 25 Aug 2016 19:49:27 +0200 Subject: [PATCH 151/191] Figured out how to continue seeds after a KeyboardInterrupt --- src/sage/algebras/cluster_algebra.py | 43 ++++++++++++++++------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 0556a18a6e3..31524c3faed 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1950,24 +1950,31 @@ def seeds(self, **kwargs): for key in clusters.keys(): sd, directions = clusters[key] while directions: - # we can mutate in some direction - i = directions.pop() - new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) - new_cl = frozenset(new_sd.g_vectors()) - if new_cl in clusters: - # we already had new_sd, make sure it does not mutate to sd during next round - j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) - try: - clusters[new_cl][1].remove(j) - except ValueError: - pass - else: - # we got a new seed - gets_bigger = True - # next round do not mutate back to sd and do commuting mutations only in directions j > i - new_directions = [ j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0 ] - clusters[new_cl] = [ new_sd, new_directions ] - yield new_sd + try: + # we can mutate in some direction + i = directions.pop() + new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) + new_cl = frozenset(new_sd.g_vectors()) + if new_cl in clusters: + # we already had new_sd, make sure it does not mutate to sd during next round + j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) + try: + clusters[new_cl][1].remove(j) + except ValueError: + pass + else: + # we got a new seed + gets_bigger = True + # next round do not mutate back to sd and make sure we only walk three sides of squares + new_directions = [ j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0 ] + clusters[new_cl] = [ new_sd, new_directions ] + yield new_sd + except KeyboardInterrupt: + print("Caught a KeyboardInterrupt; cleaning up before returning.") + # mutation in direction i was not completed; put it back in for next round + directions.append(i) + yield + continue # we went one step deeper depth_counter += 1 From 548dab77e7c19fd1a806babf72365815cf90b5f0 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 25 Aug 2016 20:03:25 +0200 Subject: [PATCH 152/191] Rafactored all fuctions based on seeds() --- src/sage/algebras/cluster_algebra.py | 39 +++++++++++----------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 31524c3faed..b07178f36fd 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1681,17 +1681,6 @@ def find_g_vector(self, g_vector, depth=infinity): ``g_vector`` is not the g-vector of any cluster variable. In this case the function resets the iterator and raises an error. - WARNING: - - This method, like some other in this class, manipulate an internal - data structure to minimize computation time. Should it be - interrupted before it returns, this data will be left in a - corrupted state that will cripple future executions of all methods - that depend on it. - - If you interrupt this method remember to sanitize the internal data - structure by calling :meth:`reset_exploring_iterator`. - EXAMPLES:: sage: A = ClusterAlgebra(['G',2],principal_coefficients=True) @@ -1708,7 +1697,11 @@ def find_g_vector(self, g_vector, depth=infinity): while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) - self._explored_depth = seed.depth() + if seed: + self._explored_depth = seed.depth() + else: + # We got None because self._sd_iter caught a KeyboardInterrupt, let's raise it again + raise KeyboardInterrupt except StopIteration: # Unless self._sd_iter has been manually altered, we checked # all the seeds of self and did not find g_vector. @@ -1902,6 +1895,11 @@ def seeds(self, **kwargs): This function traverses the exchange graph in a breadth-first search. + Note that upon catching a ``KeyboardInterrupt`` the iterators this + function produce will yield ``None`` rather then raising the + exception. This is done in order to be able to resume the + exploration from where it was interrupted. + EXAMPLES:: sage: A = ClusterAlgebra(['A',4]) @@ -2008,17 +2006,6 @@ def explore_to_depth(self, depth): - ``depth`` -- a positive integer or infinity: the maximum depth at which to stop searching. - WARNING: - - This method, like some other in this class, manipulate an internal - data structure to minimize computation time. Should it be - interrupted before it returns, this data will be left in a - corrupted state that will cripple future executions of all methods - that depend on it. - - If you interrupt this method remember to sanitize the internal data - structure by calling :meth:`reset_exploring_iterator`. - EXAMPLES:: sage: A = ClusterAlgebra(['A',4]) @@ -2029,7 +2016,11 @@ def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) - self._explored_depth = seed.depth() + if seed: + self._explored_depth = seed.depth() + else: + # We got None because self._sd_iter caught a KeyboardInterrupt, let's raise it again + raise KeyboardInterrupt except StopIteration: break From 58902643e0f7bd328cc6cfb984c15b6ed62c7be4 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 25 Aug 2016 22:42:49 +0200 Subject: [PATCH 153/191] Better interrupt handling --- src/sage/algebras/cluster_algebra.py | 42 +++++++++++++++------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b07178f36fd..c43d1cbb223 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1697,11 +1697,11 @@ def find_g_vector(self, g_vector, depth=infinity): while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) - if seed: + if isinstance(seed, ClusterAlgebraSeed) self._explored_depth = seed.depth() else: - # We got None because self._sd_iter caught a KeyboardInterrupt, let's raise it again - raise KeyboardInterrupt + # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again + raise seed except StopIteration: # Unless self._sd_iter has been manually altered, we checked # all the seeds of self and did not find g_vector. @@ -1889,17 +1889,18 @@ def seeds(self, **kwargs): - ``allowed_directions`` -- a tuple of integers (default ``range(self.rk())``): the directions in which to mutate. - - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum depth at which to stop searching. + - ``depth`` -- a positive integer or infinity (default ``infinity``): + the maximum depth at which to stop searching. + + - ``catch_KeyboardInterrupt`` -- bool (default ``False``): whether to + catch ``KeyboardInterrupt`` and return it rather then raising an + exception. This allows the iterator returned by this method to be + resumed after being interrupted. ALGORITHM: This function traverses the exchange graph in a breadth-first search. - Note that upon catching a ``KeyboardInterrupt`` the iterators this - function produce will yield ``None`` rather then raising the - exception. This is done in order to be able to resume the - exploration from where it was interrupted. - EXAMPLES:: sage: A = ClusterAlgebra(['A',4]) @@ -1967,12 +1968,15 @@ def seeds(self, **kwargs): new_directions = [ j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] yield new_sd - except KeyboardInterrupt: - print("Caught a KeyboardInterrupt; cleaning up before returning.") - # mutation in direction i was not completed; put it back in for next round - directions.append(i) - yield - continue + except KeyboardInterrupt as e: + if kwargs.get('catch_KeyboardInterrupt', False): + print("Caught a KeyboardInterrupt; cleaning up before returning.") + # mutation in direction i was not completed; put it back in for next round + directions.append(i) + yield e + continue + else: + raise e # we went one step deeper depth_counter += 1 @@ -1995,7 +1999,7 @@ def reset_exploring_iterator(self, mutating_F=True): sage: len(A.F_polynomials_so_far()) 4 """ - self._sd_iter = self.seeds(mutating_F=mutating_F) + self._sd_iter = self.seeds(mutating_F=mutating_F, catch_KeyboardInterrupt=True) self._explored_depth = 0 def explore_to_depth(self, depth): @@ -2016,11 +2020,11 @@ def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) - if seed: + if isinstance(seed, ClusterAlgebraSeed) self._explored_depth = seed.depth() else: - # We got None because self._sd_iter caught a KeyboardInterrupt, let's raise it again - raise KeyboardInterrupt + # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again + raise seed except StopIteration: break From 9ef1530c46a0416a7a3e10c70bb698b02c86593b Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 25 Aug 2016 22:50:15 +0200 Subject: [PATCH 154/191] Fixed missing : --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c43d1cbb223..c4a2fe457ae 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1697,7 +1697,7 @@ def find_g_vector(self, g_vector, depth=infinity): while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) - if isinstance(seed, ClusterAlgebraSeed) + if isinstance(seed, ClusterAlgebraSeed): self._explored_depth = seed.depth() else: # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again @@ -2020,7 +2020,7 @@ def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) - if isinstance(seed, ClusterAlgebraSeed) + if isinstance(seed, ClusterAlgebraSeed): self._explored_depth = seed.depth() else: # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again From 569a8af26fbf369594b77af0cc773701d78d40e9 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 25 Aug 2016 22:56:10 +0200 Subject: [PATCH 155/191] Fixed missing empty lines --- src/sage/algebras/cluster_algebra.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c4a2fe457ae..770aef97a9e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -724,6 +724,7 @@ def __contains__(self, element): - ``element`` -- either a g-vector or an element of :meth:`parent`. EXAMPLES:: + sage: A = ClusterAlgebra(['A',3]) sage: S = A.initial_seed() sage: (1,0,0) in S @@ -1359,6 +1360,7 @@ def _an_element_(self): Return an element of ``self``. EXAMPLES:: + sage: A = ClusterAlgebra(['A',2]) sage: A.an_element() x0 From ba2e9c9f35cd05a62cfc6ecb13f8a05c1b6588f5 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 26 Aug 2016 08:54:02 +0200 Subject: [PATCH 156/191] Missing digit in doctest --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 770aef97a9e..4073ee6aa39 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -757,7 +757,7 @@ def __hash__(self): sage: A = ClusterAlgebra(['A',3]) sage: S = A.initial_seed() sage: hash(S) - 610855963840905253 + 6108559638409052534 """ return hash(frozenset(self.g_vectors())) From 23d0b972c521a688ce80fcb05cc7164b10a4b897 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 5 Sep 2016 14:44:50 +0200 Subject: [PATCH 157/191] Improved documentation of _div_ --- src/sage/algebras/cluster_algebra.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 4073ee6aa39..ec583e5d099 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -476,18 +476,22 @@ def _div_(self, other): WARNING: - This method returns an element of ``self.parent().ambient()`` - rather than an element of ``self.parent()`` because, a priori, - we cannot guarantee membership. - - You can force the result to be an element of ``self.parent()`` - by feeding it into ``self.parent().retract``. + The result of a division is not guaranteed to remain inside + meth:`parent` therefore this method does not return an instance of + class:`ClusterAlgebraElelemt`. EXAMPLES:: sage: A = ClusterAlgebra(['F',4]) - sage: A.an_element() / A.an_element() + sage: x = A.an_element() + sage: x/x + 1 + sage: _.parent() + Multivariate Laurent Polynomial Ring in x0, x1, x2, x3 over Integer Ring + sage: A.retract(x/x) 1 + sage: _.parent() + A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring """ return self.lift()/other.lift() From e1980628e43a2a4ea14921b570094153219c03c8 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 5 Sep 2016 15:27:22 +0200 Subject: [PATCH 158/191] Fixed typos --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ec583e5d099..3c884c78d52 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -476,9 +476,9 @@ def _div_(self, other): WARNING: - The result of a division is not guaranteed to remain inside + The result of a division is not guaranteed to be inside meth:`parent` therefore this method does not return an instance of - class:`ClusterAlgebraElelemt`. + class:`ClusterAlgebraElement`. EXAMPLES:: From 78ab8ef900cb0a2f4dd5e8ef3f6bc0ef50dd60a6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 7 Sep 2016 12:14:34 +0200 Subject: [PATCH 159/191] Changed introduction to avoit plugin failure --- src/sage/algebras/cluster_algebra.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3c884c78d52..c56a106932d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -148,8 +148,7 @@ sage: _ == t*s False -... not surprising since there is no cluster in ``A`` containing both ``t`` and -``s``:: +not surprising since there is no cluster in ``A`` containing both ``t`` and ``s``:: sage: seeds = A.seeds(mutating_F=false) sage: [ S for S in seeds if (0,-1) in S and (-1,1) in S ] From 821e54c76fa3cd917153b5662e2f2bf6b20c60c6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 7 Sep 2016 12:29:55 +0200 Subject: [PATCH 160/191] 98.6% coverage reached. No idea on how to doctest _mutation_parse --- src/sage/algebras/cluster_algebra.py | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index c56a106932d..7a9d5dd59a2 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2140,24 +2140,56 @@ def mutate_initial(self, k): def upper_cluster_algebra(self): r""" Return the upper cluster algebra associated to ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.upper_cluster_algebra() + Traceback (most recent call last): + ... + NotImplementedError: Not implemented yet. """ raise NotImplementedError("Not implemented yet.") def upper_bound(self): r""" Return the upper bound associated to ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.upper_bound() + Traceback (most recent call last): + ... + NotImplementedError: Not implemented yet. """ raise NotImplementedError("Not implemented yet.") def lower_bound(self): r""" Return the lower bound associated to ``self``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.lower_bound() + Traceback (most recent call last): + ... + NotImplementedError: Not implemented yet. """ raise NotImplementedError("Not implemented yet.") def theta_basis_element(self, g_vector): r""" Return the element of the theta basis with g-vector ``g_vetor``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F',4]) + sage: A.theta_basis_element((1,0,0,0)) + Traceback (most recent call last): + ... + NotImplementedError: Not implemented yet. """ raise NotImplementedError("Not implemented yet.") From 0ec4e0d948cb751fd674d6516a95f670dd665875 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 7 Sep 2016 15:01:53 +0200 Subject: [PATCH 161/191] 100% coverage reached --- src/sage/algebras/cluster_algebra.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7a9d5dd59a2..8393fc90722 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -363,6 +363,17 @@ def _mutation_parse(mutate): - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) - urban renewals - other? + + EXAMPLES:: + + sage: A = ClusterAlgebra(['E',6]) + sage: S = A.current_seed() + sage: S.mutate(1,inplace=False) # indirect doctest + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating in direction 1 + sage: S.mutate([1,2,3],inplace=False) # indirect doctest + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] + sage: S.mutate('sinks',inplace=False) # indirect doctest + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2, 4] """ doc = mutate.__doc__.split("INPUT:") doc[0] += "INPUT:" From f299d1d93d07a6cba64b78a7ef8a0d19ab9070b6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Fri, 16 Sep 2016 18:59:57 +0200 Subject: [PATCH 162/191] Some fixes due to pylint --- src/sage/algebras/cluster_algebra.py | 91 ++++++++++++++-------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8393fc90722..b0473e5e74e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -321,6 +321,7 @@ from copy import copy from functools import wraps +from types import MethodType from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism from sage.categories.rings import Rings @@ -338,13 +339,13 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing, LaurentPolynomialRing_generic +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing_generic +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent from sage.structure.sage_object import SageObject -from types import MethodType ############################################################################## # Helper functions @@ -401,10 +402,10 @@ def mutate_wrapper(self, direction, **kwargs): if direction == "sinks": B = self.b_matrix() - seq = [ i for i in range(B.ncols()) if all( x<=0 for x in B.column(i) ) ] + seq = [i for i in range(B.ncols()) if all(x <= 0 for x in B.column(i))] elif direction == "sources": B = self.b_matrix() - seq = [ i for i in range(B.ncols()) if all( x>=0 for x in B.column(i) ) ] + seq = [i for i in range(B.ncols()) if all(x >= 0 for x in B.column(i))] else: try: seq = iter(direction) @@ -616,7 +617,7 @@ def homogeneous_components(self): sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rk()),-self.parent().b_matrix()]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rk()), -self.parent().b_matrix()]]) components = dict() x = self.lift() monomials = x.monomials() @@ -727,7 +728,7 @@ def __eq__(self, other): sage: S == B.current_seed() True """ - return type(self) == type(other) and self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + return isinstance(other, ClusterAlgebraSeed) and self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) def __contains__(self, element): r""" @@ -792,9 +793,9 @@ def _repr_(self): if self._path == []: return "The initial seed of a %s"%str(self.parent())[2:] elif len(self._path) == 1: - return "The seed of a %s obtained from the initial by mutating in direction %s"%(str(self.parent())[2:],str(self._path[0])) + return "The seed of a %s obtained from the initial by mutating in direction %s"%(str(self.parent())[2:], str(self._path[0])) else: - return "The seed of a %s obtained from the initial by mutating along the sequence %s"%(str(self.parent())[2:],str(self._path)) + return "The seed of a %s obtained from the initial by mutating along the sequence %s"%(str(self.parent())[2:], str(self._path)) def parent(self): r""" @@ -1140,7 +1141,7 @@ def _mutated_F(self, k, old_g_vector): neg *= alg._U.gen(j)**(-self._C[j,k]) if self._B[j,k] > 0: pos *= self.F_polynomial(j)**self._B[j,k] - elif self._B[j,k] <0: + elif self._B[j,k] < 0: neg *= self.F_polynomial(j)**(-self._B[j,k]) return (pos+neg)/alg.F_polynomial(old_g_vector) @@ -1227,26 +1228,26 @@ def __init__(self, data, **kwargs): self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) # Storage for computed data - self._path_dict = dict([ (v, []) for v in map(tuple,I.columns()) ]) - self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) + self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) + self._F_poly_dict = dict([(v, self._U(1)) for v in self._path_dict]) # Determine the names of the initial cluster variables - variables_prefix = kwargs.get('cluster_variable_prefix','x') + variables_prefix = kwargs.get('cluster_variable_prefix', 'x') variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in xrange(n)])) if len(variables) != n: - raise ValueError("cluster_variable_names should be a list of %d valid variable names"%n) + raise ValueError("cluster_variable_names should be a list of %d valid variable names"%n) # Determine scalars scalars = kwargs.get('scalars', ZZ) # Determine coefficients and setup self._base - if m>0: + if m > 0: coefficient_prefix = kwargs.get('coefficient_prefix', 'y') if coefficient_prefix == variables_prefix: offset = n else: offset = 0 - coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in xrange(offset,m+offset)])) + coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in xrange(offset, m+offset)])) if len(coefficients) != m: raise ValueError("coefficient_names should be a list of %d valid variable names"%m) base = LaurentPolynomialRing(scalars, coefficients) @@ -1259,8 +1260,8 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions - self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) - self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n+m)])) for j in xrange(n)]) + self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) + self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n+m)])) for j in xrange(n)]) # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1281,7 +1282,7 @@ def __init__(self, data, **kwargs): self._greedy_coefficient = MethodType(_greedy_coefficient, self, self.__class__) # Register embedding into self.ambient() - embedding = SetMorphism(Hom(self,self.ambient()), lambda x: x.lift()) + embedding = SetMorphism(Hom(self, self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) def __copy__(self): @@ -1304,11 +1305,10 @@ def __copy__(self): sage: S1 == S2 False """ - n = self.rk() cv_names = self.initial_cluster_variable_names() coeff_names = self.coefficient_names() other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, - coefficient_names=coeff_names, scalars=self.scalars()) + coefficient_names=coeff_names, scalars=self.scalars()) other._F_poly_dict = copy(self._F_poly_dict) other._path_dict = copy(self._path_dict) S = copy(self.current_seed()) @@ -1349,7 +1349,7 @@ def __eq__(self, other): sage: A1 == B False """ - return type(self) == type(other) and self._B0 == other._B0 and self.ambient() == other.ambient() + return isinstance(other, ClusterAlgebra) and self._B0 == other._B0 and self.ambient() == other.ambient() def _repr_(self): r""" @@ -1363,10 +1363,10 @@ def _repr_(self): A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring """ var_names = self.initial_cluster_variable_names() - var_names = (" " if len(var_names)==1 else "s ") + ", ".join(var_names) + var_names = (" " if len(var_names) == 1 else "s ") + ", ".join(var_names) coeff_names = self.coefficient_names() - coeff_prefix = " and" +(" " if len(coeff_names) >0 else " no ") + "coefficient" - coeff = coeff_prefix + (" " if len(coeff_names)==1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names)>0 else "") + coeff_prefix = " and" +(" " if len(coeff_names) > 0 else " no ") + "coefficient" + coeff = coeff_prefix + (" " if len(coeff_names) == 1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names) > 0 else "") return "A Cluster Algebra with cluster variable" + var_names + coeff + "over " + repr(self.scalars()) def _an_element_(self): @@ -1445,12 +1445,12 @@ def _coerce_map_from_(self, other): if len(gen_s) == len(gen_o): f = self.ambient().coerce_map_from(other.ambient()) if f is not None: - perm = Permutation([ gen_s.index(self(f(v)))+1 for v in gen_o ]) + perm = Permutation([gen_s.index(self(f(v)))+1 for v in gen_o]) n = self.rk() M = self._B0[n:,:] m = M.nrows() B = block_matrix([[self.b_matrix(),-M.transpose()],[M,matrix(m)]]) - B.permute_rows_and_columns(perm,perm) + B.permute_rows_and_columns(perm, perm) return B.matrix_from_columns(range(other.rk())) == other._B0 # everything that is in the base can be coerced to self @@ -1600,7 +1600,7 @@ def cluster_variables_so_far(self): """ return map(self.cluster_variable, self.g_vectors_so_far()) - @cached_method(key=lambda a,b: tuple(b)) + @cached_method(key=lambda a, b: tuple(b)) def cluster_variable(self, g_vector): r""" Return the cluster variable with g-vector ``g_vector`` if it has been found. @@ -1723,8 +1723,8 @@ def find_g_vector(self, g_vector, depth=infinity): # all the seeds of self and did not find g_vector. # Do some house cleaning before failing self.reset_exploring_iterator() - raise ValueError("%s is not the g-vector of any cluster variable of a %s."%(str(g_vector),str(self)[2:])) - return copy(self._path_dict.get(g_vector,None)) + raise ValueError("%s is not the g-vector of any cluster variable of a %s."%(str(g_vector), str(self)[2:])) + return copy(self._path_dict.get(g_vector, None)) def ambient(self): r""" @@ -1941,20 +1941,19 @@ def seeds(self, **kwargs): # yield first seed yield seed - # some initialization + # keep track of depth depth_counter = 0 - n = self.rk() # do we mutate F-polynomials? mutating_F = kwargs.get('mutating_F', True) # which directions are we allowed to mutate into - allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(n)))) + allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(self.rk())))) # setup seeds storage cl = frozenset(seed.g_vectors()) clusters = {} - clusters[cl] = [ seed, copy(allowed_dirs) ] + clusters[cl] = [seed, copy(allowed_dirs)] # ready, set, go! gets_bigger = True @@ -1962,17 +1961,17 @@ def seeds(self, **kwargs): # remember if we got a new seed gets_bigger = False - for key in clusters.keys(): - sd, directions = clusters[key] + for cl in clusters.keys(): + sd, directions = clusters[cl] while directions: try: # we can mutate in some direction i = directions.pop() - new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) + new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: # we already had new_sd, make sure it does not mutate to sd during next round - j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) + j = map(tuple, clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) except ValueError: @@ -1981,8 +1980,8 @@ def seeds(self, **kwargs): # we got a new seed gets_bigger = True # next round do not mutate back to sd and make sure we only walk three sides of squares - new_directions = [ j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0 ] - clusters[new_cl] = [ new_sd, new_directions ] + new_directions = [j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0] + clusters[new_cl] = [new_sd, new_directions] yield new_sd except KeyboardInterrupt as e: if kwargs.get('catch_KeyboardInterrupt', False): @@ -2059,7 +2058,7 @@ def cluster_fan(self, depth=infinity): Rational polyhedral fan in 2-d lattice N """ seeds = self.seeds(depth=depth, mutating_F=False) - cones = map(lambda s: Cone(s.g_vectors()), seeds) + cones = [Cone(S.g_vectors()) for S in seeds] return Fan(cones) @_mutation_parse @@ -2096,7 +2095,7 @@ def mutate_initial(self, k): # save computed data old_F_poly_dict = copy(self._F_poly_dict) old_path_dict = copy(self._path_dict) - old_path_to_current = copy(self.current_seed().path_from_initial_seed()) + old_path_to_current = copy(self.current_seed().path_from_initial_seed()) # mutate initial exchange matrix B0 = copy(self._B0) @@ -2112,12 +2111,12 @@ def mutate_initial(self, k): coeff_names = self.coefficient_names() scalars = self.scalars() self.__init__(B0, cluster_variable_names=cv_names, - coefficient_names=coeff_names, scalars=scalars) + coefficient_names=coeff_names, scalars=scalars) # substitution data to compute new F-polynomials Ugen = self._U.gens() # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = [Ugen[k]**(-1) if j==k else Ugen[j]*Ugen[k]**max(B0[k,j],0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)] + F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k,j], 0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)] # restore computed data for old_g_vect in old_path_dict: @@ -2137,7 +2136,7 @@ def mutate_initial(self, k): #compute new F-polynomial if old_F_poly_dict.has_key(old_g_vect): - h = -min(0,old_g_vect[k]) + h = -min(0, old_g_vect[k]) new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] self._F_poly_dict[new_g_vect] = new_F_poly @@ -2244,7 +2243,7 @@ def greedy_element(self, d_vector): output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) return self.retract(x1**(-a1) * x2**(-a2) * output) -def _greedy_coefficient(self,d_vector,p,q): +def _greedy_coefficient(self, d_vector, p, q): r""" Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with denominator vector ``d_vector``. @@ -2266,7 +2265,7 @@ def _greedy_coefficient(self,d_vector,p,q): if p == 0 and q == 0: return Integer(1) sum1 = 0 - for k in range(1,p+1): + for k in range(1, p+1): bino = 0 if a2 - c*q + k - 1 >= k: bino = binomial(a2 - c*q + k - 1, k) From 86ed3f0ae98813cd3de1c3b2debc28e3213a0fa9 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 21 Sep 2016 11:21:32 +0200 Subject: [PATCH 163/191] Use python3 map --- src/sage/algebras/cluster_algebra.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b0473e5e74e..d13b86bdf51 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -321,6 +321,7 @@ from copy import copy from functools import wraps +from future_builtins import map from types import MethodType from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism @@ -923,7 +924,7 @@ def c_vectors(self): sage: S.c_vectors() [(1, 0, 0), (0, 1, 0), (0, 0, 1)] """ - return map(tuple, self._C.columns()) + return list(map(tuple, self._C.columns())) def g_matrix(self): r""" @@ -968,7 +969,7 @@ def g_vectors(self): sage: S.g_vectors() [(1, 0, 0), (0, 1, 0), (0, 0, 1)] """ - return map(tuple, self._G.columns()) + return list(map(tuple, self._G.columns())) def F_polynomial(self, j): r""" @@ -1598,7 +1599,7 @@ def cluster_variables_so_far(self): sage: A.cluster_variables_so_far() [x1, x0, (x1 + 1)/x0] """ - return map(self.cluster_variable, self.g_vectors_so_far()) + return list(map(self.cluster_variable, self.g_vectors_so_far())) @cached_method(key=lambda a, b: tuple(b)) def cluster_variable(self, g_vector): @@ -1790,7 +1791,7 @@ def gens(self): sage: A.gens() [x0, x1, x2, x3] """ - return map(self.retract, self.ambient().gens()) + return list(map(self.retract, self.ambient().gens())) def coefficient(self, j): r""" @@ -1825,7 +1826,7 @@ def coefficients(self): [] """ if isinstance(self.base(), LaurentPolynomialRing_generic): - return map(self.retract, self.base().gens()) + return list(map(self.retract, self.base().gens())) else: return [] @@ -1873,7 +1874,7 @@ def initial_cluster_variables(self): sage: A.initial_cluster_variables() [x0, x1] """ - return map(self.retract, self.ambient().gens()[:self.rk()]) + return list(map(self.retract, self.ambient().gens()[:self.rk()])) def initial_cluster_variable_names(self): r""" @@ -1971,7 +1972,7 @@ def seeds(self, **kwargs): new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: # we already had new_sd, make sure it does not mutate to sd during next round - j = map(tuple, clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) + j = clusters[new_cl][0].g_vectors().index(new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) except ValueError: From c41f4bf942fc9fea846c658161b2fca05a6de184 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 21 Sep 2016 11:41:23 +0200 Subject: [PATCH 164/191] Added methods to produce iterators on g-vectors and cluster variables --- src/sage/algebras/cluster_algebra.py | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index d13b86bdf51..f426e920719 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1575,6 +1575,59 @@ def b_matrix(self): n = self.rk() return copy(self._B0[:n,:]) + def g_vectors(self, mutating_F=True): + r""" + Return an itareator producing all the g-vectors of ``self``. + + INPUT: + + - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also; + for speed considerations you may want to disable this. + + ALGORITHM: + + This method does not usethe caching framework provided by ``self`` but + recomputes all the g-vectors from scratch. On the other hand it stores + the results so that other methods like :meth:`g_vectors_so_far` can + access them afterwards. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: len(list(A.g_vectors())) + 9 + """ + seeds = self.seeds(mutating_F=mutating_F) + found_so_far = set() + for g in next(seeds).g_vectors(): + found_so_far.add(g) + yield(g) + for S in seeds: + j = S.path_from_initial_seed()[-1] + g = S.g_vector(j) + if g not in found_so_far: + found_so_far.add(g) + yield(g) + + def cluster_variables(self): + r""" + Return an itareator producing all the cluster variables of ``self``. + + ALGORITHM: + + This method does not usethe caching framework provided by ``self`` but + recomputes all the cluster variables from scratch. On the other hand it + stores the results so that other methods like :meth:`cluster_variables_so_far` + can access them afterwards. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A',3]) + sage: len(list(A.cluster_variables())) + 9 + """ + return map(self.cluster_variable, self.g_vectors()) + def g_vectors_so_far(self): r""" Return a list of the g-vectors of cluster variables encountered so far. From a0d64b93b1940dd86ff1c48e0eac7a83268ed114 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 21 Sep 2016 11:48:57 +0200 Subject: [PATCH 165/191] Some fixes due to pylint --- src/sage/algebras/cluster_algebra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index f426e920719..8f1736c9a70 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1601,13 +1601,13 @@ def g_vectors(self, mutating_F=True): found_so_far = set() for g in next(seeds).g_vectors(): found_so_far.add(g) - yield(g) + yield g for S in seeds: j = S.path_from_initial_seed()[-1] g = S.g_vector(j) if g not in found_so_far: found_so_far.add(g) - yield(g) + yield g def cluster_variables(self): r""" @@ -2190,7 +2190,7 @@ def mutate_initial(self, k): #compute new F-polynomial if old_F_poly_dict.has_key(old_g_vect): - h = -min(0, old_g_vect[k]) + h = -min(0, old_g_vect[k]) new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] self._F_poly_dict[new_g_vect] = new_F_poly From e0454622299fdd1ad1a64993c8aa7ad1ceb7b9ad Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 19 Oct 2016 16:20:55 +0200 Subject: [PATCH 166/191] Implemented some of the suggested changes --- src/sage/algebras/cluster_algebra.py | 75 ++++++++++++++-------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 8f1736c9a70..a0a67b5f320 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -347,6 +347,7 @@ from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent from sage.structure.sage_object import SageObject +from six.move import range as xrange ############################################################################## # Helper functions @@ -361,7 +362,7 @@ def _mutation_parse(mutate): - mutate at all sinks/sources Possible things to implement later include: - - mutate at a cluster variariable + - mutate at a cluster variable - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) - urban renewals - other? @@ -486,7 +487,7 @@ def _div_(self, other): r""" Return the quotient of ``self`` and ``other``. - WARNING: + .. WARNING:: The result of a division is not guaranteed to be inside meth:`parent` therefore this method does not return an instance of @@ -553,13 +554,13 @@ def g_vector(self): sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): ... - ValueError: This element is not homogeneous. + ValueError: This element is not homogeneous """ components = self.homogeneous_components() if len(components) == 1: return components.keys()[0] else: - raise ValueError("This element is not homogeneous.") + raise ValueError("This element is not homogeneous") def F_polynomial(self): r""" @@ -574,7 +575,7 @@ def F_polynomial(self): sage: sum(A.initial_cluster_variables()).F_polynomial() Traceback (most recent call last): ... - ValueError: This element is not homogeneous. + ValueError: This element is not homogeneous """ if self.is_homogeneous(): subs_dict = dict() @@ -585,7 +586,7 @@ def F_polynomial(self): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) return self.lift().substitute(subs_dict) else: - raise ValueError("This element is not homogeneous.") + raise ValueError("This element is not homogeneous") def is_homogeneous(self): r""" @@ -655,7 +656,7 @@ def __init__(self, B, C, G, parent, **kwargs): - ``path`` -- a list (default ``[]``): the mutation sequence from the initial seed of ``parent`` to ``self``. - WARNING: + .. WARNING:: Seeds should **not** be created manually: no test is performed to assert that they are built from consistent data nor that they @@ -814,7 +815,7 @@ def depth(self): r""" Return the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. - WARNING: + .. WARNING:: This is the length of the mutation sequence returned by :meth:`path_from_initial_seed` which need not be the shortest @@ -840,7 +841,7 @@ def path_from_initial_seed(self): r""" Return a mutation sequence from the initial seed of :meth:`parent` to ``self``. - WARNING: + .. WARNING:: This is the path used to compute ``self`` and it does not have to be the shortest possible. @@ -1057,12 +1058,12 @@ def mutate(self, k, mutating_F=True): sage: S.mutate(5) Traceback (most recent call last): ... - ValueError: Cannot mutate in direction 5. + ValueError: Cannot mutate in direction 5 """ n = self.parent().rk() if k not in xrange(n): - raise ValueError('Cannot mutate in direction ' + str(k) + '.') + raise ValueError('Cannot mutate in direction ' + str(k)) # store new mutation path if self._path != [] and self._path[-1] == k: @@ -1088,11 +1089,11 @@ def mutate(self, k, mutating_F=True): # path to new g-vector (we store the shortest encountered so far) g_vector = self.g_vector(k) - if not self.parent()._path_dict.has_key(g_vector) or len(self.parent()._path_dict[g_vector]) > len(self._path): + if not g_vector in self.parent()._path_dict or len(self.parent()._path_dict[g_vector]) > len(self._path): self.parent()._path_dict[g_vector] = copy(self._path) # compute F-polynomials - if mutating_F and not self.parent()._F_poly_dict.has_key(g_vector): + if mutating_F and not g_vector in self.parent()._F_poly_dict: self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) # compute new C-matrix @@ -1504,12 +1505,12 @@ def set_current_seed(self, seed): sage: A.set_current_seed(B.initial_seed()) Traceback (most recent call last): ... - ValueError: This is not a seed in this cluster algebra. + ValueError: This is not a seed in this cluster algebra """ if self.contains_seed(seed): self._seed = seed else: - raise ValueError("This is not a seed in this cluster algebra.") + raise ValueError("This is not a seed in this cluster algebra") def contains_seed(self, seed): r""" @@ -1577,7 +1578,7 @@ def b_matrix(self): def g_vectors(self, mutating_F=True): r""" - Return an itareator producing all the g-vectors of ``self``. + Return an iterator producing all the g-vectors of ``self``. INPUT: @@ -1615,7 +1616,7 @@ def cluster_variables(self): ALGORITHM: - This method does not usethe caching framework provided by ``self`` but + This method does not use the caching framework provided by ``self`` but recomputes all the cluster variables from scratch. On the other hand it stores the results so that other methods like :meth:`cluster_variables_so_far` can access them afterwards. @@ -1710,13 +1711,13 @@ def F_polynomial(self, g_vector): sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... - KeyError: 'The g-vector (-1, 1) has not been found yet.' + KeyError: 'The g-vector (-1, 1) has not been found yet' sage: A.initial_seed().mutate(0,mutating_F=False) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... - KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet. - You can compute it by mutating from the initial seed along the sequence [0].' + KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet; + you can compute it by mutating from the initial seed along the sequence [0]' sage: A.initial_seed().mutate(0) sage: A.F_polynomial((-1, 1)) u0 + 1 @@ -1726,12 +1727,12 @@ def F_polynomial(self, g_vector): return self._F_poly_dict[g_vector] except KeyError: if g_vector in self._path_dict: - msg = "The F-polynomial with g-vector %s has not been computed yet. "%str(g_vector) - msg += "You can compute it by mutating from the initial seed along the sequence " - msg += str(self._path_dict[g_vector]) + "." + msg = "The F-polynomial with g-vector %s has not been computed yet; "%str(g_vector) + msg += "you can compute it by mutating from the initial seed along the sequence " + msg += str(self._path_dict[g_vector]) raise KeyError(msg) else: - raise KeyError("The g-vector %s has not been found yet."%str(g_vector)) + raise KeyError("The g-vector %s has not been found yet"%str(g_vector)) def find_g_vector(self, g_vector, depth=infinity): r""" @@ -1761,7 +1762,7 @@ def find_g_vector(self, g_vector, depth=infinity): sage: A.find_g_vector((1, 1), depth=4) Traceback (most recent call last): ... - ValueError: (1, 1) is not the g-vector of any cluster variable of a Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring. + ValueError: (1, 1) is not the g-vector of any cluster variable of a Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring """ g_vector = tuple(g_vector) while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: @@ -1777,7 +1778,7 @@ def find_g_vector(self, g_vector, depth=infinity): # all the seeds of self and did not find g_vector. # Do some house cleaning before failing self.reset_exploring_iterator() - raise ValueError("%s is not the g-vector of any cluster variable of a %s."%(str(g_vector), str(self)[2:])) + raise ValueError("%s is not the g-vector of any cluster variable of a %s"%(str(g_vector), str(self)[2:])) return copy(self._path_dict.get(g_vector, None)) def ambient(self): @@ -1863,7 +1864,7 @@ def coefficient(self, j): if isinstance(self.base(), LaurentPolynomialRing_generic): return self.retract(self.base().gen(j)) else: - raise ValueError("generator not defined") + raise ValueError("Generator not defined") def coefficients(self): r""" @@ -2144,7 +2145,7 @@ def mutate_initial(self, k): """ n = self.rk() if k not in xrange(n): - raise ValueError('Cannot mutate in direction ' + str(k) + '.') + raise ValueError('Cannot mutate in direction ' + str(k)) # save computed data old_F_poly_dict = copy(self._F_poly_dict) @@ -2189,7 +2190,7 @@ def mutate_initial(self, k): self._path_dict[new_g_vect] = new_path #compute new F-polynomial - if old_F_poly_dict.has_key(old_g_vect): + if old_g_vect in old_F_poly_dict: h = -min(0, old_g_vect[k]) new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] self._F_poly_dict[new_g_vect] = new_F_poly @@ -2211,9 +2212,9 @@ def upper_cluster_algebra(self): sage: A.upper_cluster_algebra() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet. + NotImplementedError: Not implemented yet """ - raise NotImplementedError("Not implemented yet.") + raise NotImplementedError("Not implemented yet") def upper_bound(self): r""" @@ -2225,9 +2226,9 @@ def upper_bound(self): sage: A.upper_bound() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet. + NotImplementedError: Not implemented yet """ - raise NotImplementedError("Not implemented yet.") + raise NotImplementedError("Not implemented yet") def lower_bound(self): r""" @@ -2239,9 +2240,9 @@ def lower_bound(self): sage: A.lower_bound() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet. + NotImplementedError: Not implemented yet """ - raise NotImplementedError("Not implemented yet.") + raise NotImplementedError("Not implemented yet") def theta_basis_element(self, g_vector): r""" @@ -2253,9 +2254,9 @@ def theta_basis_element(self, g_vector): sage: A.theta_basis_element((1,0,0,0)) Traceback (most recent call last): ... - NotImplementedError: Not implemented yet. + NotImplementedError: Not implemented yet """ - raise NotImplementedError("Not implemented yet.") + raise NotImplementedError("Not implemented yet") #### # Methods only defined for special cases From a9675cb8e6f7fdb58b9b5a235840c50133b2c2eb Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 19 Oct 2016 16:32:27 +0200 Subject: [PATCH 167/191] missing s --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index a0a67b5f320..3da52d48fdd 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -347,7 +347,7 @@ from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent from sage.structure.sage_object import SageObject -from six.move import range as xrange +from six.moves import range as xrange ############################################################################## # Helper functions From c56edf9ce4ebf41faa90866178449f94b465ed0a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 19 Oct 2016 17:15:07 +0200 Subject: [PATCH 168/191] Pep8 fixes --- src/sage/algebras/cluster_algebra.py | 380 ++++++++++++++------------- 1 file changed, 195 insertions(+), 185 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 3da52d48fdd..b8ce7d0276f 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -85,7 +85,7 @@ We begin by creating a simple cluster algebra and printing its initial exchange matrix:: - sage: A = ClusterAlgebra(['A',2]); A + sage: A = ClusterAlgebra(['A', 2]); A A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring sage: A.b_matrix() [ 0 1] @@ -106,9 +106,9 @@ Simple operations among cluster variables behave as expected:: - sage: s = A.cluster_variable((0,-1)); s + sage: s = A.cluster_variable((0, -1)); s (x0 + 1)/x1 - sage: t = A.cluster_variable((-1,1)); t + sage: t = A.cluster_variable((-1, 1)); t (x1 + 1)/x0 sage: t + s (x0^2 + x1^2 + x0 + x1)/(x0*x1) @@ -143,7 +143,7 @@ sage: A.rk() == 2 and A.coefficients() == [] True - sage: A.greedy_element((1,1)) + sage: A.greedy_element((1, 1)) (x0 + x1 + 1)/(x0*x1) sage: _ == t*s False @@ -151,12 +151,12 @@ not surprising since there is no cluster in ``A`` containing both ``t`` and ``s``:: sage: seeds = A.seeds(mutating_F=false) - sage: [ S for S in seeds if (0,-1) in S and (-1,1) in S ] + sage: [ S for S in seeds if (0, -1) in S and (-1, 1) in S ] [] indeed:: - sage: A.greedy_element((1,1)) == A.cluster_variable((-1,0)) + sage: A.greedy_element((1, 1)) == A.cluster_variable((-1, 0)) True Disabling F-polynomials in the computation just done was redundant because we @@ -170,11 +170,11 @@ Traceback (most recent call last): ... AttributeError: 'ClusterAlgebra_with_category.element_class' object has no attribute 'g_vector' - sage: A = ClusterAlgebra(['A',2], principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.explore_to_depth(infinity) - sage: s = A.cluster_variable((0,-1)); s + sage: s = A.cluster_variable((0, -1)); s (x0*y1 + 1)/x1 - sage: t = A.cluster_variable((-1,1)); t + sage: t = A.cluster_variable((-1, 1)); t (x1 + y0)/x0 sage: (t*s).g_vector() (-1, 0) @@ -190,7 +190,7 @@ Each cluster algebra is endowed with a reference to a current seed; it could be useful to assign a name to it:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: len(A.g_vectors_so_far()) 4 sage: A.current_seed() @@ -229,7 +229,7 @@ [(x1 + 1)/x0, x1, x2, x3] sage: S.mutate('sinks'); S The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2] - sage: S.mutate([2,3,2,1,0]); S + sage: S.mutate([2, 3, 2, 1, 0]); S The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 3, 2, 1, 0] sage: S.g_vectors() [(0, 1, -2, 0), (-1, 2, -2, 0), (0, 1, -1, 0), (0, 0, 0, -1)] @@ -249,7 +249,7 @@ update ``A.current_seed()``:: sage: S1 = A.initial_seed() - sage: S1.mutate([2,1,3]) + sage: S1.mutate([2, 1, 3]) sage: len(A.g_vectors_so_far()) 11 sage: S1 == A.current_seed() @@ -283,9 +283,9 @@ We can manually freeze cluster variables and get coercions in between the two algebras:: - sage: A = ClusterAlgebra(['F',4]); A + sage: A = ClusterAlgebra(['F', 4]); A A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring - sage: B = ClusterAlgebra(A.b_matrix().matrix_from_columns([0,1,2]),coefficient_prefix='x'); B + sage: B = ClusterAlgebra(A.b_matrix().matrix_from_columns([0, 1, 2]), coefficient_prefix='x'); B A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3 over Integer Ring sage: A.has_coerce_map_from(B) True @@ -305,7 +305,7 @@ A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring sage: A.b_matrix() == B.b_matrix() False - sage: map(lambda (X,Y): X.has_coerce_map_from(Y), [(A,B),(B,A)]) + sage: map(lambda (X, Y): X.has_coerce_map_from(Y), [(A, B), (B, A)]) [False, False] """ @@ -352,6 +352,8 @@ ############################################################################## # Helper functions ############################################################################## + + def _mutation_parse(mutate): r""" Preparse input for mutation functions. @@ -369,13 +371,13 @@ def _mutation_parse(mutate): EXAMPLES:: - sage: A = ClusterAlgebra(['E',6]) + sage: A = ClusterAlgebra(['E', 6]) sage: S = A.current_seed() - sage: S.mutate(1,inplace=False) # indirect doctest + sage: S.mutate(1, inplace=False) # indirect doctest The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating in direction 1 - sage: S.mutate([1,2,3],inplace=False) # indirect doctest + sage: S.mutate([1, 2, 3], inplace=False) # indirect doctest The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] - sage: S.mutate('sinks',inplace=False) # indirect doctest + sage: S.mutate('sinks', inplace=False) # indirect doctest The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2, 4] """ doc = mutate.__doc__.split("INPUT:") @@ -412,7 +414,7 @@ def mutate_wrapper(self, direction, **kwargs): try: seq = iter(direction) except TypeError: - seq = iter((direction,)) + seq = iter((direction, )) for k in seq: mutate(to_mutate, k, **kwargs) @@ -426,6 +428,7 @@ def mutate_wrapper(self, direction, **kwargs): # Elements of a cluster algebra ############################################################################## + class ClusterAlgebraElement(ElementWrapper): def __init__(self, parent, value): @@ -440,9 +443,9 @@ def __init__(self, parent, value): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraElement - sage: ClusterAlgebraElement(A,1) + sage: ClusterAlgebraElement(A, 1) 1 """ ElementWrapper.__init__(self, parent=parent, value=value) @@ -465,7 +468,7 @@ def _add_(self, other): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: A.an_element() + A.an_element() 2*x0 """ @@ -477,7 +480,7 @@ def _neg_(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: -A.an_element() -x0 """ @@ -495,7 +498,7 @@ def _div_(self, other): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: x = A.an_element() sage: x/x 1 @@ -514,9 +517,9 @@ def d_vector(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4], principal_coefficients=True) + sage: A = ClusterAlgebra(['F', 4], principal_coefficients=True) sage: A.current_seed().mutate([0, 2, 1]) - sage: x = A.cluster_variable((-1, 2, -2, 2)) * A.cluster_variable((0,0,0,1))**2 + sage: x = A.cluster_variable((-1, 2, -2, 2)) * A.cluster_variable((0, 0, 0, 1))**2 sage: x.d_vector() (1, 1, 2, -2) """ @@ -530,7 +533,7 @@ def _repr_(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4], principal_coefficients=True) + sage: A = ClusterAlgebra(['F', 4], principal_coefficients=True) sage: A.current_seed().mutate([0, 2, 1]) sage: A.cluster_variable((-1, 2, -2, 2)) (x0*x2^2*y0*y1*y2^2 + x1^3*x3^2 + x1^2*x3^2*y0 + 2*x1^2*x3*y2 + 2*x1*x3*y0*y2 + x1*y2^2 + y0*y2^2)/(x0*x1*x2^2) @@ -542,14 +545,15 @@ def _repr_(self): # Methods not always defined #### + def g_vector(self): r""" Return the g-vector of ``self``. EXAMPLES:: - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - sage: A.cluster_variable((1,0)).g_vector() == (1,0) + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: A.cluster_variable((1, 0)).g_vector() == (1, 0) True sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): @@ -562,14 +566,15 @@ def g_vector(self): else: raise ValueError("This element is not homogeneous") + def F_polynomial(self): r""" Return the F-polynomial of ``self``. EXAMPLES:: - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) sage: S = A.initial_seed() - sage: S.mutate([0,1,0]) + sage: S.mutate([0, 1, 0]) sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) True sage: sum(A.initial_cluster_variables()).F_polynomial() @@ -588,21 +593,23 @@ def F_polynomial(self): else: raise ValueError("This element is not homogeneous") + def is_homogeneous(self): r""" Return ``True`` if ``self`` is a homogeneous element of ``self.parent()``. EXAMPLES:: - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - sage: A.cluster_variable((1,0)).is_homogeneous() + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: A.cluster_variable((1, 0)).is_homogeneous() True - sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) + sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) sage: x.is_homogeneous() False """ return len(self.homogeneous_components()) == 1 + def homogeneous_components(self): r""" Return a dictionary of the homogeneous components of ``self``. @@ -614,8 +621,8 @@ def homogeneous_components(self): EXAMPLES:: - sage: A = ClusterAlgebra(['B',2],principal_coefficients=True) - sage: x = A.cluster_variable((1,0)) + A.cluster_variable((0,1)) + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ @@ -667,9 +674,9 @@ def __init__(self, B, C, G, parent, **kwargs): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed - sage: ClusterAlgebraSeed(A.b_matrix(),identity_matrix(4),identity_matrix(4),A,path=[1,2,3]) + sage: ClusterAlgebraSeed(A.b_matrix(), identity_matrix(4), identity_matrix(4), A, path=[1, 2, 3]) The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] """ self._B = copy(B) @@ -684,7 +691,7 @@ def __copy__(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = copy(A.current_seed()) sage: S == A.current_seed() True @@ -715,16 +722,16 @@ def __eq__(self, other): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = copy(A.current_seed()) - sage: S.mutate([0,2,0]) + sage: S.mutate([0, 2, 0]) sage: S == A.current_seed() False sage: S.mutate(2) sage: S == A.current_seed() True - sage: B = ClusterAlgebra(['B',2],principal_coefficients=True) + sage: B = ClusterAlgebra(['B', 2], principal_coefficients=True) sage: S = B.current_seed() sage: S.mutate(0) sage: S == B.current_seed() @@ -742,13 +749,13 @@ def __contains__(self, element): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() - sage: (1,0,0) in S + sage: (1, 0, 0) in S True - sage: (1,1,0) in S + sage: (1, 1, 0) in S False - sage: A.cluster_variable((1,0,0)) in S + sage: A.cluster_variable((1, 0, 0)) in S True """ if isinstance(element, ClusterAlgebraElement): @@ -771,7 +778,7 @@ def __hash__(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: hash(S) 6108559638409052534 @@ -784,7 +791,7 @@ def _repr_(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.current_seed(); S The initial seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring sage: S.mutate(0); S @@ -793,11 +800,11 @@ def _repr_(self): The seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] """ if self._path == []: - return "The initial seed of a %s"%str(self.parent())[2:] + return "The initial seed of a %s" % str(self.parent())[2:] elif len(self._path) == 1: - return "The seed of a %s obtained from the initial by mutating in direction %s"%(str(self.parent())[2:], str(self._path[0])) + return "The seed of a %s obtained from the initial by mutating in direction %s" % (str(self.parent())[2:], str(self._path[0])) else: - return "The seed of a %s obtained from the initial by mutating along the sequence %s"%(str(self.parent())[2:], str(self._path)) + return "The seed of a %s obtained from the initial by mutating along the sequence %s" % (str(self.parent())[2:], str(self._path)) def parent(self): r""" @@ -805,7 +812,7 @@ def parent(self): EXAMPLES:: - sage: A = ClusterAlgebra(['B',3]) + sage: A = ClusterAlgebra(['B', 3]) sage: A.current_seed().parent() == A True """ @@ -823,9 +830,9 @@ def depth(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: S1 = A.initial_seed() - sage: S1.mutate([0,1,0,1]) + sage: S1.mutate([0, 1, 0, 1]) sage: S1.depth() 4 sage: S2 = A.initial_seed() @@ -848,9 +855,9 @@ def path_from_initial_seed(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: S1 = A.initial_seed() - sage: S1.mutate([0,1,0,1]) + sage: S1.mutate([0, 1, 0, 1]) sage: S1.path_from_initial_seed() [0, 1, 0, 1] sage: S2 = A.initial_seed() @@ -868,7 +875,7 @@ def b_matrix(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.b_matrix() [ 0 1 0] @@ -883,7 +890,7 @@ def c_matrix(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.c_matrix() [1 0 0] @@ -902,7 +909,7 @@ def c_vector(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.c_vector(0) (1, 0, 0) @@ -920,7 +927,7 @@ def c_vectors(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.c_vectors() [(1, 0, 0), (0, 1, 0), (0, 0, 1)] @@ -933,7 +940,7 @@ def g_matrix(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.g_matrix() [1 0 0] @@ -952,7 +959,7 @@ def g_vector(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.g_vector(0) (1, 0, 0) @@ -965,7 +972,7 @@ def g_vectors(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.g_vectors() [(1, 0, 0), (0, 1, 0), (0, 0, 1)] @@ -982,7 +989,7 @@ def F_polynomial(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.F_polynomial(0) 1 @@ -995,7 +1002,7 @@ def F_polynomials(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.F_polynomials() [1, 1, 1] @@ -1012,7 +1019,7 @@ def cluster_variable(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.cluster_variable(0) x0 @@ -1028,7 +1035,7 @@ def cluster_variables(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: S.cluster_variables() [x0, x1, x2] @@ -1051,7 +1058,7 @@ def mutate(self, k, mutating_F=True): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: S = A.initial_seed() sage: S.mutate(0); S The seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 @@ -1083,24 +1090,24 @@ def mutate(self, k, mutating_F=True): # compute new G-matrix J = identity_matrix(n) for j in xrange(n): - J[j,k] += max(0, -eps*self._B[j,k]) - J[k,k] = -1 + J[j, k] += max(0, -eps*self._B[j, k]) + J[k, k] = -1 self._G = self._G*J # path to new g-vector (we store the shortest encountered so far) g_vector = self.g_vector(k) - if not g_vector in self.parent()._path_dict or len(self.parent()._path_dict[g_vector]) > len(self._path): + if g_vector not in self.parent()._path_dict or len(self.parent()._path_dict[g_vector]) > len(self._path): self.parent()._path_dict[g_vector] = copy(self._path) # compute F-polynomials - if mutating_F and not g_vector in self.parent()._F_poly_dict: + if mutating_F and g_vector not in self.parent()._F_poly_dict: self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) # compute new C-matrix J = identity_matrix(n) for j in xrange(n): - J[k,j] += max(0, eps*self._B[k,j]) - J[k,k] = -1 + J[k, j] += max(0, eps*self._B[k, j]) + J[k, k] = -1 self._C = self._C*J # compute new B-matrix @@ -1127,30 +1134,31 @@ def _mutated_F(self, k, old_g_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: S = A.initial_seed() sage: S.mutate(0) - sage: S._mutated_F(0,(1,0)) + sage: S._mutated_F(0, (1, 0)) u0 + 1 """ alg = self.parent() pos = alg._U(1) neg = alg._U(1) for j in xrange(alg.rk()): - if self._C[j,k] > 0: - pos *= alg._U.gen(j)**self._C[j,k] + if self._C[j, k] > 0: + pos *= alg._U.gen(j)**self._C[j, k] else: - neg *= alg._U.gen(j)**(-self._C[j,k]) - if self._B[j,k] > 0: - pos *= self.F_polynomial(j)**self._B[j,k] - elif self._B[j,k] < 0: - neg *= self.F_polynomial(j)**(-self._B[j,k]) + neg *= alg._U.gen(j)**(-self._C[j, k]) + if self._B[j, k] > 0: + pos *= self.F_polynomial(j)**self._B[j, k] + elif self._B[j, k] < 0: + neg *= self.F_polynomial(j)**(-self._B[j, k]) return (pos+neg)/alg.F_polynomial(old_g_vector) ############################################################################## # Cluster algebras ############################################################################## + class ClusterAlgebra(Parent): Element = ClusterAlgebraElement @@ -1188,26 +1196,26 @@ def __init__(self, data, **kwargs): EXAMPLES:: - sage: B = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) + sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)]) sage: A = ClusterAlgebra(B); A A Cluster Algebra with cluster variables x0, x1, x2, x3 and coefficients y0, y1 over Integer Ring sage: A.gens() [x0, x1, x2, x3, y0, y1] - sage: A = ClusterAlgebra(['A',2]); A + sage: A = ClusterAlgebra(['A', 2]); A A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring - sage: A = ClusterAlgebra(['A',2], principal_coefficients=True); A.gens() + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A.gens() [x0, x1, y0, y1] - sage: A = ClusterAlgebra(['A',2], principal_coefficients=True, coefficient_prefix='x'); A.gens() + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x'); A.gens() [x0, x1, x2, x3] - sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, cluster_variable_names=['a','b','c']); A.gens() + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b', 'c']); A.gens() [a, b, c, y0, y1, y2] - sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, cluster_variable_names=['a','b']) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b']) Traceback (most recent call last): ... ValueError: cluster_variable_names should be a list of 3 valid variable names - sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b','c']); A.gens() + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b', 'c']); A.gens() [x0, x1, x2, a, b, c] - sage: A = ClusterAlgebra(['A',3], principal_coefficients=True, coefficient_names=['a','b']) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b']) Traceback (most recent call last): ... ValueError: coefficient_names should be a list of 3 valid variable names @@ -1219,15 +1227,15 @@ def __init__(self, data, **kwargs): if kwargs.get('principal_coefficients', False): M0 = I else: - M0 = Q.b_matrix()[n:,:] - B0 = block_matrix([[Q.b_matrix()[:n,:]],[M0]]) + M0 = Q.b_matrix()[n:, :] + B0 = block_matrix([[Q.b_matrix()[:n, :]], [M0]]) m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more # natural ZZ. The reason is that _mutated_F is faster if we do not cast # the result to polynomials but then we get "rational" coefficients - self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) + self._U = PolynomialRing(QQ, ['u%s' % i for i in xrange(n)]) # Storage for computed data self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) @@ -1237,7 +1245,7 @@ def __init__(self, data, **kwargs): variables_prefix = kwargs.get('cluster_variable_prefix', 'x') variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in xrange(n)])) if len(variables) != n: - raise ValueError("cluster_variable_names should be a list of %d valid variable names"%n) + raise ValueError("cluster_variable_names should be a list of %d valid variable names" % n) # Determine scalars scalars = kwargs.get('scalars', ZZ) @@ -1251,7 +1259,7 @@ def __init__(self, data, **kwargs): offset = 0 coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in xrange(offset, m+offset)])) if len(coefficients) != m: - raise ValueError("coefficient_names should be a list of %d valid variable names"%m) + raise ValueError("coefficient_names should be a list of %d valid variable names" % m) base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars @@ -1262,8 +1270,8 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions - self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) - self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n+m)])) for j in xrange(n)]) + self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i, j] for i in xrange(m)])) for j in xrange(n)]) + self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in xrange(n+m)])) for j in xrange(n)]) # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1293,7 +1301,7 @@ def __copy__(self): EXAMPLES:: - sage: A1 = ClusterAlgebra(['A',3]) + sage: A1 = ClusterAlgebra(['A', 3]) sage: A2 = copy(A1) sage: A2 == A1 True @@ -1335,19 +1343,19 @@ def __eq__(self, other): EXAMPLES:: - sage: A1 = ClusterAlgebra(['A',3]) + sage: A1 = ClusterAlgebra(['A', 3]) sage: A2 = copy(A1) sage: A1 is not A2 True sage: A1 == A2 True - sage: A1.current_seed().mutate([0,1,2]) + sage: A1.current_seed().mutate([0, 1, 2]) sage: A1 == A2 True - sage: A3 = ClusterAlgebra(['A',3],principal_coefficients=True) + sage: A3 = ClusterAlgebra(['A', 3], principal_coefficients=True) sage: A1 == A3 False - sage: B = ClusterAlgebra(['B',3]) + sage: B = ClusterAlgebra(['B', 3]) sage: A1 == B False """ @@ -1359,15 +1367,15 @@ def _repr_(self): EXAMPLES:: - sage: A = ClusterAlgebra(matrix(1),principal_coefficients=True); A + sage: A = ClusterAlgebra(matrix(1), principal_coefficients=True); A A Cluster Algebra with cluster variable x0 and coefficient y0 over Integer Ring - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring """ var_names = self.initial_cluster_variable_names() var_names = (" " if len(var_names) == 1 else "s ") + ", ".join(var_names) coeff_names = self.coefficient_names() - coeff_prefix = " and" +(" " if len(coeff_names) > 0 else " no ") + "coefficient" + coeff_prefix = " and" + (" " if len(coeff_names) > 0 else " no ") + "coefficient" coeff = coeff_prefix + (" " if len(coeff_names) == 1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names) > 0 else "") return "A Cluster Algebra with cluster variable" + var_names + coeff + "over " + repr(self.scalars()) @@ -1377,7 +1385,7 @@ def _an_element_(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.an_element() x0 """ @@ -1400,8 +1408,8 @@ def _coerce_map_from_(self, other): EXAMPLES:: - sage: B1 = matrix([(0, 1, 0, 0),(-1, 0, -1, 0),(0, 1, 0, 1),(0, 0, -2, 0),(-1, 0, 0, 0),(0, -1, 0, 0)]) - sage: B2 = B1.matrix_from_columns([0,1,2]) + sage: B1 = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)]) + sage: B2 = B1.matrix_from_columns([0, 1, 2]) sage: A1 = ClusterAlgebra(B1, coefficient_prefix='x') sage: A2 = ClusterAlgebra(B2, coefficient_prefix='x') sage: A1.has_coerce_map_from(A2) @@ -1414,14 +1422,14 @@ def _coerce_map_from_(self, other): sage: S = A1.initial_seed(); S.mutate([0, 2, 1]) sage: S.cluster_variable(1) == f(A2.cluster_variable((-1, 1, -1))) True - sage: B3 = B1.matrix_from_columns([1,2,3]); B3 + sage: B3 = B1.matrix_from_columns([1, 2, 3]); B3 [ 1 0 0] [ 0 -1 0] [ 1 0 1] [ 0 -2 0] [ 0 0 0] [-1 0 0] - sage: G = PermutationGroup(['(1,2,3,4)']) + sage: G = PermutationGroup(['(1, 2, 3, 4)']) sage: B3.permute_rows(G.gen(0)); B3 [ 0 -1 0] [ 1 0 1] @@ -1429,16 +1437,16 @@ def _coerce_map_from_(self, other): [ 1 0 0] [ 0 0 0] [-1 0 0] - sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1','x2','x3'], coefficient_names=['x0','x4','x5']) + sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1', 'x2', 'x3'], coefficient_names=['x0', 'x4', 'x5']) sage: A1.has_coerce_map_from(A3) True sage: g = A1.coerce_map_from(A3) - sage: A3.find_g_vector((1,-2,2)) + sage: A3.find_g_vector((1, -2, 2)) [1, 2, 1, 0] - sage: map(lambda x: x-1,map(G.gen(0),map(lambda x: x+1,[1, 2, 1, 0]))) + sage: map(lambda x: x-1, map(G.gen(0), map(lambda x: x+1, [1, 2, 1, 0]))) [2, 3, 2, 1] sage: S = A1.initial_seed(); S.mutate([2, 3, 2, 1]) - sage: S.cluster_variable(1) == g(A3.cluster_variable((1,-2,2))) + sage: S.cluster_variable(1) == g(A3.cluster_variable((1, -2, 2))) True """ if isinstance(other, ClusterAlgebra): @@ -1449,9 +1457,9 @@ def _coerce_map_from_(self, other): if f is not None: perm = Permutation([gen_s.index(self(f(v)))+1 for v in gen_o]) n = self.rk() - M = self._B0[n:,:] + M = self._B0[n:, :] m = M.nrows() - B = block_matrix([[self.b_matrix(),-M.transpose()],[M,matrix(m)]]) + B = block_matrix([[self.b_matrix(), -M.transpose()], [M, matrix(m)]]) B.permute_rows_and_columns(perm, perm) return B.matrix_from_columns(range(other.rk())) == other._B0 @@ -1464,7 +1472,7 @@ def rk(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring sage: A.rk() 2 @@ -1477,7 +1485,7 @@ def current_seed(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.current_seed() The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ @@ -1493,15 +1501,15 @@ def set_current_seed(self, seed): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: S = copy(A.current_seed()) - sage: S.mutate([0,1,0]) + sage: S.mutate([0, 1, 0]) sage: A.current_seed() == S False sage: A.set_current_seed(S) sage: A.current_seed() == S True - sage: B = ClusterAlgebra(['B',2]) + sage: B = ClusterAlgebra(['B', 2]) sage: A.set_current_seed(B.initial_seed()) Traceback (most recent call last): ... @@ -1522,7 +1530,7 @@ def contains_seed(self, seed): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True); A + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring sage: S = copy(A.current_seed()) sage: A.contains_seed(S) @@ -1538,8 +1546,8 @@ def reset_current_seed(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) - sage: A.current_seed().mutate([1,0]) + sage: A = ClusterAlgebra(['A', 2]) + sage: A.current_seed().mutate([1, 0]) sage: A.current_seed() == A.initial_seed() False sage: A.reset_current_seed() @@ -1554,7 +1562,7 @@ def initial_seed(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.initial_seed() The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ @@ -1568,13 +1576,13 @@ def b_matrix(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.b_matrix() [ 0 1] [-1 0] """ n = self.rk() - return copy(self._B0[:n,:]) + return copy(self._B0[:n, :]) def g_vectors(self, mutating_F=True): r""" @@ -1594,7 +1602,7 @@ def g_vectors(self, mutating_F=True): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: len(list(A.g_vectors())) 9 """ @@ -1623,7 +1631,7 @@ def cluster_variables(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: len(list(A.cluster_variables())) 9 """ @@ -1635,7 +1643,7 @@ def g_vectors_so_far(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.current_seed().mutate(0) sage: A.g_vectors_so_far() [(0, 1), (1, 0), (-1, 1)] @@ -1648,7 +1656,7 @@ def cluster_variables_so_far(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.current_seed().mutate(0) sage: A.cluster_variables_so_far() [x1, x0, (x1 + 1)/x0] @@ -1672,9 +1680,9 @@ def cluster_variable(self, g_vector): EXAMPLE:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.initial_seed().mutate(0) - sage: A.cluster_variable((-1,1)) + sage: A.cluster_variable((-1, 1)) (x1 + 1)/x0 """ g_vector = tuple(g_vector) @@ -1690,7 +1698,7 @@ def F_polynomials_so_far(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.current_seed().mutate(0) sage: A.F_polynomials_so_far() [1, 1, u0 + 1] @@ -1707,12 +1715,12 @@ def F_polynomial(self, g_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... KeyError: 'The g-vector (-1, 1) has not been found yet' - sage: A.initial_seed().mutate(0,mutating_F=False) + sage: A.initial_seed().mutate(0, mutating_F=False) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... @@ -1727,12 +1735,12 @@ def F_polynomial(self, g_vector): return self._F_poly_dict[g_vector] except KeyError: if g_vector in self._path_dict: - msg = "The F-polynomial with g-vector %s has not been computed yet; "%str(g_vector) + msg = "The F-polynomial with g-vector %s has not been computed yet; " % str(g_vector) msg += "you can compute it by mutating from the initial seed along the sequence " msg += str(self._path_dict[g_vector]) raise KeyError(msg) else: - raise KeyError("The g-vector %s has not been found yet"%str(g_vector)) + raise KeyError("The g-vector %s has not been found yet" % str(g_vector)) def find_g_vector(self, g_vector, depth=infinity): r""" @@ -1754,7 +1762,7 @@ def find_g_vector(self, g_vector, depth=infinity): EXAMPLES:: - sage: A = ClusterAlgebra(['G',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['G', 2], principal_coefficients=True) sage: A.find_g_vector((-2, 3), depth=2) sage: A.find_g_vector((-2, 3), depth=3) [0, 1, 0] @@ -1778,7 +1786,7 @@ def find_g_vector(self, g_vector, depth=infinity): # all the seeds of self and did not find g_vector. # Do some house cleaning before failing self.reset_exploring_iterator() - raise ValueError("%s is not the g-vector of any cluster variable of a %s"%(str(g_vector), str(self)[2:])) + raise ValueError("%s is not the g-vector of any cluster variable of a %s" % (str(g_vector), str(self)[2:])) return copy(self._path_dict.get(g_vector, None)) def ambient(self): @@ -1787,7 +1795,7 @@ def ambient(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.ambient() Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring """ @@ -1799,7 +1807,7 @@ def scalars(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.scalars() Integer Ring """ @@ -1811,8 +1819,8 @@ def lift(self, x): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) - sage: x = A.cluster_variable((1,0)) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) + sage: x = A.cluster_variable((1, 0)) sage: A.lift(x).parent() Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring """ @@ -1824,7 +1832,7 @@ def retract(self, x): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: L = A.ambient() sage: x = L.gen(0) sage: A.retract(x).parent() @@ -1838,10 +1846,10 @@ def gens(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.gens() [x0, x1, y0, y1] - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True,coefficient_prefix='x') + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x') sage: A.gens() [x0, x1, x2, x3] """ @@ -1857,7 +1865,7 @@ def coefficient(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.coefficient(0) y0 """ @@ -1872,10 +1880,10 @@ def coefficients(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.coefficients() [y0, y1] - sage: B = ClusterAlgebra(['B',2]) + sage: B = ClusterAlgebra(['B', 2]) sage: B.coefficients() [] """ @@ -1890,13 +1898,13 @@ def coefficient_names(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',3]) + sage: A = ClusterAlgebra(['A', 3]) sage: A.coefficient_names() () - sage: B = ClusterAlgebra(['B',2], principal_coefficients=True) + sage: B = ClusterAlgebra(['B', 2], principal_coefficients=True) sage: B.coefficient_names() ('y0', 'y1') - sage: C = ClusterAlgebra(['C',3], principal_coefficients=True, coefficient_prefix='x') + sage: C = ClusterAlgebra(['C', 3], principal_coefficients=True, coefficient_prefix='x') sage: C.coefficient_names() ('x3', 'x4', 'x5') """ @@ -1912,7 +1920,7 @@ def initial_cluster_variable(self, j): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.initial_cluster_variable(0) x0 """ @@ -1924,7 +1932,7 @@ def initial_cluster_variables(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.initial_cluster_variables() [x0, x1] """ @@ -1936,10 +1944,10 @@ def initial_cluster_variable_names(self): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2],principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.initial_cluster_variable_names() ('x0', 'x1') - sage: B = ClusterAlgebra(['B',2],cluster_variable_prefix='a') + sage: B = ClusterAlgebra(['B', 2], cluster_variable_prefix='a') sage: B.initial_cluster_variable_names() ('a0', 'a1') """ @@ -1974,8 +1982,8 @@ def seeds(self, **kwargs): EXAMPLES:: - sage: A = ClusterAlgebra(['A',4]) - sage: seeds = A.seeds(allowed_directions=[3,0,1]) + sage: A = ClusterAlgebra(['A', 4]) + sage: seeds = A.seeds(allowed_directions=[3, 0, 1]) sage: _ = list(seeds) sage: A.g_vectors_so_far() [(-1, 0, 0, 0), @@ -2035,7 +2043,7 @@ def seeds(self, **kwargs): # we got a new seed gets_bigger = True # next round do not mutate back to sd and make sure we only walk three sides of squares - new_directions = [j for j in allowed_dirs if j > i or new_sd.b_matrix()[j,i] != 0] + new_directions = [j for j in allowed_dirs if j > i or new_sd.b_matrix()[j, i] != 0] clusters[new_cl] = [new_sd, new_directions] yield new_sd except KeyboardInterrupt as e: @@ -2061,7 +2069,7 @@ def reset_exploring_iterator(self, mutating_F=True): EXAMPLES:: - sage: A = ClusterAlgebra(['A',4]) + sage: A = ClusterAlgebra(['A', 4]) sage: A.reset_exploring_iterator(mutating_F=False) sage: A.explore_to_depth(infinity) sage: len(A.g_vectors_so_far()) @@ -2082,7 +2090,7 @@ def explore_to_depth(self, depth): EXAMPLES:: - sage: A = ClusterAlgebra(['A',4]) + sage: A = ClusterAlgebra(['A', 4]) sage: A.explore_to_depth(infinity) sage: len(A.g_vectors_so_far()) 14 @@ -2108,7 +2116,7 @@ def cluster_fan(self, depth=infinity): EXAMPLES:: - sage: A = ClusterAlgebra(['A',2]) + sage: A = ClusterAlgebra(['A', 2]) sage: A.cluster_fan() Rational polyhedral fan in 2-d lattice N """ @@ -2128,12 +2136,12 @@ def mutate_initial(self, k): This function computes data for the new algebra from known data for the old algebra using [NZ12]_ equation (4.2) for g-vectors, and [FZ07]_ equation (6.21) for F-polynomials. The exponent ``h`` in the - formula for F-polynomials is ``-min(0,old_g_vect[k])`` due to [NZ12]_ + formula for F-polynomials is ``-min(0, old_g_vect[k])`` due to [NZ12]_ Proposition 4.2. EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: A.explore_to_depth(infinity) sage: B = A.b_matrix() sage: B.mutate(0) @@ -2171,7 +2179,7 @@ def mutate_initial(self, k): # substitution data to compute new F-polynomials Ugen = self._U.gens() # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k,j], 0)*(1+Ugen[k])**(-B0[k,j]) for j in xrange(n)] + F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k, j], 0)*(1+Ugen[k])**(-B0[k, j]) for j in xrange(n)] # restore computed data for old_g_vect in old_path_dict: @@ -2180,8 +2188,8 @@ def mutate_initial(self, k): eps = sign(old_g_vect[k]) for j in xrange(n): # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 - J[j,k] += max(0, -eps*B0[j,k]) - J[k,k] = -1 + J[j, k] += max(0, -eps*B0[j, k]) + J[k, k] = -1 new_g_vect = tuple(J*vector(old_g_vect)) #compute new path @@ -2208,7 +2216,7 @@ def upper_cluster_algebra(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: A.upper_cluster_algebra() Traceback (most recent call last): ... @@ -2222,7 +2230,7 @@ def upper_bound(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: A.upper_bound() Traceback (most recent call last): ... @@ -2236,7 +2244,7 @@ def lower_bound(self): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) + sage: A = ClusterAlgebra(['F', 4]) sage: A.lower_bound() Traceback (most recent call last): ... @@ -2250,8 +2258,8 @@ def theta_basis_element(self, g_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['F',4]) - sage: A.theta_basis_element((1,0,0,0)) + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_element((1, 0, 0, 0)) Traceback (most recent call last): ... NotImplementedError: Not implemented yet @@ -2262,6 +2270,7 @@ def theta_basis_element(self, g_vector): # Methods only defined for special cases #### + def greedy_element(self, d_vector): r""" Return the greedy element with denominator vector ``d_vector``. @@ -2276,12 +2285,12 @@ def greedy_element(self, d_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['A',[1,1],1]) - sage: A.greedy_element((1,1)) + sage: A = ClusterAlgebra(['A', [1, 1], 1]) + sage: A.greedy_element((1, 1)) (x0^2 + x1^2 + 1)/(x0*x1) """ - b = abs(self.b_matrix()[0,1]) - c = abs(self.b_matrix()[1,0]) + b = abs(self.b_matrix()[0, 1]) + c = abs(self.b_matrix()[1, 0]) (a1, a2) = d_vector # here we use the generators of self.ambient() because cluster variables do not have an inverse. (x1, x2) = self.ambient().gens() @@ -2298,22 +2307,23 @@ def greedy_element(self, d_vector): output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) return self.retract(x1**(-a1) * x2**(-a2) * output) + def _greedy_coefficient(self, d_vector, p, q): r""" Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with denominator vector ``d_vector``. EXAMPLES:: - sage: A = ClusterAlgebra(['A',[1,1],1]) - sage: A.greedy_element((1,1)) + sage: A = ClusterAlgebra(['A', [1, 1], 1]) + sage: A.greedy_element((1, 1)) (x0^2 + x1^2 + 1)/(x0*x1) - sage: A._greedy_coefficient((1,1),0,0) + sage: A._greedy_coefficient((1, 1), 0, 0) 1 - sage: A._greedy_coefficient((1,1),1,0) + sage: A._greedy_coefficient((1, 1), 1, 0) 1 """ - b = abs(self.b_matrix()[0,1]) - c = abs(self.b_matrix()[1,0]) + b = abs(self.b_matrix()[0, 1]) + c = abs(self.b_matrix()[1, 0]) (a1, a2) = d_vector p = Integer(p) q = Integer(q) From 22c642bba56ccb3b4b513244f6d273654c78ab10 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 11:52:33 +0100 Subject: [PATCH 169/191] Removed xrange --- src/sage/algebras/cluster_algebra.py | 36 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index b8ce7d0276f..af485dd6b81 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -347,7 +347,7 @@ from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent from sage.structure.sage_object import SageObject -from six.moves import range as xrange +from six.moves import range as range ############################################################################## # Helper functions @@ -587,7 +587,7 @@ def F_polynomial(self): A = self.parent() for x in A.initial_cluster_variables(): subs_dict[x.lift()] = A._U(1) - for i in xrange(A.rk()): + for i in range(A.rk()): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) return self.lift().substitute(subs_dict) else: @@ -1069,7 +1069,7 @@ def mutate(self, k, mutating_F=True): """ n = self.parent().rk() - if k not in xrange(n): + if k not in range(n): raise ValueError('Cannot mutate in direction ' + str(k)) # store new mutation path @@ -1089,7 +1089,7 @@ def mutate(self, k, mutating_F=True): # compute new G-matrix J = identity_matrix(n) - for j in xrange(n): + for j in range(n): J[j, k] += max(0, -eps*self._B[j, k]) J[k, k] = -1 self._G = self._G*J @@ -1105,7 +1105,7 @@ def mutate(self, k, mutating_F=True): # compute new C-matrix J = identity_matrix(n) - for j in xrange(n): + for j in range(n): J[k, j] += max(0, eps*self._B[k, j]) J[k, k] = -1 self._C = self._C*J @@ -1143,7 +1143,7 @@ def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) neg = alg._U(1) - for j in xrange(alg.rk()): + for j in range(alg.rk()): if self._C[j, k] > 0: pos *= alg._U.gen(j)**self._C[j, k] else: @@ -1235,7 +1235,7 @@ def __init__(self, data, **kwargs): # NOTE: for speed purposes we need to have QQ here instead of the more # natural ZZ. The reason is that _mutated_F is faster if we do not cast # the result to polynomials but then we get "rational" coefficients - self._U = PolynomialRing(QQ, ['u%s' % i for i in xrange(n)]) + self._U = PolynomialRing(QQ, ['u%s' % i for i in range(n)]) # Storage for computed data self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) @@ -1243,7 +1243,7 @@ def __init__(self, data, **kwargs): # Determine the names of the initial cluster variables variables_prefix = kwargs.get('cluster_variable_prefix', 'x') - variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in xrange(n)])) + variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in range(n)])) if len(variables) != n: raise ValueError("cluster_variable_names should be a list of %d valid variable names" % n) @@ -1257,7 +1257,7 @@ def __init__(self, data, **kwargs): offset = n else: offset = 0 - coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in xrange(offset, m+offset)])) + coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in range(offset, m+offset)])) if len(coefficients) != m: raise ValueError("coefficient_names should be a list of %d valid variable names" % m) base = LaurentPolynomialRing(scalars, coefficients) @@ -1270,8 +1270,8 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions - self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i, j] for i in xrange(m)])) for j in xrange(n)]) - self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in xrange(n+m)])) for j in xrange(n)]) + self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i, j] for i in range(m)])) for j in range(n)]) + self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in range(n+m)])) for j in range(n)]) # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1461,7 +1461,7 @@ def _coerce_map_from_(self, other): m = M.nrows() B = block_matrix([[self.b_matrix(), -M.transpose()], [M, matrix(m)]]) B.permute_rows_and_columns(perm, perm) - return B.matrix_from_columns(range(other.rk())) == other._B0 + return B[:, :other.rk()] == other._B0 # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) @@ -1688,7 +1688,7 @@ def cluster_variable(self, g_vector): g_vector = tuple(g_vector) F = self.F_polynomial(g_vector) F_std = F.subs(self._yhat) - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk())]) + g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in range(self.rk())]) F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] return self.retract(g_mon*F_std*F_trop) @@ -2152,7 +2152,7 @@ def mutate_initial(self, k): True """ n = self.rk() - if k not in xrange(n): + if k not in range(n): raise ValueError('Cannot mutate in direction ' + str(k)) # save computed data @@ -2179,14 +2179,14 @@ def mutate_initial(self, k): # substitution data to compute new F-polynomials Ugen = self._U.gens() # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k, j], 0)*(1+Ugen[k])**(-B0[k, j]) for j in xrange(n)] + F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k, j], 0)*(1+Ugen[k])**(-B0[k, j]) for j in range(n)] # restore computed data for old_g_vect in old_path_dict: # compute new g-vector J = identity_matrix(n) eps = sign(old_g_vect[k]) - for j in xrange(n): + for j in range(n): # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 J[j, k] += max(0, -eps*B0[j, k]) J[k, k] = -1 @@ -2302,8 +2302,8 @@ def greedy_element(self, d_vector): elif a2 < 0: return self.retract(((1+x1**b)/x2)**a1 * x2**(-a2)) output = 0 - for p in xrange(0, a2+1): - for q in xrange(0, a1+1): + for p in range(0, a2+1): + for q in range(0, a1+1): output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) return self.retract(x1**(-a1) * x2**(-a2) * output) From 4712f48e9449e3ce5eb179b027ff75e285c8cabb Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 11:59:27 +0100 Subject: [PATCH 170/191] Spelling --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index af485dd6b81..1769af5e62c 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1595,7 +1595,7 @@ def g_vectors(self, mutating_F=True): ALGORITHM: - This method does not usethe caching framework provided by ``self`` but + This method does not use the caching framework provided by ``self`` but recomputes all the g-vectors from scratch. On the other hand it stores the results so that other methods like :meth:`g_vectors_so_far` can access them afterwards. @@ -1620,7 +1620,7 @@ def g_vectors(self, mutating_F=True): def cluster_variables(self): r""" - Return an itareator producing all the cluster variables of ``self``. + Return an iterator producing all the cluster variables of ``self``. ALGORITHM: From a6966c2c692ec3a8b1763d5dfe9367cc722b8a75 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 12:11:06 +0100 Subject: [PATCH 171/191] PEP8 E265 --- src/sage/algebras/cluster_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 1769af5e62c..e31a0eb5409 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2192,12 +2192,12 @@ def mutate_initial(self, k): J[k, k] = -1 new_g_vect = tuple(J*vector(old_g_vect)) - #compute new path + # compute new path new_path = old_path_dict[old_g_vect] new_path = ([k]+new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] self._path_dict[new_g_vect] = new_path - #compute new F-polynomial + # compute new F-polynomial if old_g_vect in old_F_poly_dict: h = -min(0, old_g_vect[k]) new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] From da0843d2478b6cf3c4bdaf085c79c3b8b0aee2c9 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 12:29:56 +0100 Subject: [PATCH 172/191] PEP8 E226 --- src/sage/algebras/cluster_algebra.py | 88 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e31a0eb5409..ec073fdb051 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -509,7 +509,7 @@ def _div_(self, other): sage: _.parent() A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring """ - return self.lift()/other.lift() + return self.lift() / other.lift() def d_vector(self): r""" @@ -539,7 +539,7 @@ def _repr_(self): (x0*x2^2*y0*y1*y2^2 + x1^3*x3^2 + x1^2*x3^2*y0 + 2*x1^2*x3*y2 + 2*x1*x3*y0*y2 + x1*y2^2 + y0*y2^2)/(x0*x1*x2^2) """ numer, denom = self.lift()._fraction_pair() - return repr(numer/denom) + return repr(numer / denom) #### # Methods not always defined @@ -631,11 +631,11 @@ def homogeneous_components(self): x = self.lift() monomials = x.monomials() for m in monomials: - g_vect = tuple(deg_matrix*vector(m.exponents()[0])) + g_vect = tuple(deg_matrix * vector(m.exponents()[0])) if g_vect in components: - components[g_vect] += self.parent().retract(x.monomial_coefficient(m)*m) + components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) else: - components[g_vect] = self.parent().retract(x.monomial_coefficient(m)*m) + components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) return components @@ -1090,9 +1090,9 @@ def mutate(self, k, mutating_F=True): # compute new G-matrix J = identity_matrix(n) for j in range(n): - J[j, k] += max(0, -eps*self._B[j, k]) + J[j, k] += max(0, -eps * self._B[j, k]) J[k, k] = -1 - self._G = self._G*J + self._G = self._G * J # path to new g-vector (we store the shortest encountered so far) g_vector = self.g_vector(k) @@ -1106,9 +1106,9 @@ def mutate(self, k, mutating_F=True): # compute new C-matrix J = identity_matrix(n) for j in range(n): - J[k, j] += max(0, eps*self._B[k, j]) + J[k, j] += max(0, eps * self._B[k, j]) J[k, k] = -1 - self._C = self._C*J + self._C = self._C * J # compute new B-matrix self._B.mutate(k) @@ -1145,14 +1145,14 @@ def _mutated_F(self, k, old_g_vector): neg = alg._U(1) for j in range(alg.rk()): if self._C[j, k] > 0: - pos *= alg._U.gen(j)**self._C[j, k] + pos *= alg._U.gen(j) ** self._C[j, k] else: - neg *= alg._U.gen(j)**(-self._C[j, k]) + neg *= alg._U.gen(j) ** (-self._C[j, k]) if self._B[j, k] > 0: - pos *= self.F_polynomial(j)**self._B[j, k] + pos *= self.F_polynomial(j) ** self._B[j, k] elif self._B[j, k] < 0: - neg *= self.F_polynomial(j)**(-self._B[j, k]) - return (pos+neg)/alg.F_polynomial(old_g_vector) + neg *= self.F_polynomial(j) ** (-self._B[j, k]) + return (pos + neg) / alg.F_polynomial(old_g_vector) ############################################################################## # Cluster algebras @@ -1243,7 +1243,7 @@ def __init__(self, data, **kwargs): # Determine the names of the initial cluster variables variables_prefix = kwargs.get('cluster_variable_prefix', 'x') - variables = list(kwargs.get('cluster_variable_names', [variables_prefix+str(i) for i in range(n)])) + variables = list(kwargs.get('cluster_variable_names', [variables_prefix + str(i) for i in range(n)])) if len(variables) != n: raise ValueError("cluster_variable_names should be a list of %d valid variable names" % n) @@ -1257,7 +1257,7 @@ def __init__(self, data, **kwargs): offset = n else: offset = 0 - coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix+str(i) for i in range(offset, m+offset)])) + coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix + str(i) for i in range(offset, m + offset)])) if len(coefficients) != m: raise ValueError("coefficient_names should be a list of %d valid variable names" % m) base = LaurentPolynomialRing(scalars, coefficients) @@ -1266,12 +1266,12 @@ def __init__(self, data, **kwargs): coefficients = [] # setup Parent and ambient - self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) - Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) + self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) + Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) # Data to compute cluster variables using separation of additions - self._y = dict([(self._U.gen(j), prod([self._base.gen(i)**M0[i, j] for i in range(m)])) for j in range(n)]) - self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in range(n+m)])) for j in range(n)]) + self._y = dict([(self._U.gen(j), prod([self._base.gen(i) ** M0[i, j] for i in range(m)])) for j in range(n)]) + self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i) ** B0[i, j] for i in range(n + m)])) for j in range(n)]) # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1455,7 +1455,7 @@ def _coerce_map_from_(self, other): if len(gen_s) == len(gen_o): f = self.ambient().coerce_map_from(other.ambient()) if f is not None: - perm = Permutation([gen_s.index(self(f(v)))+1 for v in gen_o]) + perm = Permutation([gen_s.index(self(f(v))) + 1 for v in gen_o]) n = self.rk() M = self._B0[n:, :] m = M.nrows() @@ -1688,9 +1688,9 @@ def cluster_variable(self, g_vector): g_vector = tuple(g_vector) F = self.F_polynomial(g_vector) F_std = F.subs(self._yhat) - g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in range(self.rk())]) + g_mon = prod([self.ambient().gen(i) ** g_vector[i] for i in range(self.rk())]) F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] - return self.retract(g_mon*F_std*F_trop) + return self.retract(g_mon * F_std * F_trop) def F_polynomials_so_far(self): r""" @@ -2179,7 +2179,7 @@ def mutate_initial(self, k): # substitution data to compute new F-polynomials Ugen = self._U.gens() # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = [Ugen[k]**(-1) if j == k else Ugen[j]*Ugen[k]**max(B0[k, j], 0)*(1+Ugen[k])**(-B0[k, j]) for j in range(n)] + F_subs = [Ugen[k] ** (-1) if j == k else Ugen[j] * Ugen[k] ** max(B0[k, j], 0) * (1 + Ugen[k]) ** (-B0[k, j]) for j in range(n)] # restore computed data for old_g_vect in old_path_dict: @@ -2188,24 +2188,24 @@ def mutate_initial(self, k): eps = sign(old_g_vect[k]) for j in range(n): # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 - J[j, k] += max(0, -eps*B0[j, k]) + J[j, k] += max(0, -eps * B0[j, k]) J[k, k] = -1 - new_g_vect = tuple(J*vector(old_g_vect)) + new_g_vect = tuple(J * vector(old_g_vect)) # compute new path new_path = old_path_dict[old_g_vect] - new_path = ([k]+new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] + new_path = ([k] + new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] self._path_dict[new_g_vect] = new_path # compute new F-polynomial if old_g_vect in old_F_poly_dict: h = -min(0, old_g_vect[k]) - new_F_poly = old_F_poly_dict[old_g_vect](F_subs)*Ugen[k]**h*(Ugen[k]+1)**old_g_vect[k] + new_F_poly = old_F_poly_dict[old_g_vect](F_subs) * Ugen[k] ** h * (Ugen[k] + 1) ** old_g_vect[k] self._F_poly_dict[new_g_vect] = new_F_poly # reset self.current_seed() to the previous location S = self.initial_seed() - S.mutate([k]+old_path_to_current, mutating_F=False) + S.mutate([k] + old_path_to_current, mutating_F=False) self.set_current_seed(S) # DESIDERATA @@ -2296,16 +2296,16 @@ def greedy_element(self, d_vector): (x1, x2) = self.ambient().gens() if a1 < 0: if a2 < 0: - return self.retract(x1**(-a1) * x2**(-a2)) + return self.retract(x1 ** (-a1) * x2 ** (-a2)) else: - return self.retract(x1**(-a1) * ((1+x2**c)/x1)**a2) + return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) elif a2 < 0: - return self.retract(((1+x1**b)/x2)**a1 * x2**(-a2)) + return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) output = 0 - for p in range(0, a2+1): - for q in range(0, a1+1): - output += self._greedy_coefficient(d_vector, p, q) * x1**(b*p) * x2**(c*q) - return self.retract(x1**(-a1) * x2**(-a2) * output) + for p in range(0, a2 + 1): + for q in range(0, a1 + 1): + output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) + return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) def _greedy_coefficient(self, d_vector, p, q): @@ -2330,15 +2330,15 @@ def _greedy_coefficient(self, d_vector, p, q): if p == 0 and q == 0: return Integer(1) sum1 = 0 - for k in range(1, p+1): + for k in range(1, p + 1): bino = 0 - if a2 - c*q + k - 1 >= k: - bino = binomial(a2 - c*q + k - 1, k) - sum1 += (-1)**(k-1) * self._greedy_coefficient(d_vector, p-k, q) * bino + if a2 - c * q + k - 1 >= k: + bino = binomial(a2 - c * q + k - 1, k) + sum1 += (-1) ** (k - 1) * self._greedy_coefficient(d_vector, p - k, q) * bino sum2 = 0 - for l in range(1, q+1): + for l in range(1, q + 1): bino = 0 - if a1 - b*p + l - 1 >= l: - bino = binomial(a1 - b*p + l - 1, l) - sum2 += (-1)**(l-1) * self._greedy_coefficient(d_vector, p, q-l) * bino + if a1 - b * p + l - 1 >= l: + bino = binomial(a1 - b * p + l - 1, l) + sum2 += (-1) ** (l - 1) * self._greedy_coefficient(d_vector, p, q - l) * bino return Integer(max(sum1, sum2)) From bb9578843bc50a1ced386be46afa98115f09645d Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 14:22:42 +0100 Subject: [PATCH 173/191] Fixed indentations within documentation --- src/sage/algebras/cluster_algebra.py | 108 +++++++++++++-------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ec073fdb051..9974bc46048 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -6,21 +6,21 @@ The key points being used here are these: - * cluster variables are parametrized by their g-vectors; +- cluster variables are parametrized by their g-vectors; - * g-vectors (together with c-vectors) provide a self-standing model for the - combinatorics behind any cluster algebra; +- g-vectors (together with c-vectors) provide a self-standing model for the + combinatorics behind any cluster algebra; - * each cluster variable in any cluster algebra can be computed, by the - separation of additions formula, from its g-vector and F-polynomial. +- each cluster variable in any cluster algebra can be computed, by the + separation of additions formula, from its g-vector and F-polynomial. Accordingly this file provides three classes: - * :class:`ClusterAlgebra` +- :class:`ClusterAlgebra` - * :class:`ClusterAlgebraSeed` +- :class:`ClusterAlgebraSeed` - * :class:`ClusterAlgebraElement` +- :class:`ClusterAlgebraElement` :class:`ClusterAlgebra`, constructed as a subobject of :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`, @@ -359,15 +359,15 @@ def _mutation_parse(mutate): Preparse input for mutation functions. This wrapper provides: - - inplace (only for seeds) - - mutate along sequence - - mutate at all sinks/sources + - inplace (only for seeds) + - mutate along sequence + - mutate at all sinks/sources Possible things to implement later include: - - mutate at a cluster variable - - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) - - urban renewals - - other? + - mutate at a cluster variable + - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) + - urban renewals + - other? EXAMPLES:: @@ -716,9 +716,9 @@ def __eq__(self, other): ALGORITHM: - ``self`` and ``other`` are deemed to be equal if they have the same - parent and their set of g-vectors coincide, i.e. this tests - equality of unlabelled seeds. + ``self`` and ``other`` are deemed to be equal if they have the same + parent and their set of g-vectors coincide, i.e. this tests + equality of unlabelled seeds. EXAMPLES:: @@ -771,10 +771,10 @@ def __hash__(self): ALGORITHM: - For speed purposes the hash is computed on :meth:`self.g_vectors`. - In particular it is guaranteed to be unique only within a given - instance of :class:`ClusterAlgebra`. Moreover unlabelled seeds that - have the same set of g-vectors have the same hash. + For speed purposes the hash is computed on :meth:`self.g_vectors`. + In particular it is guaranteed to be unique only within a given + instance of :class:`ClusterAlgebra`. Moreover unlabelled seeds that + have the same set of g-vectors have the same hash. EXAMPLES:: @@ -1127,10 +1127,10 @@ def _mutated_F(self, k, old_g_vector): NOTE: - This function is the bottleneck of :meth:`mutate`. The problem is - that operations on polynomials are slow. One can get a significant - speed boost by disabling this method calling :meth:`mutate` with - ``mutating_F=False``. + This function is the bottleneck of :meth:`mutate`. The problem is + that operations on polynomials are slow. One can get a significant + speed boost by disabling this method calling :meth:`mutate` with + ``mutating_F=False``. EXAMPLES:: @@ -1192,7 +1192,7 @@ def __init__(self, data, **kwargs): ALGORITHM: - The implementation is mainly based on [FZ07]_ and [NZ12]_. + The implementation is mainly based on [FZ07]_ and [NZ12]_. EXAMPLES:: @@ -1336,10 +1336,9 @@ def __eq__(self, other): ALGORITHM: - ``self`` and ``other`` are deemed to be equal if they have the same - initial exchange matrix and their ambients coincide. In - particular we do not keep track of how much each Cluster Algebra has - been explored. + ``self`` and ``other`` are deemed to be equal if they have the same + initial exchange matrix and their ambients coincide. In particular we + do not keep track of how much each Cluster Algebra has been explored. EXAMPLES:: @@ -1397,14 +1396,13 @@ def _coerce_map_from_(self, other): ALGORITHM: - If ``other`` is an instance of :class:`ClusterAlgebra` then allow - coercion if ``other.ambient()`` can be coerced into - ``self.ambient()`` and ``other`` can be obtained from ``self`` by - permuting variables and coefficients and/or freezing some initial - cluster variables. + If ``other`` is an instance of :class:`ClusterAlgebra` then allow + coercion if ``other.ambient()`` can be coerced into ``self.ambient()`` + and ``other`` can be obtained from ``self`` by permuting variables + and coefficients and/or freezing some initial cluster variables. - Otherwise allow anything that coerces into ``self.base()`` to coerce - into ``self``. + Otherwise allow anything that coerces into ``self.base()`` to coerce + into ``self``. EXAMPLES:: @@ -1674,9 +1672,9 @@ def cluster_variable(self, g_vector): ALGORITHM: - This function computes cluster variables from their g-vectors and - F-polynomials using the "separation of additions" formula of - Theorem 3.7 in [FZ07]_. + This function computes cluster variables from their g-vectors and + F-polynomials using the "separation of additions" formula of Theorem + 3.7 in [FZ07]_. EXAMPLE:: @@ -1754,11 +1752,11 @@ def find_g_vector(self, g_vector, depth=infinity): OUTPUT: - This function returns a list of integers if it can find - ``g_vector``, otherwise it returns ``None``. If the exploring - iterator stops, it means that the algebra is of finite type and - ``g_vector`` is not the g-vector of any cluster variable. In this - case the function resets the iterator and raises an error. + This function returns a list of integers if it can find ``g_vector``, + otherwise it returns ``None``. If the exploring iterator stops, it + means that the algebra is of finite type and ``g_vector`` is not the + g-vector of any cluster variable. In this case the function resets the + iterator and raises an error. EXAMPLES:: @@ -1978,7 +1976,7 @@ def seeds(self, **kwargs): ALGORITHM: - This function traverses the exchange graph in a breadth-first search. + This function traverses the exchange graph in a breadth-first search. EXAMPLES:: @@ -2133,11 +2131,11 @@ def mutate_initial(self, k): ALGORITHM: - This function computes data for the new algebra from known data for - the old algebra using [NZ12]_ equation (4.2) for g-vectors, and - [FZ07]_ equation (6.21) for F-polynomials. The exponent ``h`` in the - formula for F-polynomials is ``-min(0, old_g_vect[k])`` due to [NZ12]_ - Proposition 4.2. + This function computes data for the new algebra from known data for the + old algebra using [NZ12]_ equation (4.2) for g-vectors, and [FZ07]_ + equation (6.21) for F-polynomials. The exponent ``h`` in the formula + for F-polynomials is ``-min(0, old_g_vect[k])`` due to [NZ12]_ + Proposition 4.2. EXAMPLES:: @@ -2275,13 +2273,13 @@ def greedy_element(self, d_vector): r""" Return the greedy element with denominator vector ``d_vector``. - INPUT + INPUT: - ``d_vector`` -- tuple of 2 integers: the denominator vector of the element to compute. ALGORITHM: - This implements greedy elements of a rank 2 cluster algebra from [LLZ14]_ equation (1.5). + This implements greedy elements of a rank 2 cluster algebra from [LLZ14]_ equation (1.5). EXAMPLES:: @@ -2310,7 +2308,7 @@ def greedy_element(self, d_vector): def _greedy_coefficient(self, d_vector, p, q): r""" - Return the coefficient of the monomial ``x1**(b*p) * x2**(c*q)`` in the numerator of the greedy element with denominator vector ``d_vector``. + Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` in the numerator of the greedy element with denominator vector ``d_vector``. EXAMPLES:: From 7d1187015ee31b864ccd30c0d3322958c7cc02b2 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 14:24:27 +0100 Subject: [PATCH 174/191] Changed rk() to rank() --- src/sage/algebras/cluster_algebra.py | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 9974bc46048..bdbc96836cc 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -141,7 +141,7 @@ Since we are in rank 2 and we do not have coefficients we can compute the greedy element associated to any denominator vector:: - sage: A.rk() == 2 and A.coefficients() == [] + sage: A.rank() == 2 and A.coefficients() == [] True sage: A.greedy_element((1, 1)) (x0 + x1 + 1)/(x0*x1) @@ -390,7 +390,7 @@ def _mutation_parse(mutate): doc[0] += r""" - ``direction`` -- in which direction(s) to mutate, it can be: - - an integer in ``range(self.rk())`` to mutate in one direction only; + - an integer in ``range(self.rank())`` to mutate in one direction only; - an iterable of such integers to mutate along a sequence; - a string "sinks" or "sources" to mutate at all sinks or sources simultaneously. """ @@ -525,7 +525,7 @@ def d_vector(self): """ monomials = self.lift()._dict().keys() minimal = map(min, zip(*monomials)) - return tuple(-vector(minimal))[:self.parent().rk()] + return tuple(-vector(minimal))[:self.parent().rank()] def _repr_(self): r""" @@ -587,7 +587,7 @@ def F_polynomial(self): A = self.parent() for x in A.initial_cluster_variables(): subs_dict[x.lift()] = A._U(1) - for i in range(A.rk()): + for i in range(A.rank()): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) return self.lift().substitute(subs_dict) else: @@ -626,7 +626,7 @@ def homogeneous_components(self): sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rk()), -self.parent().b_matrix()]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rank()), -self.parent().b_matrix()]]) components = dict() x = self.lift() monomials = x.monomials() @@ -905,7 +905,7 @@ def c_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the c-vector to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the c-vector to return. EXAMPLES:: @@ -955,7 +955,7 @@ def g_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the g-vector to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the g-vector to return. EXAMPLES:: @@ -985,7 +985,7 @@ def F_polynomial(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the F-polynomial to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the F-polynomial to return. EXAMPLES:: @@ -1015,7 +1015,7 @@ def cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the cluster variable to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the cluster variable to return. EXAMPLES:: @@ -1067,7 +1067,7 @@ def mutate(self, k, mutating_F=True): ... ValueError: Cannot mutate in direction 5 """ - n = self.parent().rk() + n = self.parent().rank() if k not in range(n): raise ValueError('Cannot mutate in direction ' + str(k)) @@ -1119,7 +1119,7 @@ def _mutated_F(self, k, old_g_vector): INPUT: - - ``k`` -- an integer in ``range(self.parent().rk())``: the direction + - ``k`` -- an integer in ``range(self.parent().rank())``: the direction in which we are mutating. - ``old_g_vector`` -- tuple: the k-th g-vector of ``self`` before @@ -1143,7 +1143,7 @@ def _mutated_F(self, k, old_g_vector): alg = self.parent() pos = alg._U(1) neg = alg._U(1) - for j in range(alg.rk()): + for j in range(alg.rank()): if self._C[j, k] > 0: pos *= alg._U.gen(j) ** self._C[j, k] else: @@ -1454,17 +1454,17 @@ def _coerce_map_from_(self, other): f = self.ambient().coerce_map_from(other.ambient()) if f is not None: perm = Permutation([gen_s.index(self(f(v))) + 1 for v in gen_o]) - n = self.rk() + n = self.rank() M = self._B0[n:, :] m = M.nrows() B = block_matrix([[self.b_matrix(), -M.transpose()], [M, matrix(m)]]) B.permute_rows_and_columns(perm, perm) - return B[:, :other.rk()] == other._B0 + return B[:, :other.rank()] == other._B0 # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) - def rk(self): + def rank(self): r""" Return the rank of ``self``, i.e. the number of cluster variables in any seed. @@ -1472,7 +1472,7 @@ def rk(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring - sage: A.rk() + sage: A.rank() 2 """ return self._n @@ -1564,7 +1564,7 @@ def initial_seed(self): sage: A.initial_seed() The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring """ - n = self.rk() + n = self.rank() I = identity_matrix(n) return ClusterAlgebraSeed(self.b_matrix(), I, I, self) @@ -1579,7 +1579,7 @@ def b_matrix(self): [ 0 1] [-1 0] """ - n = self.rk() + n = self.rank() return copy(self._B0[:n, :]) def g_vectors(self, mutating_F=True): @@ -1686,7 +1686,7 @@ def cluster_variable(self, g_vector): g_vector = tuple(g_vector) F = self.F_polynomial(g_vector) F_std = F.subs(self._yhat) - g_mon = prod([self.ambient().gen(i) ** g_vector[i] for i in range(self.rk())]) + g_mon = prod([self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())]) F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] return self.retract(g_mon * F_std * F_trop) @@ -1859,7 +1859,7 @@ def coefficient(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the coefficient to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the coefficient to return. EXAMPLES:: @@ -1906,7 +1906,7 @@ def coefficient_names(self): sage: C.coefficient_names() ('x3', 'x4', 'x5') """ - return self.variable_names()[self.rk():] + return self.variable_names()[self.rank():] def initial_cluster_variable(self, j): r""" @@ -1914,7 +1914,7 @@ def initial_cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rk())``: the index of the cluster variable to return. + - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the cluster variable to return. EXAMPLES:: @@ -1934,7 +1934,7 @@ def initial_cluster_variables(self): sage: A.initial_cluster_variables() [x0, x1] """ - return list(map(self.retract, self.ambient().gens()[:self.rk()])) + return list(map(self.retract, self.ambient().gens()[:self.rank()])) def initial_cluster_variable_names(self): r""" @@ -1949,7 +1949,7 @@ def initial_cluster_variable_names(self): sage: B.initial_cluster_variable_names() ('a0', 'a1') """ - return self.variable_names()[:self.rk()] + return self.variable_names()[:self.rank()] def seeds(self, **kwargs): r""" @@ -1963,7 +1963,7 @@ def seeds(self, **kwargs): - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also; for speed considerations you may want to disable this. - - ``allowed_directions`` -- a tuple of integers (default ``range(self.rk())``): the + - ``allowed_directions`` -- a tuple of integers (default ``range(self.rank())``): the directions in which to mutate. - ``depth`` -- a positive integer or infinity (default ``infinity``): @@ -2009,7 +2009,7 @@ def seeds(self, **kwargs): mutating_F = kwargs.get('mutating_F', True) # which directions are we allowed to mutate into - allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(self.rk())))) + allowed_dirs = list(sorted(kwargs.get('allowed_directions', range(self.rank())))) # setup seeds storage cl = frozenset(seed.g_vectors()) @@ -2149,7 +2149,7 @@ def mutate_initial(self, k): sage: A2._F_poly_dict == A._F_poly_dict True """ - n = self.rk() + n = self.rank() if k not in range(n): raise ValueError('Cannot mutate in direction ' + str(k)) From d71a175fcb8699eae60593aec78a8feece892f62 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 17:08:25 +0100 Subject: [PATCH 175/191] Streamlined tuple assignments --- src/sage/algebras/cluster_algebra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index bdbc96836cc..111a70bc5a9 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2289,9 +2289,9 @@ def greedy_element(self, d_vector): """ b = abs(self.b_matrix()[0, 1]) c = abs(self.b_matrix()[1, 0]) - (a1, a2) = d_vector + a1, a2 = d_vector # here we use the generators of self.ambient() because cluster variables do not have an inverse. - (x1, x2) = self.ambient().gens() + x1, x2 = self.ambient().gens() if a1 < 0: if a2 < 0: return self.retract(x1 ** (-a1) * x2 ** (-a2)) @@ -2322,7 +2322,7 @@ def _greedy_coefficient(self, d_vector, p, q): """ b = abs(self.b_matrix()[0, 1]) c = abs(self.b_matrix()[1, 0]) - (a1, a2) = d_vector + a1, a2 = d_vector p = Integer(p) q = Integer(q) if p == 0 and q == 0: From e569b46b077d41b5dd43efd0f829e6b916247123 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 17:17:10 +0100 Subject: [PATCH 176/191] Removed useless [] --- src/sage/algebras/cluster_algebra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 111a70bc5a9..7dc7eb4aa45 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1238,8 +1238,8 @@ def __init__(self, data, **kwargs): self._U = PolynomialRing(QQ, ['u%s' % i for i in range(n)]) # Storage for computed data - self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) - self._F_poly_dict = dict([(v, self._U(1)) for v in self._path_dict]) + self._path_dict = dict((v, []) for v in map(tuple, I.columns())) + self._F_poly_dict = dict((v, self._U(1)) for v in self._path_dict) # Determine the names of the initial cluster variables variables_prefix = kwargs.get('cluster_variable_prefix', 'x') @@ -1270,8 +1270,8 @@ def __init__(self, data, **kwargs): Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) # Data to compute cluster variables using separation of additions - self._y = dict([(self._U.gen(j), prod([self._base.gen(i) ** M0[i, j] for i in range(m)])) for j in range(n)]) - self._yhat = dict([(self._U.gen(j), prod([self._ambient.gen(i) ** B0[i, j] for i in range(n + m)])) for j in range(n)]) + self._y = dict((self._U.gen(j), prod(self._base.gen(i) ** M0[i, j] for i in range(m))) for j in range(n)) + self._yhat = dict((self._U.gen(j), prod(self._ambient.gen(i) ** B0[i, j] for i in range(n + m))) for j in range(n)) # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1686,7 +1686,7 @@ def cluster_variable(self, g_vector): g_vector = tuple(g_vector) F = self.F_polynomial(g_vector) F_std = F.subs(self._yhat) - g_mon = prod([self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())]) + g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] return self.retract(g_mon * F_std * F_trop) From 27b0c2de04f7292537a64096f984343e71d0aad6 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 17:20:29 +0100 Subject: [PATCH 177/191] typo --- src/sage/algebras/cluster_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 7dc7eb4aa45..5d279c7d227 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2252,7 +2252,7 @@ def lower_bound(self): def theta_basis_element(self, g_vector): r""" - Return the element of the theta basis with g-vector ``g_vetor``. + Return the element of the theta basis with g-vector ``g_vector``. EXAMPLES:: From 72d039f93c42d813bee8050e8f2aa837f673b3f8 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sun, 6 Nov 2016 18:38:44 +0100 Subject: [PATCH 178/191] Reorganization + implementation of F_polynomials --- src/sage/algebras/cluster_algebra.py | 77 +++++++++++++++++----------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 5d279c7d227..92e6149f15c 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1518,6 +1518,22 @@ def set_current_seed(self, seed): else: raise ValueError("This is not a seed in this cluster algebra") + def reset_current_seed(self): + r""" + Reset the value reported by :meth:`current_seed` to :meth:`initial_seed`. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', 2]) + sage: A.current_seed().mutate([1, 0]) + sage: A.current_seed() == A.initial_seed() + False + sage: A.reset_current_seed() + sage: A.current_seed() == A.initial_seed() + True + """ + self._seed = self.initial_seed() + def contains_seed(self, seed): r""" Test if ``seed`` is a seed of ``self``. @@ -1538,22 +1554,6 @@ def contains_seed(self, seed): computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed - def reset_current_seed(self): - r""" - Reset the value reported by :meth:`current_seed` to :meth:`initial_seed`. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['A', 2]) - sage: A.current_seed().mutate([1, 0]) - sage: A.current_seed() == A.initial_seed() - False - sage: A.reset_current_seed() - sage: A.current_seed() == A.initial_seed() - True - """ - self._seed = self.initial_seed() - def initial_seed(self): r""" Return the initial seed of ``self``. @@ -1635,6 +1635,25 @@ def cluster_variables(self): """ return map(self.cluster_variable, self.g_vectors()) + def F_polynomials(self): + r""" + Return an iterator producing all the F_polynomials of ``self``. + + ALGORITHM: + + This method does not use the caching framework provided by ``self`` but + recomputes all the F_polynomials from scratch. On the other hand it + stores the results so that other methods like :meth:`F_polynomials_so_far` + can access them afterwards. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', 3]) + sage: len(list(A.F_polynomials())) + 9 + """ + return map(self.F_polynomial, self.g_vectors()) + def g_vectors_so_far(self): r""" Return a list of the g-vectors of cluster variables encountered so far. @@ -1661,6 +1680,19 @@ def cluster_variables_so_far(self): """ return list(map(self.cluster_variable, self.g_vectors_so_far())) + def F_polynomials_so_far(self): + r""" + Return a list of the F-polynomials encountered so far. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', 2]) + sage: A.current_seed().mutate(0) + sage: A.F_polynomials_so_far() + [1, 1, u0 + 1] + """ + return self._F_poly_dict.values() + @cached_method(key=lambda a, b: tuple(b)) def cluster_variable(self, g_vector): r""" @@ -1690,19 +1722,6 @@ def cluster_variable(self, g_vector): F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1] return self.retract(g_mon * F_std * F_trop) - def F_polynomials_so_far(self): - r""" - Return a list of the F-polynomials encountered so far. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['A', 2]) - sage: A.current_seed().mutate(0) - sage: A.F_polynomials_so_far() - [1, 1, u0 + 1] - """ - return self._F_poly_dict.values() - def F_polynomial(self, g_vector): r""" Return the F-polynomial with g-vector ``g_vector`` if it has been found. From 933720c687b5e8ea9bf13cde9925d93abf103e60 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 22 Nov 2016 02:35:21 -0600 Subject: [PATCH 179/191] Initial pass of documentation fixes and added TestSuite (to be fixed). --- src/sage/algebras/cluster_algebra.py | 682 +++++++++++++++------------ 1 file changed, 386 insertions(+), 296 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 92e6149f15c..21c54b204ca 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2,7 +2,7 @@ Cluster algebras This file constructs cluster algebras using the Parent-Element framework. -The implementation mainly utilizes structural theorems from [FZ07]_. +The implementation mainly utilizes structural theorems from [FZ2007]_. The key points being used here are these: @@ -24,55 +24,50 @@ :class:`ClusterAlgebra`, constructed as a subobject of :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`, -is the frontend of this implementation. It provides all the algebraic features -(like ring morphisms), it computes cluster variables, it is responsible for -controlling the exploration of the exchange graph and serves as the repository for -all the data recursively computed so far. -In particular, all g-vectors and all F-polynomials of known cluster variables as -well as a mutation path by which they can be obtained are recorded. In the optic -of efficiency, this implementation does not store directly the exchange graph -nor the exchange relations. Both of these could be added to -:class:`ClusterAlgebra` with minimal effort. - -:class:`ClusterAlgebraSeed` provides the combinatorial backbone for :class:`ClusterAlgebra`. -It is an auxiliary class and therefore its instances should **not** be directly -created by the user. Rather it should be accessed via -:meth:`ClusterAlgebra.current_seed` and :meth:`ClusterAlgebra.initial_seed`. -The task of performing current seed mutations is delegated to this class. -Seeds are considered equal if they have the same parent cluster -algebra and they can be obtained from each other by a permutation of their -data (i.e. if they coincide as unlabelled seeds). Cluster algebras whose -initial seeds are equal in the above sense are not considered equal but are -endowed with coercion maps to each other. More generally, a cluster algebra is -endowed with coercion maps from any cluster algebra which is obtained by -freezing a collection of initial cluster variables and/or permuting both -cluster variables and coefficients. +is the frontend of this implementation. It provides all the algebraic +features (like ring morphisms), it computes cluster variables, it is +responsible for controlling the exploration of the exchange graph and +serves as the repository for all the data recursively computed so far. +In particular, all g-vectors and all F-polynomials of known cluster +variables as well as a mutation path by which they can be obtained +are recorded. In the optic of efficiency, this implementation does not +store directly the exchange graph nor the exchange relations. Both of +these could be added to :class:`ClusterAlgebra` with minimal effort. + +:class:`ClusterAlgebraSeed` provides the combinatorial backbone +for :class:`ClusterAlgebra`. It is an auxiliary class and therefore its +instances should **not** be directly created by the user. Rather it +should be accessed via :meth:`ClusterAlgebra.current_seed` +and :meth:`ClusterAlgebra.initial_seed`. The task of performing current +seed mutations is delegated to this class. Seeds are considered equal if +they have the same parent cluster algebra and they can be obtained from +each other by a permutation of their data (i.e. if they coincide as +unlabelled seeds). Cluster algebras whose initial seeds are equal in the +above sense are not considered equal but are endowed with coercion maps +to each other. More generally, a cluster algebra is endowed with coercion +maps from any cluster algebra which is obtained by freezing a collection +of initial cluster variables and/or permuting both cluster variables +and coefficients. :class:`ClusterAlgebraElement` is a thin wrapper around :class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_generic` providing all the functions specific to cluster variables. -One more remark about this implementation. Instances of +One more remark about this implementation. Instances of :class:`ClusterAlgebra` are built by identifying the initial cluster variables with the generators of :meth:`ClusterAlgebra.ambient`. In particular, this -forces a specific embedding into the ambient field of rational expressions. In +forces a specific embedding into the ambient field of rational expressions. In view of this, although cluster algebras themselves are independent of the choice of initial seed, :meth:`ClusterAlgebra.mutate_initial` is forced to -return a different instance of :class:`ClusterAlgebra`. At the moment there is -no coercion implemented among the two instances but this could in principle be -added to :meth:`ClusterAlgebra.mutate_initial`. +return a different instance of :class:`ClusterAlgebra`. At the moment there +is no coercion implemented among the two instances but this could in +principle be added to :meth:`ClusterAlgebra.mutate_initial`. REFERENCES: -.. [FZ07] \S. Fomin and \A. Zelevinsky, "Cluster algebras IV. Coefficients", - Compos. Math. 143 (2007), no. 1, 112-164. - -.. [LLZ14] \K. Lee, \L. Li, and \A. Zelevinsky, "Greedy elements in rank 2 - cluster algebras", Selecta Math. 20 (2014), 57-82. - -.. [NZ12] \T. Nakanishi and \A. Zelevinsky, "On tropical dualities in cluster - algebras', Algebraic groups and quantum groups, Contemp. Math., vol. 565, - Amer. Math. Soc., Providence, RI, 2012, pp. 217-226. +- [FZ2007]_ +- [LLZ2014]_ +- [NZ2012]_ AUTHORS: @@ -82,8 +77,8 @@ EXAMPLES: -We begin by creating a simple cluster algebra and printing its initial exchange -matrix:: +We begin by creating a simple cluster algebra and printing its +initial exchange matrix:: sage: A = ClusterAlgebra(['A', 2]); A A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring @@ -141,14 +136,15 @@ Since we are in rank 2 and we do not have coefficients we can compute the greedy element associated to any denominator vector:: - sage: A.rank() == 2 and A.coefficients() == [] + sage: A.rank() == 2 and A.coefficients() == () True sage: A.greedy_element((1, 1)) (x0 + x1 + 1)/(x0*x1) sage: _ == t*s False -not surprising since there is no cluster in ``A`` containing both ``t`` and ``s``:: +not surprising since there is no cluster in ``A`` containing +both ``t`` and ``s``:: sage: seeds = A.seeds(mutating_F=false) sage: [ S for S in seeds if (0, -1) in S and (-1, 1) in S ] @@ -160,8 +156,8 @@ True Disabling F-polynomials in the computation just done was redundant because we -already explored the whole exchange graph before. Though in different circumstances it -could have saved us considerable time. +already explored the whole exchange graph before. Though in different +circumstances it could have saved us considerable time. g-vectors and F-polynomials can be computed from elements of ``A`` only if ``A`` has principal coefficients at the initial seed:: @@ -187,14 +183,15 @@ sage: (t+s).homogeneous_components() {(-1, 1): (x1 + y0)/x0, (0, -1): (x0*y1 + 1)/x1} -Each cluster algebra is endowed with a reference to a current seed; it could be -useful to assign a name to it:: +Each cluster algebra is endowed with a reference to a current seed; +it could be useful to assign a name to it:: sage: A = ClusterAlgebra(['F', 4]) sage: len(A.g_vectors_so_far()) 4 sage: A.current_seed() - The initial seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring sage: A.current_seed() == A.initial_seed() True sage: S = A.current_seed() @@ -214,7 +211,9 @@ and use ``S`` to walk around the exchange graph of ``A``:: sage: S.mutate(0); S - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring obtained from the initial + by mutating in direction 0 sage: S.b_matrix() [ 0 -1 0 0] [ 1 0 -1 0] @@ -228,9 +227,13 @@ sage: S.cluster_variables() [(x1 + 1)/x0, x1, x2, x3] sage: S.mutate('sinks'); S - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [0, 2] sage: S.mutate([2, 3, 2, 1, 0]); S - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 3, 2, 1, 0] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [0, 3, 2, 1, 0] sage: S.g_vectors() [(0, 1, -2, 0), (-1, 2, -2, 0), (0, 1, -1, 0), (0, 0, 0, -1)] sage: S.cluster_variable(3) @@ -266,7 +269,8 @@ This also performs mutations of F-polynomials:: sage: A.F_polynomial((-1, 1, -1, 1, -1, 1, 0, 0, 1)) - u0*u1*u2*u3*u4 + u0*u1*u2*u4 + u0*u2*u3*u4 + u0*u1*u2 + u0*u2*u4 + u2*u3*u4 + u0*u2 + u0*u4 + u2*u4 + u0 + u2 + u4 + 1 + u0*u1*u2*u3*u4 + u0*u1*u2*u4 + u0*u2*u3*u4 + u0*u1*u2 + u0*u2*u4 + + u2*u3*u4 + u0*u2 + u0*u4 + u2*u4 + u0 + u2 + u4 + 1 which might not be a good idea in algebras that are too big. One workaround is to first disable F-polynomials and then recompute only the desired mutations:: @@ -280,29 +284,32 @@ ... 2*u2 + u4 + u6 + 1 -We can manually freeze cluster variables and get coercions in between the two -algebras:: +We can manually freeze cluster variables and get coercions in between +the two algebras:: sage: A = ClusterAlgebra(['F', 4]); A - A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients + over Integer Ring sage: B = ClusterAlgebra(A.b_matrix().matrix_from_columns([0, 1, 2]), coefficient_prefix='x'); B - A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3 over Integer Ring + A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3 + over Integer Ring sage: A.has_coerce_map_from(B) True -and we also have an immersion of ``A.base()`` into ``A`` and of ``A`` into -``A.ambient()``:: +and we also have an immersion of ``A.base()`` into ``A`` and of ``A`` +into ``A.ambient()``:: sage: A.has_coerce_map_from(A.base()) True sage: A.ambient().has_coerce_map_from(A) True -but there is currently no coercion in between algebras obtained by mutating at -the initial seed:: +but there is currently no coercion in between algebras obtained by +mutating at the initial seed:: sage: B = A.mutate_initial(0); B - A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients + over Integer Ring sage: A.b_matrix() == B.b_matrix() False sage: map(lambda (X, Y): X.has_coerce_map_from(Y), [(A, B), (B, A)]) @@ -319,6 +326,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from __future__ import absolute_import + from copy import copy from functools import wraps from future_builtins import map @@ -340,8 +349,8 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing_generic -from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.polynomial.laurent_polynomial_ring import (LaurentPolynomialRing_generic, + LaurentPolynomialRing) from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ from sage.structure.element_wrapper import ElementWrapper @@ -359,13 +368,16 @@ def _mutation_parse(mutate): Preparse input for mutation functions. This wrapper provides: + - inplace (only for seeds) - mutate along sequence - mutate at all sinks/sources Possible things to implement later include: + - mutate at a cluster variable - - mutate at a g-vector (it is hard to distinguish this case from a generic sequence) + - mutate at a g-vector (it is hard to distinguish this case from + a generic sequence) - urban renewals - other? @@ -374,25 +386,32 @@ def _mutation_parse(mutate): sage: A = ClusterAlgebra(['E', 6]) sage: S = A.current_seed() sage: S.mutate(1, inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating in direction 1 + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 + and no coefficients over Integer Ring obtained from the initial + by mutating in direction 1 sage: S.mutate([1, 2, 3], inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [1, 2, 3] sage: S.mutate('sinks', inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 2, 4] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [0, 2, 4] """ doc = mutate.__doc__.split("INPUT:") doc[0] += "INPUT:" if mutate.__name__ == "mutate": doc[0] += r""" - - ``inplace`` -- bool (default ``True``): whether to mutate in place or to return a new object. + - ``inplace`` -- bool (default ``True``); whether to mutate in place or to return a new object """ doc[0] += r""" - ``direction`` -- in which direction(s) to mutate, it can be: - - an integer in ``range(self.rank())`` to mutate in one direction only; - - an iterable of such integers to mutate along a sequence; - - a string "sinks" or "sources" to mutate at all sinks or sources simultaneously. + + * an integer in ``range(self.rank())`` to mutate in one direction only + * an iterable of such integers to mutate along a sequence + * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously """ mutate.__doc__ = doc[0] + doc[1] @@ -430,16 +449,17 @@ def mutate_wrapper(self, direction, **kwargs): class ClusterAlgebraElement(ElementWrapper): - + """ + An element of a cluster algebra. + """ def __init__(self, parent, value): r""" - An element of a Cluster Algebra. + Initialize ``self``. INPUT: - - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the element belongs. - - - ``value`` -- the value of the element. + - ``parent`` -- :class:`ClusterAlgebra`; the parent algebra + - ``value`` -- the value of the element EXAMPLES:: @@ -464,7 +484,7 @@ def _add_(self, other): INPUT: - - ``other`` - an element of ``self.parent()``. + - ``other`` -- an element of ``self.parent()`` EXAMPLES:: @@ -493,8 +513,8 @@ def _div_(self, other): .. WARNING:: The result of a division is not guaranteed to be inside - meth:`parent` therefore this method does not return an instance of - class:`ClusterAlgebraElement`. + meth:`parent` therefore this method does not return an + instance of class:`ClusterAlgebraElement`. EXAMPLES:: @@ -503,11 +523,13 @@ def _div_(self, other): sage: x/x 1 sage: _.parent() - Multivariate Laurent Polynomial Ring in x0, x1, x2, x3 over Integer Ring + Multivariate Laurent Polynomial Ring in x0, x1, x2, x3 + over Integer Ring sage: A.retract(x/x) 1 sage: _.parent() - A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring + A Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring """ return self.lift() / other.lift() @@ -558,13 +580,12 @@ def g_vector(self): sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): ... - ValueError: This element is not homogeneous + ValueError: this element is not homogeneous """ components = self.homogeneous_components() - if len(components) == 1: - return components.keys()[0] - else: - raise ValueError("This element is not homogeneous") + if len(components) != 1: + raise ValueError("this element is not homogeneous") + return components.keys()[0] def F_polynomial(self): @@ -580,18 +601,17 @@ def F_polynomial(self): sage: sum(A.initial_cluster_variables()).F_polynomial() Traceback (most recent call last): ... - ValueError: This element is not homogeneous + ValueError: this element is not homogeneous """ - if self.is_homogeneous(): - subs_dict = dict() - A = self.parent() - for x in A.initial_cluster_variables(): - subs_dict[x.lift()] = A._U(1) - for i in range(A.rank()): - subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) - else: - raise ValueError("This element is not homogeneous") + if not self.is_homogeneous(): + raise ValueError("this element is not homogeneous") + subs_dict = dict() + A = self.parent() + for x in A.initial_cluster_variables(): + subs_dict[x.lift()] = A._U(1) + for i in range(A.rank()): + subs_dict[A.coefficient(i).lift()] = A._U.gen(i) + return self.lift().substitute(subs_dict) def is_homogeneous(self): @@ -626,7 +646,8 @@ def homogeneous_components(self): sage: x.homogeneous_components() {(0, 1): x1, (1, 0): x0} """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rank()), -self.parent().b_matrix()]]) + deg_matrix = block_matrix([[identity_matrix(self.parent().rank()), + -self.parent().b_matrix()]]) components = dict() x = self.lift() monomials = x.monomials() @@ -644,40 +665,40 @@ def homogeneous_components(self): ############################################################################## class ClusterAlgebraSeed(SageObject): + """ + A seed in a Cluster Algebra. + + INPUT: + - ``B`` -- a skew-symmetrizable integer matrix + - ``C`` -- the matrix of c-vectors of ``self`` + - ``G`` -- the matrix of g-vectors of ``self`` + - ``parent`` -- :class:`ClusterAlgebra`; the algebra to which the + seed belongs + - ``path`` -- list (default ``[]``); the mutation sequence from the + initial seed of ``parent`` to ``self`` + + .. WARNING:: + + Seeds should **not** be created manually: no test is performed to + assert that they are built from consistent data nor that they + really are seeds of ``parent``. If you create seeds with + inconsistent data all sort of things can go wrong, even + :meth:`__eq__` is no longer guaranteed to give correct answers. + Use at your own risk. + """ def __init__(self, B, C, G, parent, **kwargs): r""" - A seed in a Cluster Algebra. - - INPUT: - - - ``B`` -- a skew-symmetrizable integer matrix. - - - ``C`` -- the matrix of c-vectors of ``self``. - - - ``G`` -- the matrix of g-vectors of ``self``. - - - ``parent`` -- a :class:`ClusterAlgebra`: the algebra to which the - seed belongs. - - - ``path`` -- a list (default ``[]``): the mutation sequence from the initial - seed of ``parent`` to ``self``. - - .. WARNING:: - - Seeds should **not** be created manually: no test is performed to - assert that they are built from consistent data nor that they - really are seeds of ``parent``. If you create seeds with - inconsistent data all sort of things can go wrong, even - :meth:`__eq__` is no longer guaranteed to give correct answers. - Use at your own risk. + Initialize ``self``. EXAMPLES:: sage: A = ClusterAlgebra(['F', 4]) sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed sage: ClusterAlgebraSeed(A.b_matrix(), identity_matrix(4), identity_matrix(4), A, path=[1, 2, 3]) - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [1, 2, 3] + The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [1, 2, 3] """ self._B = copy(B) self._C = copy(C) @@ -712,7 +733,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a :class:`ClusterAlgebraSeed`. + - ``other`` -- a :class:`ClusterAlgebraSeed` ALGORITHM: @@ -737,7 +758,9 @@ def __eq__(self, other): sage: S == B.current_seed() True """ - return isinstance(other, ClusterAlgebraSeed) and self.parent() == other.parent() and frozenset(self.g_vectors()) == frozenset(other.g_vectors()) + return (isinstance(other, ClusterAlgebraSeed) + and self.parent() == other.parent() + and frozenset(self.g_vectors()) == frozenset(other.g_vectors())) def __contains__(self, element): r""" @@ -745,7 +768,7 @@ def __contains__(self, element): INPUT: - - ``element`` -- either a g-vector or an element of :meth:`parent`. + - ``element`` -- either a g-vector or an element of :meth:`parent` EXAMPLES:: @@ -793,11 +816,16 @@ def _repr_(self): sage: A = ClusterAlgebra(['A', 3]) sage: S = A.current_seed(); S - The initial seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1, x2 + and no coefficients over Integer Ring sage: S.mutate(0); S - The seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of a Cluster Algebra with cluster variables x0, x1, x2 + and no coefficients over Integer Ring obtained from the initial + by mutating in direction 0 sage: S.mutate(1); S - The seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring obtained from the initial by mutating along the sequence [0, 1] + The seed of a Cluster Algebra with cluster variables x0, x1, x2 + and no coefficients over Integer Ring obtained from the initial + by mutating along the sequence [0, 1] """ if self._path == []: return "The initial seed of a %s" % str(self.parent())[2:] @@ -820,13 +848,14 @@ def parent(self): def depth(self): r""" - Return the length of a mutation sequence from the initial seed of :meth:`parent` to ``self``. + Return the length of a mutation sequence from the initial seed + of :meth:`parent` to ``self``. .. WARNING:: This is the length of the mutation sequence returned by - :meth:`path_from_initial_seed` which need not be the shortest - possible. + :meth:`path_from_initial_seed`, which need not be the + shortest possible. EXAMPLES:: @@ -846,12 +875,13 @@ def depth(self): def path_from_initial_seed(self): r""" - Return a mutation sequence from the initial seed of :meth:`parent` to ``self``. + Return a mutation sequence from the initial seed of :meth:`parent` + to ``self``. .. WARNING:: - This is the path used to compute ``self`` and it does not have to - be the shortest possible. + This is the path used to compute ``self`` and it does not + have to be the shortest possible. EXAMPLES:: @@ -905,7 +935,8 @@ def c_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the c-vector to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the c-vector to return EXAMPLES:: @@ -955,7 +986,8 @@ def g_vector(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the g-vector to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the g-vector to return EXAMPLES:: @@ -985,7 +1017,8 @@ def F_polynomial(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the F-polynomial to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the F-polynomial to return EXAMPLES:: @@ -1015,7 +1048,8 @@ def cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the cluster variable to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the cluster variable to return EXAMPLES:: @@ -1049,28 +1083,34 @@ def mutate(self, k, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials - also. While knowing F-polynomials is essential to computing - cluster variables, the process of mutating them is quite slow. If you - care only about combinatorial data like g-vectors and c-vectors, - setting ``mutating_F=False`` yields significant benefits in terms of - speed. + - ``mutating_F`` -- bool (default ``True``); whether to compute + F-polynomials while mutating + + .. NOTE:: + + While knowing F-polynomials is essential to computing + cluster variables, the process of mutating them is quite slow. + If you care only about combinatorial data like g-vectors and + c-vectors, setting ``mutating_F=False`` yields significant + benefits in terms of speed. EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) sage: S = A.initial_seed() sage: S.mutate(0); S - The seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring obtained from the initial by mutating in direction 0 + The seed of a Cluster Algebra with cluster variables x0, x1 + and no coefficients over Integer Ring obtained from the initial + by mutating in direction 0 sage: S.mutate(5) Traceback (most recent call last): ... - ValueError: Cannot mutate in direction 5 + ValueError: cannot mutate in direction 5 """ n = self.parent().rank() - if k not in range(n): - raise ValueError('Cannot mutate in direction ' + str(k)) + if k < 0 or k >= n: + raise ValueError('cannot mutate in direction ' + str(k)) # store new mutation path if self._path != [] and self._path[-1] == k: @@ -1119,18 +1159,18 @@ def _mutated_F(self, k, old_g_vector): INPUT: - - ``k`` -- an integer in ``range(self.parent().rank())``: the direction - in which we are mutating. + - ``k`` -- an integer in ``range(self.parent().rank())``; + the direction in which we are mutating - - ``old_g_vector`` -- tuple: the k-th g-vector of ``self`` before - mutating. + - ``old_g_vector`` -- tuple; the k-th g-vector of ``self`` + before mutating - NOTE: + .. NOTE:: - This function is the bottleneck of :meth:`mutate`. The problem is - that operations on polynomials are slow. One can get a significant - speed boost by disabling this method calling :meth:`mutate` with - ``mutating_F=False``. + This function is the bottleneck of :meth:`mutate`. The problem is + that operations on polynomials are slow. One can get a significant + speed boost by disabling this method calling :meth:`mutate` with + ``mutating_F=False``. EXAMPLES:: @@ -1160,65 +1200,86 @@ def _mutated_F(self, k, old_g_vector): class ClusterAlgebra(Parent): + r""" + A Cluster Algebra. - Element = ClusterAlgebraElement + INPUT: - def __init__(self, data, **kwargs): - r""" - A Cluster Algebra. + - ``data`` -- some data defining a cluster algebra; it can be anything + that can be parsed by :class:`ClusterQuiver` - INPUT: + - ``scalars`` -- a ring (default `\ZZ`); the scalars over + which the cluster algebra is defined - - ``data`` -- some data defining a cluster algebra: it can be anything - that can be parsed by :class:`ClusterQuiver`. + - ``cluster_variable_prefix`` -- string (default ``'x'``); it needs to be + a valid variable name - - ``scalars`` -- a ring (default `\ZZ`): the scalars over - which the cluster algebra is defined. + - ``cluster_variable_names`` -- a list of strings; each element needs + to be a valid variable name; supersedes ``cluster_variable_prefix`` - - ``cluster_variable_prefix`` -- string (default ``'x'``): it needs to be - a valid variable name. + - ``coefficient_prefix`` -- string (default ``'y'``); it needs to be + a valid variable name. - - ``cluster_variable_names`` -- a list of strings: each element needs - to be a valid variable name; supersedes ``cluster_variable_prefix``. + - ``coefficient_names`` -- a list of strings; each element needs + to be a valid variable name; supersedes ``cluster_variable_prefix`` - - ``coefficient_prefix`` -- string (default ``'y'``): it needs to be - a valid variable name. + - ``principal_coefficients`` -- bool (default ``False``); supersedes any + coefficient defined by ``data`` - - ``coefficient_names`` -- a list of strings: each element needs - to be a valid variable name; supersedes ``cluster_variable_prefix``. + ALGORITHM: - - ``principal_coefficients`` -- bool (default ``False``): supersedes any - coefficient defined by ``data``. + The implementation is mainly based on [FZ2007]_ and [NZ2012]_. - ALGORITHM: + EXAMPLES:: - The implementation is mainly based on [FZ07]_ and [NZ12]_. + sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)]) + sage: A = ClusterAlgebra(B); A + A Cluster Algebra with cluster variables x0, x1, x2, x3 + and coefficients y0, y1 over Integer Ring + sage: A.gens() + (x0, x1, x2, x3, y0, y1) + sage: A = ClusterAlgebra(['A', 2]); A + A Cluster Algebra with cluster variables x0, x1 and no coefficients + over Integer Ring + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A.gens() + (x0, x1, y0, y1) + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x'); A.gens() + (x0, x1, x2, x3) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b', 'c']); A.gens() + (a, b, c, y0, y1, y2) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b']) + Traceback (most recent call last): + ... + ValueError: cluster_variable_names should be a list of 3 valid variable names + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b', 'c']); A.gens() + (x0, x1, x2, a, b, c) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b']) + Traceback (most recent call last): + ... + ValueError: coefficient_names should be a list of 3 valid variable names + """ - EXAMPLES:: + Element = ClusterAlgebraElement + + def __init__(self, data, **kwargs): + """ + Initialize ``self``. + + TESTS:: sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)]) - sage: A = ClusterAlgebra(B); A - A Cluster Algebra with cluster variables x0, x1, x2, x3 and coefficients y0, y1 over Integer Ring - sage: A.gens() - [x0, x1, x2, x3, y0, y1] - sage: A = ClusterAlgebra(['A', 2]); A - A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring - sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A.gens() - [x0, x1, y0, y1] - sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x'); A.gens() - [x0, x1, x2, x3] - sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b', 'c']); A.gens() - [a, b, c, y0, y1, y2] - sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b']) - Traceback (most recent call last): - ... - ValueError: cluster_variable_names should be a list of 3 valid variable names - sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b', 'c']); A.gens() - [x0, x1, x2, a, b, c] - sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b']) - Traceback (most recent call last): - ... - ValueError: coefficient_names should be a list of 3 valid variable names + sage: A = ClusterAlgebra(B) + sage: TestSuite(A).run() + sage: A = ClusterAlgebra(['A', 2]) + sage: TestSuite(A).run() + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) + sage: TestSuite(A).run() + sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x') + sage: TestSuite(A).run() + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a','b','c']) + sage: TestSuite(A).run() + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a','b','c']) + sage: TestSuite(A).run() """ # Temporary variables Q = ClusterQuiver(data) @@ -1267,11 +1328,15 @@ def __init__(self, data, **kwargs): # setup Parent and ambient self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) - Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) + Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), + names=variables + coefficients) # Data to compute cluster variables using separation of additions - self._y = dict((self._U.gen(j), prod(self._base.gen(i) ** M0[i, j] for i in range(m))) for j in range(n)) - self._yhat = dict((self._U.gen(j), prod(self._ambient.gen(i) ** B0[i, j] for i in range(n + m))) for j in range(n)) + self._y = {self._U.gen(j): prod(self._base.gen(i) ** M0[i, j] for i in range(m)) + for j in range(n)} + self._yhat = {self._U.gen(j): prod(self._ambient.gen(i) ** B0[i, j] + for i in range(n + m)) + for j in range(n)} # Have we got principal coefficients? self._is_principal = (M0 == I) @@ -1332,7 +1397,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a :class:`ClusterAlgebra`. + - ``other`` -- a :class:`ClusterAlgebra` ALGORITHM: @@ -1358,7 +1423,8 @@ def __eq__(self, other): sage: A1 == B False """ - return isinstance(other, ClusterAlgebra) and self._B0 == other._B0 and self.ambient() == other.ambient() + return (isinstance(other, ClusterAlgebra) and self._B0 == other._B0 + and self.ambient() == other.ambient()) def _repr_(self): r""" @@ -1367,9 +1433,11 @@ def _repr_(self): EXAMPLES:: sage: A = ClusterAlgebra(matrix(1), principal_coefficients=True); A - A Cluster Algebra with cluster variable x0 and coefficient y0 over Integer Ring + A Cluster Algebra with cluster variable x0 + and coefficient y0 over Integer Ring sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A - A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + A Cluster Algebra with cluster variables x0, x1 + and coefficients y0, y1 over Integer Ring """ var_names = self.initial_cluster_variable_names() var_names = (" " if len(var_names) == 1 else "s ") + ", ".join(var_names) @@ -1466,12 +1534,14 @@ def _coerce_map_from_(self, other): def rank(self): r""" - Return the rank of ``self``, i.e. the number of cluster variables in any seed. + Return the rank of ``self``, i.e. the number of cluster variables + in any seed. EXAMPLES:: sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A - A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + A Cluster Algebra with cluster variables x0, x1 + and coefficients y0, y1 over Integer Ring sage: A.rank() 2 """ @@ -1485,17 +1555,19 @@ def current_seed(self): sage: A = ClusterAlgebra(['A', 2]) sage: A.current_seed() - The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring + The initial seed of a Cluster Algebra with cluster variables x0, x1 + and no coefficients over Integer Ring """ return self._seed def set_current_seed(self, seed): r""" - Set the value reported by :meth:`current_seed` to ``seed``, if it makes sense. + Set the value reported by :meth:`current_seed` to ``seed``, + if it makes sense. INPUT: - - ``seed`` -- an instance of :class:`ClusterAlgebraSeed`. + - ``seed`` -- a :class:`ClusterAlgebraSeed` EXAMPLES:: @@ -1520,7 +1592,8 @@ def set_current_seed(self, seed): def reset_current_seed(self): r""" - Reset the value reported by :meth:`current_seed` to :meth:`initial_seed`. + Reset the value reported by :meth:`current_seed` + to :meth:`initial_seed`. EXAMPLES:: @@ -1540,7 +1613,7 @@ def contains_seed(self, seed): INPUT: - - ``seed`` -- an instance of :class:`ClusterAlgebraSeed`. + - ``seed`` -- a :class:`ClusterAlgebraSeed` EXAMPLES:: @@ -1588,15 +1661,15 @@ def g_vectors(self, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also; - for speed considerations you may want to disable this. + - ``mutating_F`` -- bool (default ``True``); whether to compute + F-polynomials; disable this for speed considerations ALGORITHM: - This method does not use the caching framework provided by ``self`` but - recomputes all the g-vectors from scratch. On the other hand it stores - the results so that other methods like :meth:`g_vectors_so_far` can - access them afterwards. + This method does not use the caching framework provided by ``self``, + but recomputes all the g-vectors from scratch. On the other hand it + stores the results so that other methods like :meth:`g_vectors_so_far` + can access them afterwards. EXAMPLES:: @@ -1622,10 +1695,10 @@ def cluster_variables(self): ALGORITHM: - This method does not use the caching framework provided by ``self`` but - recomputes all the cluster variables from scratch. On the other hand it - stores the results so that other methods like :meth:`cluster_variables_so_far` - can access them afterwards. + This method does not use the caching framework provided by ``self``, + but recomputes all the cluster variables from scratch. On the other + hand it stores the results so that other methods like + :meth:`cluster_variables_so_far` can access them afterwards. EXAMPLES:: @@ -1641,10 +1714,10 @@ def F_polynomials(self): ALGORITHM: - This method does not use the caching framework provided by ``self`` but - recomputes all the F_polynomials from scratch. On the other hand it - stores the results so that other methods like :meth:`F_polynomials_so_far` - can access them afterwards. + This method does not use the caching framework provided by ``self``, + but recomputes all the F-polynomials from scratch. On the other hand + it stores the results so that other methods like + :meth:`F_polynomials_so_far` can access them afterwards. EXAMPLES:: @@ -1696,19 +1769,20 @@ def F_polynomials_so_far(self): @cached_method(key=lambda a, b: tuple(b)) def cluster_variable(self, g_vector): r""" - Return the cluster variable with g-vector ``g_vector`` if it has been found. + Return the cluster variable with g-vector ``g_vector`` if it has + been found. INPUT: - - ``g_vector`` -- a tuple: the g-vector of the cluster variable to return. + - ``g_vector`` -- tuple; the g-vector of the cluster variable to return ALGORITHM: This function computes cluster variables from their g-vectors and - F-polynomials using the "separation of additions" formula of Theorem - 3.7 in [FZ07]_. + F-polynomials using the "separation of additions" formula of + Theorem 3.7 in [FZ2007]_. - EXAMPLE:: + EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) sage: A.initial_seed().mutate(0) @@ -1724,11 +1798,12 @@ def cluster_variable(self, g_vector): def F_polynomial(self, g_vector): r""" - Return the F-polynomial with g-vector ``g_vector`` if it has been found. + Return the F-polynomial with g-vector ``g_vector`` if it has + been found. INPUT: - - ``g_vector`` -- a tuple: the g-vector of the F-polynomial to return. + - ``g_vector`` -- tuple; the g-vector of the F-polynomial to return EXAMPLES:: @@ -1736,13 +1811,13 @@ def F_polynomial(self, g_vector): sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... - KeyError: 'The g-vector (-1, 1) has not been found yet' + KeyError: 'the g-vector (-1, 1) has not been found yet' sage: A.initial_seed().mutate(0, mutating_F=False) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... - KeyError: 'The F-polynomial with g-vector (-1, 1) has not been computed yet; - you can compute it by mutating from the initial seed along the sequence [0]' + KeyError: 'the F-polynomial with g-vector (-1, 1) has not been computed yet; + you can compute it by mutating from the initial seed along the sequence [0]' sage: A.initial_seed().mutate(0) sage: A.F_polynomial((-1, 1)) u0 + 1 @@ -1752,12 +1827,12 @@ def F_polynomial(self, g_vector): return self._F_poly_dict[g_vector] except KeyError: if g_vector in self._path_dict: - msg = "The F-polynomial with g-vector %s has not been computed yet; " % str(g_vector) + msg = "the F-polynomial with g-vector {} has not been computed yet; ".format(g_vector) msg += "you can compute it by mutating from the initial seed along the sequence " msg += str(self._path_dict[g_vector]) raise KeyError(msg) else: - raise KeyError("The g-vector %s has not been found yet" % str(g_vector)) + raise KeyError("the g-vector %s has not been found yet" % str(g_vector)) def find_g_vector(self, g_vector, depth=infinity): r""" @@ -1765,9 +1840,9 @@ def find_g_vector(self, g_vector, depth=infinity): INPUT: - - ``g_vector`` -- a tuple: the g-vector to find. - - - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum distance from ``self.current_seed`` to reach. + - ``g_vector`` -- a tuple: the g-vector to find + - ``depth`` -- a positive integer or infinity (default ``infinity``); + the maximum distance from ``self.current_seed`` to reach OUTPUT: @@ -1787,7 +1862,9 @@ def find_g_vector(self, g_vector, depth=infinity): sage: A.find_g_vector((1, 1), depth=4) Traceback (most recent call last): ... - ValueError: (1, 1) is not the g-vector of any cluster variable of a Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring + ValueError: (1, 1) is not the g-vector of any cluster variable of a + Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 + over Integer Ring """ g_vector = tuple(g_vector) while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: @@ -1857,6 +1934,7 @@ def retract(self, x): """ return self(x) + @cached_method def gens(self): r""" Return the list of initial cluster variables and coefficients of ``self``. @@ -1865,12 +1943,12 @@ def gens(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.gens() - [x0, x1, y0, y1] + (x0, x1, y0, y1) sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x') sage: A.gens() - [x0, x1, x2, x3] + (x0, x1, x2, x3) """ - return list(map(self.retract, self.ambient().gens())) + return tuple(map(self.retract, self.ambient().gens())) def coefficient(self, j): r""" @@ -1878,7 +1956,8 @@ def coefficient(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the coefficient to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the coefficient to return EXAMPLES:: @@ -1886,11 +1965,11 @@ def coefficient(self, j): sage: A.coefficient(0) y0 """ - if isinstance(self.base(), LaurentPolynomialRing_generic): - return self.retract(self.base().gen(j)) - else: - raise ValueError("Generator not defined") + if not isinstance(self.base(), LaurentPolynomialRing_generic): + raise ValueError("generator not defined") + return self.retract(self.base().gen(j)) + @cached_method def coefficients(self): r""" Return the list of coefficients of ``self``. @@ -1899,15 +1978,15 @@ def coefficients(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.coefficients() - [y0, y1] + (y0, y1) sage: B = ClusterAlgebra(['B', 2]) sage: B.coefficients() - [] + () """ if isinstance(self.base(), LaurentPolynomialRing_generic): - return list(map(self.retract, self.base().gens())) + return tuple(map(self.retract, self.base().gens())) else: - return [] + return () def coefficient_names(self): r""" @@ -1933,7 +2012,8 @@ def initial_cluster_variable(self, j): INPUT: - - ``j`` -- an integer in ``range(self.parent().rank())``: the index of the cluster variable to return. + - ``j`` -- an integer in ``range(self.parent().rank())``; + the index of the cluster variable to return EXAMPLES:: @@ -1943,6 +2023,7 @@ def initial_cluster_variable(self, j): """ return self.retract(self.ambient().gen(j)) + @cached_method def initial_cluster_variables(self): r""" Return the list of initial cluster variables of ``self``. @@ -1951,9 +2032,9 @@ def initial_cluster_variables(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.initial_cluster_variables() - [x0, x1] + (x0, x1) """ - return list(map(self.retract, self.ambient().gens()[:self.rank()])) + return tuple(map(self.retract, self.ambient().gens()[:self.rank()])) def initial_cluster_variable_names(self): r""" @@ -1976,22 +2057,22 @@ def seeds(self, **kwargs): INPUT: - - ``from_current_seed`` -- bool (default ``False``): whether to start the - iterator from :meth:`current_seed` or :meth:`initial_seed`. + - ``from_current_seed`` -- bool (default ``False``); whether to start + the iterator from :meth:`current_seed` or :meth:`initial_seed` - - ``mutating_F`` -- bool (default ``True``): whether to compute F-polynomials also; - for speed considerations you may want to disable this. + - ``mutating_F`` -- bool (default ``True``); whether to compute + F-polynomials also; disable this for speed considerations - - ``allowed_directions`` -- a tuple of integers (default ``range(self.rank())``): the - directions in which to mutate. + - ``allowed_directions`` -- iterable of integers + (default ``range(self.rank())``); the directions in which to mutate - - ``depth`` -- a positive integer or infinity (default ``infinity``): - the maximum depth at which to stop searching. + - ``depth`` -- a positive integer or infinity (default ``infinity``); + the maximum depth at which to stop searching - - ``catch_KeyboardInterrupt`` -- bool (default ``False``): whether to + - ``catch_KeyboardInterrupt`` -- bool (default ``False``); whether to catch ``KeyboardInterrupt`` and return it rather then raising an - exception. This allows the iterator returned by this method to be - resumed after being interrupted. + exception -- this allows the iterator returned by this method to be + resumed after being interrupted ALGORITHM: @@ -2065,7 +2146,7 @@ def seeds(self, **kwargs): yield new_sd except KeyboardInterrupt as e: if kwargs.get('catch_KeyboardInterrupt', False): - print("Caught a KeyboardInterrupt; cleaning up before returning.") + print("caught a KeyboardInterrupt; cleaning up before returning") # mutation in direction i was not completed; put it back in for next round directions.append(i) yield e @@ -2081,8 +2162,8 @@ def reset_exploring_iterator(self, mutating_F=True): INPUT: - - ``mutating_F`` -- bool (default ``True``): whether to also compute - F-polynomials; for speed considerations you may want to disable this. + - ``mutating_F`` -- bool (default ``True``); whether to also compute + F-polynomials; disable this for speed considerations EXAMPLES:: @@ -2099,11 +2180,13 @@ def reset_exploring_iterator(self, mutating_F=True): def explore_to_depth(self, depth): r""" - Explore the exchange graph of ``self`` up to distance ``depth`` from the initial seed. + Explore the exchange graph of ``self`` up to distance ``depth`` + from the initial seed. INPUT: - - ``depth`` -- a positive integer or infinity: the maximum depth at which to stop searching. + - ``depth`` -- a positive integer or infinity; the maximum depth + at which to stop searching EXAMPLES:: @@ -2129,7 +2212,8 @@ def cluster_fan(self, depth=infinity): INPUT: - - ``depth`` -- a positive integer or infinity (default ``infinity``): the maximum depth at which to compute. + - ``depth`` -- a positive integer or infinity (default ``infinity``); + the maximum depth at which to compute EXAMPLES:: @@ -2144,17 +2228,18 @@ def cluster_fan(self, depth=infinity): @_mutation_parse def mutate_initial(self, k): r""" - Return the cluster algebra obtained by mutating ``self`` at the initial seed. + Return the cluster algebra obtained by mutating ``self`` at + the initial seed. INPUT: ALGORITHM: - This function computes data for the new algebra from known data for the - old algebra using [NZ12]_ equation (4.2) for g-vectors, and [FZ07]_ - equation (6.21) for F-polynomials. The exponent ``h`` in the formula - for F-polynomials is ``-min(0, old_g_vect[k])`` due to [NZ12]_ - Proposition 4.2. + This function computes data for the new algebra from known data for + the old algebra using Equation (4.2) from [NZ2012]_ for g-vectors, and + Equation (6.21) from [FZ2007]_ for F-polynomials. The exponent `h` + in the formula for F-polynomials is ``-min(0, old_g_vect[k])`` + due to [NZ2012]_ Proposition 4.2. EXAMPLES:: @@ -2170,7 +2255,7 @@ def mutate_initial(self, k): """ n = self.rank() if k not in range(n): - raise ValueError('Cannot mutate in direction ' + str(k)) + raise ValueError('cannot mutate in direction ' + str(k)) # save computed data old_F_poly_dict = copy(self._F_poly_dict) @@ -2237,9 +2322,9 @@ def upper_cluster_algebra(self): sage: A.upper_cluster_algebra() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet + NotImplementedError: not implemented yet """ - raise NotImplementedError("Not implemented yet") + raise NotImplementedError("not implemented yet") def upper_bound(self): r""" @@ -2251,9 +2336,9 @@ def upper_bound(self): sage: A.upper_bound() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet + NotImplementedError: not implemented yet """ - raise NotImplementedError("Not implemented yet") + raise NotImplementedError("not implemented yet") def lower_bound(self): r""" @@ -2265,9 +2350,9 @@ def lower_bound(self): sage: A.lower_bound() Traceback (most recent call last): ... - NotImplementedError: Not implemented yet + NotImplementedError: not implemented yet """ - raise NotImplementedError("Not implemented yet") + raise NotImplementedError("not implemented yet") def theta_basis_element(self, g_vector): r""" @@ -2279,9 +2364,9 @@ def theta_basis_element(self, g_vector): sage: A.theta_basis_element((1, 0, 0, 0)) Traceback (most recent call last): ... - NotImplementedError: Not implemented yet + NotImplementedError: not implemented yet """ - raise NotImplementedError("Not implemented yet") + raise NotImplementedError("not implemented yet") #### # Methods only defined for special cases @@ -2294,11 +2379,13 @@ def greedy_element(self, d_vector): INPUT: - - ``d_vector`` -- tuple of 2 integers: the denominator vector of the element to compute. + - ``d_vector`` -- tuple of 2 integers; the denominator vector of + the element to compute ALGORITHM: - This implements greedy elements of a rank 2 cluster algebra from [LLZ14]_ equation (1.5). + This implements greedy elements of a rank 2 cluster algebra using + Equation (1.5) from [LLZ2014]_. EXAMPLES:: @@ -2309,7 +2396,8 @@ def greedy_element(self, d_vector): b = abs(self.b_matrix()[0, 1]) c = abs(self.b_matrix()[1, 0]) a1, a2 = d_vector - # here we use the generators of self.ambient() because cluster variables do not have an inverse. + # Here we use the generators of self.ambient() because cluster variables + # do not have an inverse. x1, x2 = self.ambient().gens() if a1 < 0: if a2 < 0: @@ -2327,7 +2415,8 @@ def greedy_element(self, d_vector): def _greedy_coefficient(self, d_vector, p, q): r""" - Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` in the numerator of the greedy element with denominator vector ``d_vector``. + Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` + in the numerator of the greedy element with denominator vector ``d_vector``. EXAMPLES:: @@ -2359,3 +2448,4 @@ def _greedy_coefficient(self, d_vector, p, q): bino = binomial(a1 - b * p + l - 1, l) sum2 += (-1) ** (l - 1) * self._greedy_coefficient(d_vector, p, q - l) * bino return Integer(max(sum1, sum2)) + From 40deffe229c3dce3732df9fb998d95abe634b699 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 22 Nov 2016 02:40:30 -0600 Subject: [PATCH 180/191] Creating element class for cluster algebras with prinicpal coefficients. --- src/sage/algebras/cluster_algebra.py | 197 ++++++++++++--------------- 1 file changed, 87 insertions(+), 110 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 21c54b204ca..89d7a6dffa6 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -452,31 +452,6 @@ class ClusterAlgebraElement(ElementWrapper): """ An element of a cluster algebra. """ - def __init__(self, parent, value): - r""" - Initialize ``self``. - - INPUT: - - - ``parent`` -- :class:`ClusterAlgebra`; the parent algebra - - ``value`` -- the value of the element - - EXAMPLES:: - - sage: A = ClusterAlgebra(['F', 4]) - sage: from sage.algebras.cluster_algebra import ClusterAlgebraElement - sage: ClusterAlgebraElement(A, 1) - 1 - """ - ElementWrapper.__init__(self, parent=parent, value=value) - - # setup methods defined only in special cases - if parent._is_principal: - self.g_vector = MethodType(g_vector, self, self.__class__) - self.F_polynomial = MethodType(F_polynomial, self, self.__class__) - self.is_homogeneous = MethodType(is_homogeneous, self, self.__class__) - self.homogeneous_components = MethodType(homogeneous_components, self, self.__class__) - # AdditiveMagmas.Subobjects currently does not implements _add_ def _add_(self, other): r""" @@ -567,97 +542,99 @@ def _repr_(self): # Methods not always defined #### +class PrincipalClusterAlgebraElement(ClusterAlgebraElement): + """ + An element in a cluster algebra with principle coefficients. + """ + def g_vector(self): + r""" + Return the g-vector of ``self``. -def g_vector(self): - r""" - Return the g-vector of ``self``. - - EXAMPLES:: + EXAMPLES:: - sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: A.cluster_variable((1, 0)).g_vector() == (1, 0) - True - sage: sum(A.initial_cluster_variables()).g_vector() - Traceback (most recent call last): - ... - ValueError: this element is not homogeneous - """ - components = self.homogeneous_components() - if len(components) != 1: - raise ValueError("this element is not homogeneous") - return components.keys()[0] + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: A.cluster_variable((1, 0)).g_vector() == (1, 0) + True + sage: sum(A.initial_cluster_variables()).g_vector() + Traceback (most recent call last): + ... + ValueError: this element is not homogeneous + """ + components = self.homogeneous_components() + if len(components) != 1: + raise ValueError("this element is not homogeneous") + return components.keys()[0] + def F_polynomial(self): + r""" + Return the F-polynomial of ``self``. -def F_polynomial(self): - r""" - Return the F-polynomial of ``self``. + EXAMPLES:: - EXAMPLES:: - sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: S = A.initial_seed() - sage: S.mutate([0, 1, 0]) - sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) - True - sage: sum(A.initial_cluster_variables()).F_polynomial() - Traceback (most recent call last): - ... - ValueError: this element is not homogeneous - """ - if not self.is_homogeneous(): - raise ValueError("this element is not homogeneous") - subs_dict = dict() - A = self.parent() - for x in A.initial_cluster_variables(): - subs_dict[x.lift()] = A._U(1) - for i in range(A.rank()): - subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) - - -def is_homogeneous(self): - r""" - Return ``True`` if ``self`` is a homogeneous element of ``self.parent()``. + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: S = A.initial_seed() + sage: S.mutate([0, 1, 0]) + sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0) + True + sage: sum(A.initial_cluster_variables()).F_polynomial() + Traceback (most recent call last): + ... + ValueError: this element is not homogeneous + """ + if not self.is_homogeneous(): + raise ValueError("this element is not homogeneous") + subs_dict = dict() + A = self.parent() + for x in A.initial_cluster_variables(): + subs_dict[x.lift()] = A._U(1) + for i in range(A.rank()): + subs_dict[A.coefficient(i).lift()] = A._U.gen(i) + return self.lift().substitute(subs_dict) - EXAMPLES:: + def is_homogeneous(self): + r""" + Return ``True`` if ``self`` is a homogeneous element + of ``self.parent()``. - sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: A.cluster_variable((1, 0)).is_homogeneous() - True - sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) - sage: x.is_homogeneous() - False - """ - return len(self.homogeneous_components()) == 1 + EXAMPLES:: + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: A.cluster_variable((1, 0)).is_homogeneous() + True + sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) + sage: x.is_homogeneous() + False + """ + return len(self.homogeneous_components()) == 1 -def homogeneous_components(self): - r""" - Return a dictionary of the homogeneous components of ``self``. + def homogeneous_components(self): + r""" + Return a dictionary of the homogeneous components of ``self``. - OUTPUT: + OUTPUT: - A dictionary whose keys are homogeneous degrees and whose values are the - summands of ``self`` of the given degree. + A dictionary whose keys are homogeneous degrees and whose values + are the summands of ``self`` of the given degree. - EXAMPLES:: + EXAMPLES:: - sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) - sage: x.homogeneous_components() - {(0, 1): x1, (1, 0): x0} - """ - deg_matrix = block_matrix([[identity_matrix(self.parent().rank()), - -self.parent().b_matrix()]]) - components = dict() - x = self.lift() - monomials = x.monomials() - for m in monomials: - g_vect = tuple(deg_matrix * vector(m.exponents()[0])) - if g_vect in components: - components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) - else: - components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) - return components + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1)) + sage: x.homogeneous_components() + {(0, 1): x1, (1, 0): x0} + """ + deg_matrix = block_matrix([[identity_matrix(self.parent().rank()), + -self.parent().b_matrix()]]) + components = dict() + x = self.lift() + monomials = x.monomials() + for m in monomials: + g_vect = tuple(deg_matrix * vector(m.exponents()[0])) + if g_vect in components: + components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) + else: + components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) + return components ############################################################################## @@ -1198,7 +1175,6 @@ def _mutated_F(self, k, old_g_vector): # Cluster algebras ############################################################################## - class ClusterAlgebra(Parent): r""" A Cluster Algebra. @@ -1258,9 +1234,6 @@ class ClusterAlgebra(Parent): ... ValueError: coefficient_names should be a list of 3 valid variable names """ - - Element = ClusterAlgebraElement - def __init__(self, data, **kwargs): """ Initialize ``self``. @@ -1326,6 +1299,13 @@ def __init__(self, data, **kwargs): base = scalars coefficients = [] + # Have we got principal coefficients? + self._is_principal = (M0 == I) + if self._is_principal: + self.Element = PrincipalClusterAlgebraElement + else: + self.Element = ClusterAlgebraElement + # setup Parent and ambient self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), @@ -1338,9 +1318,6 @@ def __init__(self, data, **kwargs): for i in range(n + m)) for j in range(n)} - # Have we got principal coefficients? - self._is_principal = (M0 == I) - # Store initial data # NOTE: storing both _B0 as rectangular matrix and _yhat is redundant. # We keep both around for speed purposes. From 6c2d7a2613d332dbdc8068fb92a27885b74d11d9 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Wed, 23 Nov 2016 23:51:57 +0100 Subject: [PATCH 181/191] First attempt at using UniqueRepresentation, mutate_initial should now be broken --- src/sage/algebras/cluster_algebra.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 89d7a6dffa6..33e25f400e2 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -357,6 +357,7 @@ from sage.structure.parent import Parent from sage.structure.sage_object import SageObject from six.moves import range as range +from sage.structure.unique_representation import UniqueRepresentation ############################################################################## # Helper functions @@ -1175,7 +1176,7 @@ def _mutated_F(self, k, old_g_vector): # Cluster algebras ############################################################################## -class ClusterAlgebra(Parent): +class ClusterAlgebra(Parent, UniqueRepresentation): r""" A Cluster Algebra. @@ -1234,7 +1235,16 @@ class ClusterAlgebra(Parent): ... ValueError: coefficient_names should be a list of 3 valid variable names """ - def __init__(self, data, **kwargs): + + @staticmethod + def __classcall__(self, data, **kwargs): + Q = ClusterQuiver(data) + for key in kwargs: + if isinstance(kwargs[key],list): + kwargs[key] = tuple(kwargs[key]) + return super(ClusterAlgebra, self).__classcall__(self, Q, **kwargs) + + def __init__(self, Q, **kwargs): """ Initialize ``self``. @@ -1245,7 +1255,7 @@ def __init__(self, data, **kwargs): sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 2]) sage: TestSuite(A).run() - sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) + sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True) sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x') sage: TestSuite(A).run() @@ -1255,7 +1265,6 @@ def __init__(self, data, **kwargs): sage: TestSuite(A).run() """ # Temporary variables - Q = ClusterQuiver(data) n = Q.n() I = identity_matrix(n) if kwargs.get('principal_coefficients', False): @@ -1433,7 +1442,7 @@ def _an_element_(self): sage: A.an_element() x0 """ - return self.current_seed().cluster_variable(0) + return self.initial_cluster_variable(0) def _coerce_map_from_(self, other): r""" @@ -1575,6 +1584,7 @@ def reset_current_seed(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.reset_current_seed() sage: A.current_seed().mutate([1, 0]) sage: A.current_seed() == A.initial_seed() False @@ -1784,7 +1794,7 @@ def F_polynomial(self, g_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['A', 2]) + sage: A = ClusterAlgebra(['C', 2]) sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... @@ -2144,11 +2154,11 @@ def reset_exploring_iterator(self, mutating_F=True): EXAMPLES:: - sage: A = ClusterAlgebra(['A', 4]) + sage: A = ClusterAlgebra(['C', 4]) sage: A.reset_exploring_iterator(mutating_F=False) sage: A.explore_to_depth(infinity) sage: len(A.g_vectors_so_far()) - 14 + 20 sage: len(A.F_polynomials_so_far()) 4 """ @@ -2252,7 +2262,7 @@ def mutate_initial(self, k): cv_names = self.initial_cluster_variable_names() coeff_names = self.coefficient_names() scalars = self.scalars() - self.__init__(B0, cluster_variable_names=cv_names, + self.__init__(ClusterQuiver(B0), cluster_variable_names=cv_names, coefficient_names=coeff_names, scalars=scalars) # substitution data to compute new F-polynomials From 130de19b1827199699660f418e5dbc5fb0771779 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Nov 2016 10:30:42 +0100 Subject: [PATCH 182/191] Second pass at using UniqueRepresentatio; still need to remove useless methods like __eq__ --- src/sage/algebras/cluster_algebra.py | 151 ++++++++++++++++++--------- 1 file changed, 99 insertions(+), 52 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 33e25f400e2..4f48efa791e 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -258,6 +258,23 @@ sage: S1 == A.current_seed() False +Since :class:`ClusterAlgebra` inherits from :class:`UniqueRepresentation`, +computed data is shared across instances:: + + sage: A1 = ClusterAlgebra(['F', 4]) + sage: A1 is A + True + sage: len(A1.g_vectors_so_far()) + 11 + +It can be useful, at times to forget all computed data. Because of +:class:`UniqueRepresentation` this cannot be achieved by simply creating a +new instance; instead it has to be manually triggered by:: + + sage: A.clear_computed_data() + sage: len(A.g_vectors_so_far()) + 4 + Given a cluster algebra ``A`` we may be looking for a specific cluster variable:: @@ -722,6 +739,7 @@ def __eq__(self, other): EXAMPLES:: sage: A = ClusterAlgebra(['A', 3]) + sage: A.clear_computed_data() sage: S = copy(A.current_seed()) sage: S.mutate([0, 2, 0]) sage: S == A.current_seed() @@ -730,10 +748,10 @@ def __eq__(self, other): sage: S == A.current_seed() True - sage: B = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: S = B.current_seed() + sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: S = A.current_seed() sage: S.mutate(0) - sage: S == B.current_seed() + sage: S == A.current_seed() True """ return (isinstance(other, ClusterAlgebraSeed) @@ -793,6 +811,7 @@ def _repr_(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 3]) + sage: A.clear_computed_data() sage: S = A.current_seed(); S The initial seed of a Cluster Algebra with cluster variables x0, x1, x2 and no coefficients over Integer Ring @@ -1252,52 +1271,57 @@ def __init__(self, Q, **kwargs): sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)]) sage: A = ClusterAlgebra(B) + sage: A.clear_computed_data() sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True) + sage: A.clear_computed_data() sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x') + sage: A.clear_computed_data() sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a','b','c']) + sage: A.clear_computed_data() sage: TestSuite(A).run() sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a','b','c']) + sage: A.clear_computed_data() sage: TestSuite(A).run() """ - # Temporary variables - n = Q.n() - I = identity_matrix(n) + # Parse input + self._n = Q.n() + I = identity_matrix(self._n) if kwargs.get('principal_coefficients', False): M0 = I else: - M0 = Q.b_matrix()[n:, :] - B0 = block_matrix([[Q.b_matrix()[:n, :]], [M0]]) + M0 = Q.b_matrix()[self._n:, :] + self._B0 = block_matrix([[Q.b_matrix()[:self._n, :]], [M0]]) m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more # natural ZZ. The reason is that _mutated_F is faster if we do not cast # the result to polynomials but then we get "rational" coefficients - self._U = PolynomialRing(QQ, ['u%s' % i for i in range(n)]) + self._U = PolynomialRing(QQ, ['u%s' % i for i in range(self._n)]) - # Storage for computed data - self._path_dict = dict((v, []) for v in map(tuple, I.columns())) - self._F_poly_dict = dict((v, self._U(1)) for v in self._path_dict) + # Setup infrastruture to store computed data + self.clear_computed_data() # Determine the names of the initial cluster variables variables_prefix = kwargs.get('cluster_variable_prefix', 'x') - variables = list(kwargs.get('cluster_variable_names', [variables_prefix + str(i) for i in range(n)])) - if len(variables) != n: - raise ValueError("cluster_variable_names should be a list of %d valid variable names" % n) + variables = list(kwargs.get('cluster_variable_names', [variables_prefix + str(i) for i in range(self._n)])) + if len(variables) != self._n: + raise ValueError("cluster_variable_names should be a list of %d valid variable names" % self._n) # Determine scalars scalars = kwargs.get('scalars', ZZ) - # Determine coefficients and setup self._base + # Determine coefficients and base if m > 0: coefficient_prefix = kwargs.get('coefficient_prefix', 'y') if coefficient_prefix == variables_prefix: - offset = n + offset = self._n else: offset = 0 coefficients = list(kwargs.get('coefficient_names', [coefficient_prefix + str(i) for i in range(offset, m + offset)])) @@ -1309,36 +1333,27 @@ def __init__(self, Q, **kwargs): coefficients = [] # Have we got principal coefficients? - self._is_principal = (M0 == I) - if self._is_principal: + if M0 == I: self.Element = PrincipalClusterAlgebraElement else: self.Element = ClusterAlgebraElement - # setup Parent and ambient + # Setup Parent and ambient self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) # Data to compute cluster variables using separation of additions - self._y = {self._U.gen(j): prod(self._base.gen(i) ** M0[i, j] for i in range(m)) - for j in range(n)} - self._yhat = {self._U.gen(j): prod(self._ambient.gen(i) ** B0[i, j] - for i in range(n + m)) - for j in range(n)} - - # Store initial data # NOTE: storing both _B0 as rectangular matrix and _yhat is redundant. # We keep both around for speed purposes. - self._B0 = copy(B0) - self._n = n - self.reset_current_seed() - - # Internal data for exploring the exchange graph - self.reset_exploring_iterator() + self._y = {self._U.gen(j): prod(self._base.gen(i) ** M0[i, j] for i in range(m)) + for j in range(self._n)} + self._yhat = {self._U.gen(j): prod(self._ambient.gen(i) ** self._B0[i, j] + for i in range(self._n + m)) + for j in range(self._n)} # Add methods that are defined only for special cases - if n == 2 and m == 0: + if self._n == 2 and m == 0: self.greedy_element = MethodType(greedy_element, self, self.__class__) self._greedy_coefficient = MethodType(_greedy_coefficient, self, self.__class__) @@ -1388,7 +1403,7 @@ def __eq__(self, other): ALGORITHM: ``self`` and ``other`` are deemed to be equal if they have the same - initial exchange matrix and their ambients coincide. In particular we + initial exchange matrix and their ambients coincide. In particular we do not keep track of how much each Cluster Algebra has been explored. EXAMPLES:: @@ -1451,8 +1466,8 @@ def _coerce_map_from_(self, other): ALGORITHM: If ``other`` is an instance of :class:`ClusterAlgebra` then allow - coercion if ``other.ambient()`` can be coerced into ``self.ambient()`` - and ``other`` can be obtained from ``self`` by permuting variables + coercion if ``other.ambient()`` can be coerced into ``self.ambient()`` + and ``other`` can be obtained from ``self`` by permuting variables and coefficients and/or freezing some initial cluster variables. Otherwise allow anything that coerces into ``self.base()`` to coerce @@ -1540,6 +1555,7 @@ def current_seed(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: A.current_seed() The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring @@ -1558,6 +1574,7 @@ def set_current_seed(self, seed): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: S = copy(A.current_seed()) sage: S.mutate([0, 1, 0]) sage: A.current_seed() == S @@ -1565,8 +1582,8 @@ def set_current_seed(self, seed): sage: A.set_current_seed(S) sage: A.current_seed() == S True - sage: B = ClusterAlgebra(['B', 2]) - sage: A.set_current_seed(B.initial_seed()) + sage: A1 = ClusterAlgebra(['B', 2]) + sage: A.set_current_seed(A1.initial_seed()) Traceback (most recent call last): ... ValueError: This is not a seed in this cluster algebra @@ -1584,7 +1601,7 @@ def reset_current_seed(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) - sage: A.reset_current_seed() + sage: A.clear_computed_data() sage: A.current_seed().mutate([1, 0]) sage: A.current_seed() == A.initial_seed() False @@ -1594,6 +1611,29 @@ def reset_current_seed(self): """ self._seed = self.initial_seed() + def clear_computed_data(self): + r""" + Clear the cache of computed g-vectors and F-polynomials + and reset both the current seed and the exploring iterator. + + EXAMPLES:: + sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() + sage: A.g_vectors_so_far() + [(0, 1), (1, 0)] + sage: A.current_seed().mutate([1, 0]) + sage: A.g_vectors_so_far() + [(0, 1), (0, -1), (1, 0), (-1, 0)] + sage: A.clear_computed_data() + sage: A.g_vectors_so_far() + [(0, 1), (1, 0)] + """ + I = identity_matrix(self._n) + self._path_dict = dict((v, []) for v in map(tuple, I.columns())) + self._F_poly_dict = dict((v, self._U(1)) for v in self._path_dict) + self.reset_current_seed() + self.reset_exploring_iterator() + def contains_seed(self, seed): r""" Test if ``seed`` is a seed of ``self``. @@ -1701,7 +1741,7 @@ def F_polynomials(self): ALGORITHM: - This method does not use the caching framework provided by ``self``, + This method does not use the caching framework provided by ``self``, but recomputes all the F-polynomials from scratch. On the other hand it stores the results so that other methods like :meth:`F_polynomials_so_far` can access them afterwards. @@ -1721,6 +1761,7 @@ def g_vectors_so_far(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: A.current_seed().mutate(0) sage: A.g_vectors_so_far() [(0, 1), (1, 0), (-1, 1)] @@ -1734,6 +1775,7 @@ def cluster_variables_so_far(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: A.current_seed().mutate(0) sage: A.cluster_variables_so_far() [x1, x0, (x1 + 1)/x0] @@ -1747,6 +1789,7 @@ def F_polynomials_so_far(self): EXAMPLES:: sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: A.current_seed().mutate(0) sage: A.F_polynomials_so_far() [1, 1, u0 + 1] @@ -1794,7 +1837,8 @@ def F_polynomial(self, g_vector): EXAMPLES:: - sage: A = ClusterAlgebra(['C', 2]) + sage: A = ClusterAlgebra(['A', 2]) + sage: A.clear_computed_data() sage: A.F_polynomial((-1, 1)) Traceback (most recent call last): ... @@ -1842,6 +1886,7 @@ def find_g_vector(self, g_vector, depth=infinity): EXAMPLES:: sage: A = ClusterAlgebra(['G', 2], principal_coefficients=True) + sage: A.clear_computed_data() sage: A.find_g_vector((-2, 3), depth=2) sage: A.find_g_vector((-2, 3), depth=3) [0, 1, 0] @@ -1966,8 +2011,8 @@ def coefficients(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.coefficients() (y0, y1) - sage: B = ClusterAlgebra(['B', 2]) - sage: B.coefficients() + sage: A1 = ClusterAlgebra(['B', 2]) + sage: A1.coefficients() () """ if isinstance(self.base(), LaurentPolynomialRing_generic): @@ -1984,11 +2029,11 @@ def coefficient_names(self): sage: A = ClusterAlgebra(['A', 3]) sage: A.coefficient_names() () - sage: B = ClusterAlgebra(['B', 2], principal_coefficients=True) - sage: B.coefficient_names() + sage: A1 = ClusterAlgebra(['B', 2], principal_coefficients=True) + sage: A1.coefficient_names() ('y0', 'y1') - sage: C = ClusterAlgebra(['C', 3], principal_coefficients=True, coefficient_prefix='x') - sage: C.coefficient_names() + sage: A2 = ClusterAlgebra(['C', 3], principal_coefficients=True, coefficient_prefix='x') + sage: A2.coefficient_names() ('x3', 'x4', 'x5') """ return self.variable_names()[self.rank():] @@ -2032,8 +2077,8 @@ def initial_cluster_variable_names(self): sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True) sage: A.initial_cluster_variable_names() ('x0', 'x1') - sage: B = ClusterAlgebra(['B', 2], cluster_variable_prefix='a') - sage: B.initial_cluster_variable_names() + sage: A1 = ClusterAlgebra(['B', 2], cluster_variable_prefix='a') + sage: A1.initial_cluster_variable_names() ('a0', 'a1') """ return self.variable_names()[:self.rank()] @@ -2068,6 +2113,7 @@ def seeds(self, **kwargs): EXAMPLES:: sage: A = ClusterAlgebra(['A', 4]) + sage: A.clear_computed_data() sage: seeds = A.seeds(allowed_directions=[3, 0, 1]) sage: _ = list(seeds) sage: A.g_vectors_so_far() @@ -2154,11 +2200,12 @@ def reset_exploring_iterator(self, mutating_F=True): EXAMPLES:: - sage: A = ClusterAlgebra(['C', 4]) + sage: A = ClusterAlgebra(['A', 4]) + sage: A.clear_computed_data() sage: A.reset_exploring_iterator(mutating_F=False) sage: A.explore_to_depth(infinity) sage: len(A.g_vectors_so_far()) - 20 + 14 sage: len(A.F_polynomials_so_far()) 4 """ From 66e75cf645c427f954eae9cccb124ec671a245e1 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Nov 2016 10:45:49 +0100 Subject: [PATCH 183/191] Removed useless methods and got the code to fail with UniqeRepresentation --- src/sage/algebras/cluster_algebra.py | 78 ++++------------------------ 1 file changed, 9 insertions(+), 69 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 4f48efa791e..83489fa717d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -324,12 +324,12 @@ but there is currently no coercion in between algebras obtained by mutating at the initial seed:: - sage: B = A.mutate_initial(0); B + sage: A1 = A.mutate_initial(0); A1 A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring - sage: A.b_matrix() == B.b_matrix() + sage: A.b_matrix() == A1.b_matrix() False - sage: map(lambda (X, Y): X.has_coerce_map_from(Y), [(A, B), (B, A)]) + sage: map(lambda (X, Y): X.has_coerce_map_from(Y), [(A, A1), (A1, A)]) [False, False] """ @@ -1361,72 +1361,6 @@ def __init__(self, Q, **kwargs): embedding = SetMorphism(Hom(self, self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) - def __copy__(self): - r""" - Return a copy of ``self``. - - EXAMPLES:: - - sage: A1 = ClusterAlgebra(['A', 3]) - sage: A2 = copy(A1) - sage: A2 == A1 - True - sage: A2 is not A1 - True - sage: S1 = A1.current_seed() - sage: S2 = A2.current_seed() - sage: S1 == S2 - True - sage: S1.mutate(0) - sage: S1 == S2 - False - """ - cv_names = self.initial_cluster_variable_names() - coeff_names = self.coefficient_names() - other = ClusterAlgebra(self._B0, cluster_variable_names=cv_names, - coefficient_names=coeff_names, scalars=self.scalars()) - other._F_poly_dict = copy(self._F_poly_dict) - other._path_dict = copy(self._path_dict) - S = copy(self.current_seed()) - S._parent = other - other.set_current_seed(S) - return other - - def __eq__(self, other): - r""" - Test equality of two Cluster Algebras. - - INPUT: - - - ``other`` -- a :class:`ClusterAlgebra` - - ALGORITHM: - - ``self`` and ``other`` are deemed to be equal if they have the same - initial exchange matrix and their ambients coincide. In particular we - do not keep track of how much each Cluster Algebra has been explored. - - EXAMPLES:: - - sage: A1 = ClusterAlgebra(['A', 3]) - sage: A2 = copy(A1) - sage: A1 is not A2 - True - sage: A1 == A2 - True - sage: A1.current_seed().mutate([0, 1, 2]) - sage: A1 == A2 - True - sage: A3 = ClusterAlgebra(['A', 3], principal_coefficients=True) - sage: A1 == A3 - False - sage: B = ClusterAlgebra(['B', 3]) - sage: A1 == B - False - """ - return (isinstance(other, ClusterAlgebra) and self._B0 == other._B0 - and self.ambient() == other.ambient()) - def _repr_(self): r""" Return the string representation of ``self``. @@ -2286,6 +2220,12 @@ def mutate_initial(self, k): sage: A2 = A1.mutate_initial(0) sage: A2._F_poly_dict == A._F_poly_dict True + + Fail with :class:`UniqueRepresentation`:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.mutate_initial(0) is A + False """ n = self.rank() if k not in range(n): From bcac0e9d3e49d5d200a39e6d351dc4d22daae2e2 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Nov 2016 12:12:03 +0100 Subject: [PATCH 184/191] Implemented new mutate_initial; this is now consistent with the use of UniqueRepresentation --- src/sage/algebras/cluster_algebra.py | 128 ++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 83489fa717d..bd862ee1a4c 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -435,7 +435,7 @@ def _mutation_parse(mutate): @wraps(mutate) def mutate_wrapper(self, direction, **kwargs): - inplace = kwargs.pop('inplace', True) and mutate.__name__ != "mutate_initial" + inplace = kwargs.pop('inplace', True) and mutate.__name__ != "mutate_initial_1" if inplace: to_mutate = self else: @@ -2193,14 +2193,19 @@ def cluster_fan(self, depth=infinity): cones = [Cone(S.g_vectors()) for S in seeds] return Fan(cones) - @_mutation_parse - def mutate_initial(self, k): + def mutate_initial(self, direction): r""" Return the cluster algebra obtained by mutating ``self`` at the initial seed. INPUT: + - ``direction`` -- in which direction(s) to mutate, it can be: + + * an integer in ``range(self.rank())`` to mutate in one direction only + * an iterable of such integers to mutate along a sequence + * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously + ALGORITHM: This function computes data for the new algebra from known data for @@ -2221,13 +2226,128 @@ def mutate_initial(self, k): sage: A2._F_poly_dict == A._F_poly_dict True - Fail with :class:`UniqueRepresentation`:: + Check that we did not mess up the original algebra because of :class:`UniqueRepresentation`:: sage: A = ClusterAlgebra(['A',2]) sage: A.mutate_initial(0) is A False """ n = self.rank() + + # construct mutation sequence + if direction == "sinks": + B = self.b_matrix() + seq = [i for i in range(n) if all(x <= 0 for x in B.column(i))] + elif direction == "sources": + B = self.b_matrix() + seq = [i for i in range(n) if all(x >= 0 for x in B.column(i))] + else: + try: + seq = iter(direction) + except TypeError: + seq = iter((direction, )) + + # setup + Ugen = self._U.gens() + F_poly_dict = copy(self._F_poly_dict) + path_dict = copy(self._path_dict) + path_to_current = copy(self.current_seed().path_from_initial_seed()) + B0 = copy(self._B0) + + # go + for k in seq: + if k not in range(n): + raise ValueError('cannot mutate in direction ' + str(k)) + + # clear storage + tmp_path_dict = {} + tmp_F_poly_dict = {} + + # mutate B-matrix + B0.mutate(k) + + # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 + F_subs = [Ugen[k] ** (-1) if j == k else Ugen[j] * Ugen[k] ** max(B0[k, j], 0) * (1 + Ugen[k]) ** (-B0[k, j]) for j in range(n)] + + for old_g_vect in path_dict: + # compute new g-vector + J = identity_matrix(n) + eps = sign(old_g_vect[k]) + for j in range(n): + # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 + J[j, k] += max(0, -eps * B0[j, k]) + J[k, k] = -1 + new_g_vect = tuple(J * vector(old_g_vect)) + + # compute new path + new_path = path_dict[old_g_vect] + new_path = ([k] + new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] + tmp_path_dict[new_g_vect] = new_path + + # compute new F-polynomial + if old_g_vect in F_poly_dict: + h = -min(0, old_g_vect[k]) + new_F_poly = F_poly_dict[old_g_vect](F_subs) * Ugen[k] ** h * (Ugen[k] + 1) ** old_g_vect[k] + tmp_F_poly_dict[new_g_vect] = new_F_poly + + # update storage + path_dict = tmp_path_dict + F_poly_dict = tmp_F_poly_dict + path_to_current = ([k] + path_to_current[:1] if path_to_current[:1] != [k] else []) + path_to_current[1:] + + # create new algebra + cv_names = self.initial_cluster_variable_names() + coeff_names = self.coefficient_names() + scalars = self.scalars() + A = ClusterAlgebra(B0, cluster_variable_names=cv_names, + coefficient_names=coeff_names, scalars=scalars) + + # store computed data + A._F_poly_dict.update(F_poly_dict) + A._path_dict.update(path_dict) + + # reset self.current_seed() to the previous location + S = A.initial_seed() + S.mutate(path_to_current, mutating_F=False) + A.set_current_seed(S) + + return A + + @_mutation_parse + def mutate_initial_1(self, k): + r""" + Return the cluster algebra obtained by mutating ``self`` at + the initial seed. + + INPUT: + + ALGORITHM: + + This function computes data for the new algebra from known data for + the old algebra using Equation (4.2) from [NZ2012]_ for g-vectors, and + Equation (6.21) from [FZ2007]_ for F-polynomials. The exponent `h` + in the formula for F-polynomials is ``-min(0, old_g_vect[k])`` + due to [NZ2012]_ Proposition 4.2. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.explore_to_depth(infinity) + sage: B = A.b_matrix() + sage: B.mutate(0) + sage: A1 = ClusterAlgebra(B) + sage: A1.explore_to_depth(infinity) + sage: A2 = A1.mutate_initial_1(0) + sage: A2._F_poly_dict == A._F_poly_dict + True + + Fail with :class:`UniqueRepresentation`:: + + sage: A = ClusterAlgebra(['A',2]) + sage: A.mutate_initial_1(0) is A + False + """ + n = self.rank() if k not in range(n): raise ValueError('cannot mutate in direction ' + str(k)) From fdc90e801d61d2482885be54cf1927df6d57edaa Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Nov 2016 12:38:41 +0100 Subject: [PATCH 185/191] Removed old mutate_initial; removed mutate_wrapper; refactored mutate --- src/sage/algebras/cluster_algebra.py | 285 +++++++-------------------- 1 file changed, 73 insertions(+), 212 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index bd862ee1a4c..e8a63b54076 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -376,91 +376,6 @@ from six.moves import range as range from sage.structure.unique_representation import UniqueRepresentation -############################################################################## -# Helper functions -############################################################################## - - -def _mutation_parse(mutate): - r""" - Preparse input for mutation functions. - - This wrapper provides: - - - inplace (only for seeds) - - mutate along sequence - - mutate at all sinks/sources - - Possible things to implement later include: - - - mutate at a cluster variable - - mutate at a g-vector (it is hard to distinguish this case from - a generic sequence) - - urban renewals - - other? - - EXAMPLES:: - - sage: A = ClusterAlgebra(['E', 6]) - sage: S = A.current_seed() - sage: S.mutate(1, inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 - and no coefficients over Integer Ring obtained from the initial - by mutating in direction 1 - sage: S.mutate([1, 2, 3], inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 - and no coefficients over Integer Ring obtained from the initial - by mutating along the sequence [1, 2, 3] - sage: S.mutate('sinks', inplace=False) # indirect doctest - The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3, x4, x5 - and no coefficients over Integer Ring obtained from the initial - by mutating along the sequence [0, 2, 4] - """ - doc = mutate.__doc__.split("INPUT:") - doc[0] += "INPUT:" - if mutate.__name__ == "mutate": - doc[0] += r""" - - - ``inplace`` -- bool (default ``True``); whether to mutate in place or to return a new object - """ - doc[0] += r""" - - - ``direction`` -- in which direction(s) to mutate, it can be: - - * an integer in ``range(self.rank())`` to mutate in one direction only - * an iterable of such integers to mutate along a sequence - * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously - """ - mutate.__doc__ = doc[0] + doc[1] - - @wraps(mutate) - def mutate_wrapper(self, direction, **kwargs): - inplace = kwargs.pop('inplace', True) and mutate.__name__ != "mutate_initial_1" - if inplace: - to_mutate = self - else: - to_mutate = copy(self) - - if direction == "sinks": - B = self.b_matrix() - seq = [i for i in range(B.ncols()) if all(x <= 0 for x in B.column(i))] - elif direction == "sources": - B = self.b_matrix() - seq = [i for i in range(B.ncols()) if all(x >= 0 for x in B.column(i))] - else: - try: - seq = iter(direction) - except TypeError: - seq = iter((direction, )) - - for k in seq: - mutate(to_mutate, k, **kwargs) - - if not inplace: - return to_mutate - - return mutate_wrapper - ############################################################################## # Elements of a cluster algebra ############################################################################## @@ -1073,13 +988,20 @@ def cluster_variables(self): """ return [self.parent().cluster_variable(g) for g in self.g_vectors()] - @_mutation_parse - def mutate(self, k, mutating_F=True): + def mutate(self, direction, **kwargs): r""" Mutate ``self``. INPUT: + - ``direction`` -- in which direction(s) to mutate, it can be: + + * an integer in ``range(self.rank())`` to mutate in one direction only + * an iterable of such integers to mutate along a sequence + * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously + + - ``inplace`` -- bool (default ``True``); whether to mutate in place or to return a new object + - ``mutating_F`` -- bool (default ``True``); whether to compute F-polynomials while mutating @@ -1106,49 +1028,78 @@ def mutate(self, k, mutating_F=True): """ n = self.parent().rank() - if k < 0 or k >= n: - raise ValueError('cannot mutate in direction ' + str(k)) - - # store new mutation path - if self._path != [] and self._path[-1] == k: - self._path.pop() + # do we want to change self? + inplace = kwargs.pop('inplace', True) + if inplace: + to_mutate = self else: - self._path.append(k) + to_mutate = copy(self) - # find sign of k-th c-vector - if any(x > 0 for x in self._C.column(k)): - eps = +1 + # construct mutation sequence + # if you change this be considerate and change also :class:`ClusterAlgebra`.mutate_initial + if direction == "sinks": + B = self.b_matrix() + seq = [i for i in range(n) if all(x <= 0 for x in B.column(i))] + elif direction == "sources": + B = self.b_matrix() + seq = [i for i in range(n) if all(x >= 0 for x in B.column(i))] else: - eps = -1 + try: + seq = iter(direction) + except TypeError: + seq = iter((direction, )) - # store the g-vector to be mutated in case we are mutating F-polynomials also - old_g_vector = self.g_vector(k) + # are we mutating F-polynomials? + mutating_F = kwargs.pop('mutating_F', True) - # compute new G-matrix - J = identity_matrix(n) - for j in range(n): - J[j, k] += max(0, -eps * self._B[j, k]) - J[k, k] = -1 - self._G = self._G * J + for k in seq: + if k not in range(n): + raise ValueError('cannot mutate in direction ' + str(k)) - # path to new g-vector (we store the shortest encountered so far) - g_vector = self.g_vector(k) - if g_vector not in self.parent()._path_dict or len(self.parent()._path_dict[g_vector]) > len(self._path): - self.parent()._path_dict[g_vector] = copy(self._path) + # store new mutation path + if to_mutate._path != [] and to_mutate._path[-1] == k: + to_mutate._path.pop() + else: + to_mutate._path.append(k) - # compute F-polynomials - if mutating_F and g_vector not in self.parent()._F_poly_dict: - self.parent()._F_poly_dict[g_vector] = self._mutated_F(k, old_g_vector) + # find sign of k-th c-vector + if any(x > 0 for x in to_mutate._C.column(k)): + eps = +1 + else: + eps = -1 - # compute new C-matrix - J = identity_matrix(n) - for j in range(n): - J[k, j] += max(0, eps * self._B[k, j]) - J[k, k] = -1 - self._C = self._C * J + # store the g-vector to be mutated in case we are mutating F-polynomials also + old_g_vector = to_mutate.g_vector(k) + + # compute new G-matrix + J = identity_matrix(n) + for j in range(n): + J[j, k] += max(0, -eps * to_mutate._B[j, k]) + J[k, k] = -1 + to_mutate._G = to_mutate._G * J - # compute new B-matrix - self._B.mutate(k) + # path to new g-vector (we store the shortest encountered so far) + g_vector = to_mutate.g_vector(k) + if g_vector not in to_mutate.parent()._path_dict or len(to_mutate.parent()._path_dict[g_vector]) > len(to_mutate._path): + to_mutate.parent()._path_dict[g_vector] = copy(to_mutate._path) + + # compute F-polynomials + if mutating_F and g_vector not in to_mutate.parent()._F_poly_dict: + to_mutate.parent()._F_poly_dict[g_vector] = to_mutate._mutated_F(k, old_g_vector) + + # compute new C-matrix + J = identity_matrix(n) + for j in range(n): + J[k, j] += max(0, eps * to_mutate._B[k, j]) + J[k, k] = -1 + to_mutate._C = to_mutate._C * J + + # compute new B-matrix + to_mutate._B.mutate(k) + + # return if we need to + if not inplace: + return to_mutate def _mutated_F(self, k, old_g_vector): r""" @@ -2235,6 +2186,7 @@ def mutate_initial(self, direction): n = self.rank() # construct mutation sequence + # if you change this be considerate and change also :class:`ClusterAlgebraSeed`.mutate if direction == "sinks": B = self.b_matrix() seq = [i for i in range(n) if all(x <= 0 for x in B.column(i))] @@ -2313,97 +2265,6 @@ def mutate_initial(self, direction): return A - @_mutation_parse - def mutate_initial_1(self, k): - r""" - Return the cluster algebra obtained by mutating ``self`` at - the initial seed. - - INPUT: - - ALGORITHM: - - This function computes data for the new algebra from known data for - the old algebra using Equation (4.2) from [NZ2012]_ for g-vectors, and - Equation (6.21) from [FZ2007]_ for F-polynomials. The exponent `h` - in the formula for F-polynomials is ``-min(0, old_g_vect[k])`` - due to [NZ2012]_ Proposition 4.2. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['F', 4]) - sage: A.explore_to_depth(infinity) - sage: B = A.b_matrix() - sage: B.mutate(0) - sage: A1 = ClusterAlgebra(B) - sage: A1.explore_to_depth(infinity) - sage: A2 = A1.mutate_initial_1(0) - sage: A2._F_poly_dict == A._F_poly_dict - True - - Fail with :class:`UniqueRepresentation`:: - - sage: A = ClusterAlgebra(['A',2]) - sage: A.mutate_initial_1(0) is A - False - """ - n = self.rank() - if k not in range(n): - raise ValueError('cannot mutate in direction ' + str(k)) - - # save computed data - old_F_poly_dict = copy(self._F_poly_dict) - old_path_dict = copy(self._path_dict) - old_path_to_current = copy(self.current_seed().path_from_initial_seed()) - - # mutate initial exchange matrix - B0 = copy(self._B0) - B0.mutate(k) - - # HACK: pretend there is no embedding of self into self.parent(): we - # will recreate this in a moment. The problem is that there can be - # only one embedding per object and it is set up in __init__. - self._unset_embedding() - - # update algebra - cv_names = self.initial_cluster_variable_names() - coeff_names = self.coefficient_names() - scalars = self.scalars() - self.__init__(ClusterQuiver(B0), cluster_variable_names=cv_names, - coefficient_names=coeff_names, scalars=scalars) - - # substitution data to compute new F-polynomials - Ugen = self._U.gens() - # here we have \mp B0 rather then \pm B0 because we want the k-th row of the old B0 - F_subs = [Ugen[k] ** (-1) if j == k else Ugen[j] * Ugen[k] ** max(B0[k, j], 0) * (1 + Ugen[k]) ** (-B0[k, j]) for j in range(n)] - - # restore computed data - for old_g_vect in old_path_dict: - # compute new g-vector - J = identity_matrix(n) - eps = sign(old_g_vect[k]) - for j in range(n): - # here we have -eps*B0 rather than eps*B0 because we want the k-th column of the old B0 - J[j, k] += max(0, -eps * B0[j, k]) - J[k, k] = -1 - new_g_vect = tuple(J * vector(old_g_vect)) - - # compute new path - new_path = old_path_dict[old_g_vect] - new_path = ([k] + new_path[:1] if new_path[:1] != [k] else []) + new_path[1:] - self._path_dict[new_g_vect] = new_path - - # compute new F-polynomial - if old_g_vect in old_F_poly_dict: - h = -min(0, old_g_vect[k]) - new_F_poly = old_F_poly_dict[old_g_vect](F_subs) * Ugen[k] ** h * (Ugen[k] + 1) ** old_g_vect[k] - self._F_poly_dict[new_g_vect] = new_F_poly - - # reset self.current_seed() to the previous location - S = self.initial_seed() - S.mutate([k] + old_path_to_current, mutating_F=False) - self.set_current_seed(S) - # DESIDERATA # Some of these are probably unrealistic def upper_cluster_algebra(self): From 41560eebf578786d7a6cf2f04651c417a5825f25 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Thu, 24 Nov 2016 18:46:05 +0100 Subject: [PATCH 186/191] Polish + removed MethodType --- src/sage/algebras/cluster_algebra.py | 183 +++++++++++++-------------- 1 file changed, 86 insertions(+), 97 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e8a63b54076..257bd0e0cbd 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -52,6 +52,8 @@ :class:`ClusterAlgebraElement` is a thin wrapper around :class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_generic` providing all the functions specific to cluster variables. +Elemets of a cluster algebra with principal coefficients have special methods +and these are grouped in the subclass :class:`PrincipalClusterAlgebraElement`. One more remark about this implementation. Instances of :class:`ClusterAlgebra` are built by identifying the initial cluster variables @@ -307,10 +309,10 @@ sage: A = ClusterAlgebra(['F', 4]); A A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients over Integer Ring - sage: B = ClusterAlgebra(A.b_matrix().matrix_from_columns([0, 1, 2]), coefficient_prefix='x'); B + sage: A1 = ClusterAlgebra(A.b_matrix().matrix_from_columns([0, 1, 2]), coefficient_prefix='x'); A1 A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3 over Integer Ring - sage: A.has_coerce_map_from(B) + sage: A.has_coerce_map_from(A1) True and we also have an immersion of ``A.base()`` into ``A`` and of ``A`` @@ -344,11 +346,9 @@ #***************************************************************************** from __future__ import absolute_import - from copy import copy from functools import wraps from future_builtins import map -from types import MethodType from sage.categories.homset import Hom from sage.categories.morphism import SetMorphism from sage.categories.rings import Rings @@ -373,14 +373,13 @@ from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent from sage.structure.sage_object import SageObject -from six.moves import range as range from sage.structure.unique_representation import UniqueRepresentation +from six.moves import range as range ############################################################################## # Elements of a cluster algebra ############################################################################## - class ClusterAlgebraElement(ElementWrapper): """ An element of a cluster algebra. @@ -471,10 +470,6 @@ def _repr_(self): numer, denom = self.lift()._fraction_pair() return repr(numer / denom) -#### -# Methods not always defined -#### - class PrincipalClusterAlgebraElement(ClusterAlgebraElement): """ An element in a cluster algebra with principle coefficients. @@ -1303,11 +1298,6 @@ def __init__(self, Q, **kwargs): for i in range(self._n + m)) for j in range(self._n)} - # Add methods that are defined only for special cases - if self._n == 2 and m == 0: - self.greedy_element = MethodType(greedy_element, self, self.__class__) - self._greedy_coefficient = MethodType(_greedy_coefficient, self, self.__class__) - # Register embedding into self.ambient() embedding = SetMorphism(Hom(self, self.ambient()), lambda x: x.lift()) self._populate_coercion_lists_(embedding=embedding) @@ -2265,6 +2255,87 @@ def mutate_initial(self, direction): return A + def greedy_element(self, d_vector): + r""" + Return the greedy element with denominator vector ``d_vector``. + + INPUT: + + - ``d_vector`` -- tuple of 2 integers; the denominator vector of + the element to compute + + ALGORITHM: + + This implements greedy elements of a rank 2 cluster algebra using + Equation (1.5) from [LLZ2014]_. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', [1, 1], 1]) + sage: A.greedy_element((1, 1)) + (x0^2 + x1^2 + 1)/(x0*x1) + """ + if self.rank() != 2: + raise ValueError('greedy elements are only defined in rank 2') + + if len(self.coefficients()) != 0: + raise NotImplementedError('can only compute greedy elements in the coefficient-free case') + + b = abs(self.b_matrix()[0, 1]) + c = abs(self.b_matrix()[1, 0]) + a1, a2 = d_vector + # Here we use the generators of self.ambient() because cluster variables + # do not have an inverse. + x1, x2 = self.ambient().gens() + if a1 < 0: + if a2 < 0: + return self.retract(x1 ** (-a1) * x2 ** (-a2)) + else: + return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) + elif a2 < 0: + return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) + output = 0 + for p in range(0, a2 + 1): + for q in range(0, a1 + 1): + output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) + return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) + + def _greedy_coefficient(self, d_vector, p, q): + r""" + Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` + in the numerator of the greedy element with denominator vector ``d_vector``. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', [1, 1], 1]) + sage: A.greedy_element((1, 1)) + (x0^2 + x1^2 + 1)/(x0*x1) + sage: A._greedy_coefficient((1, 1), 0, 0) + 1 + sage: A._greedy_coefficient((1, 1), 1, 0) + 1 + """ + b = abs(self.b_matrix()[0, 1]) + c = abs(self.b_matrix()[1, 0]) + a1, a2 = d_vector + p = Integer(p) + q = Integer(q) + if p == 0 and q == 0: + return Integer(1) + sum1 = 0 + for k in range(1, p + 1): + bino = 0 + if a2 - c * q + k - 1 >= k: + bino = binomial(a2 - c * q + k - 1, k) + sum1 += (-1) ** (k - 1) * self._greedy_coefficient(d_vector, p - k, q) * bino + sum2 = 0 + for l in range(1, q + 1): + bino = 0 + if a1 - b * p + l - 1 >= l: + bino = binomial(a1 - b * p + l - 1, l) + sum2 += (-1) ** (l - 1) * self._greedy_coefficient(d_vector, p, q - l) * bino + return Integer(max(sum1, sum2)) + # DESIDERATA # Some of these are probably unrealistic def upper_cluster_algebra(self): @@ -2322,85 +2393,3 @@ def theta_basis_element(self, g_vector): NotImplementedError: not implemented yet """ raise NotImplementedError("not implemented yet") - -#### -# Methods only defined for special cases -#### - - -def greedy_element(self, d_vector): - r""" - Return the greedy element with denominator vector ``d_vector``. - - INPUT: - - - ``d_vector`` -- tuple of 2 integers; the denominator vector of - the element to compute - - ALGORITHM: - - This implements greedy elements of a rank 2 cluster algebra using - Equation (1.5) from [LLZ2014]_. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['A', [1, 1], 1]) - sage: A.greedy_element((1, 1)) - (x0^2 + x1^2 + 1)/(x0*x1) - """ - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) - a1, a2 = d_vector - # Here we use the generators of self.ambient() because cluster variables - # do not have an inverse. - x1, x2 = self.ambient().gens() - if a1 < 0: - if a2 < 0: - return self.retract(x1 ** (-a1) * x2 ** (-a2)) - else: - return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) - elif a2 < 0: - return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) - output = 0 - for p in range(0, a2 + 1): - for q in range(0, a1 + 1): - output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) - return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) - - -def _greedy_coefficient(self, d_vector, p, q): - r""" - Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` - in the numerator of the greedy element with denominator vector ``d_vector``. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['A', [1, 1], 1]) - sage: A.greedy_element((1, 1)) - (x0^2 + x1^2 + 1)/(x0*x1) - sage: A._greedy_coefficient((1, 1), 0, 0) - 1 - sage: A._greedy_coefficient((1, 1), 1, 0) - 1 - """ - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) - a1, a2 = d_vector - p = Integer(p) - q = Integer(q) - if p == 0 and q == 0: - return Integer(1) - sum1 = 0 - for k in range(1, p + 1): - bino = 0 - if a2 - c * q + k - 1 >= k: - bino = binomial(a2 - c * q + k - 1, k) - sum1 += (-1) ** (k - 1) * self._greedy_coefficient(d_vector, p - k, q) * bino - sum2 = 0 - for l in range(1, q + 1): - bino = 0 - if a1 - b * p + l - 1 >= l: - bino = binomial(a1 - b * p + l - 1, l) - sum2 += (-1) ** (l - 1) * self._greedy_coefficient(d_vector, p, q - l) * bino - return Integer(max(sum1, sum2)) - From 5a827a10349566846a21e184fc7a751299ab4883 Mon Sep 17 00:00:00 2001 From: Emily Gunawan Date: Sun, 8 Jan 2017 23:47:25 +0100 Subject: [PATCH 187/191] 21254: add titles and authors to references LLZ2014 and NZ2012, remove duplicate FZ2007 --- src/sage/algebras/cluster_algebra.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 257bd0e0cbd..127f847f843 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -67,9 +67,12 @@ REFERENCES: -- [FZ2007]_ -- [LLZ2014]_ -- [NZ2012]_ +.. [LLZ2014] Kyungyong Lee, Li Li, Andrei Zelevinsky + *Greedy elements in rank 2 cluster algebras* + :arxiv:`1208.2391` +.. [NZ2012] Tomoki Nakanishi and Andrei Zelevinsky + *On tropical dualities in cluster algebras* + :arXiv:`1101.3736` AUTHORS: From 39812990e7a2fde173b7413b97f9902802f93cbb Mon Sep 17 00:00:00 2001 From: Emily Gunawan Date: Sun, 8 Jan 2017 23:53:35 +0100 Subject: [PATCH 188/191] 21254: add cluster algebras to /algebras/catalog.py --- src/sage/algebras/catalog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 1704af7c5f5..304293e4f51 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -11,6 +11,7 @@ - :class:`algebras.Brauer ` - :class:`algebras.Clifford ` +- :class:`algebras.ClusterAlgebra ` - :class:`algebras.Descent ` - :class:`algebras.DifferentialWeyl ` From 94a5d1ac0518ab2033bfa6306695d6889e706855 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Mon, 9 Jan 2017 18:46:58 +0100 Subject: [PATCH 189/191] Revert 5341cbd219c650801030dd9df3dd49b34e3c6952 --- src/sage/algebras/cluster_algebra.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 127f847f843..257bd0e0cbd 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -67,12 +67,9 @@ REFERENCES: -.. [LLZ2014] Kyungyong Lee, Li Li, Andrei Zelevinsky - *Greedy elements in rank 2 cluster algebras* - :arxiv:`1208.2391` -.. [NZ2012] Tomoki Nakanishi and Andrei Zelevinsky - *On tropical dualities in cluster algebras* - :arXiv:`1101.3736` +- [FZ2007]_ +- [LLZ2014]_ +- [NZ2012]_ AUTHORS: From 06000a4f2e9a2a2b2bf6455add97c25dd486a798 Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 14 Jan 2017 01:47:53 +0100 Subject: [PATCH 190/191] Added missing doctest --- src/sage/algebras/cluster_algebra.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index 257bd0e0cbd..e8db19c1c60 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1203,6 +1203,15 @@ class ClusterAlgebra(Parent, UniqueRepresentation): @staticmethod def __classcall__(self, data, **kwargs): + r""" + Preparse input to make it hashable. + + EXAMPLES:: + + sage: A = ClusterAlgebra(['A', 2]); A # indirect doctest + A Cluster Algebra with cluster variables x0, x1 and no coefficients + over Integer Ring + """ Q = ClusterQuiver(data) for key in kwargs: if isinstance(kwargs[key],list): From 389cdc925fb4ed8b438c2bece4f078cefe248d4a Mon Sep 17 00:00:00 2001 From: Salvatore Stella Date: Sat, 18 Feb 2017 00:05:39 +0100 Subject: [PATCH 191/191] Fixed hash doctest --- src/sage/algebras/cluster_algebra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e8db19c1c60..8648818a500 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -710,7 +710,8 @@ def __hash__(self): sage: A = ClusterAlgebra(['A', 3]) sage: S = A.initial_seed() sage: hash(S) - 6108559638409052534 + 6108559638409052534 # 64-bit + 1755906422 # 32-bit """ return hash(frozenset(self.g_vectors()))