def __classcall_private__(cls, R, n=None, M=None, ambient=None): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: from sage.categories.examples.finite_dimensional_lie_algebras_with_basis import AbelianLieAlgebra sage: A1 = AbelianLieAlgebra(QQ, n=3) sage: A2 = AbelianLieAlgebra(QQ, M=FreeModule(QQ, 3)) sage: A3 = AbelianLieAlgebra(QQ, 3, FreeModule(QQ, 3)) sage: A1 is A2 and A2 is A3 True sage: A1 = AbelianLieAlgebra(QQ, 2) sage: A2 = AbelianLieAlgebra(ZZ, 2) sage: A1 is A2 False sage: A1 = AbelianLieAlgebra(QQ, 0) sage: A2 = AbelianLieAlgebra(QQ, 1) sage: A1 is A2 False """ if M is None: M = FreeModule(R, n) else: M = M.change_ring(R) n = M.dimension() return super(AbelianLieAlgebra, cls).__classcall__(cls, R, n=n, M=M, ambient=ambient)
def reduce_basis(self, long_etas): r""" Produce a more manageable basis via LLL-reduction. INPUT: - ``long_etas`` - a list of EtaGroupElement objects (which should all be of the same level) OUTPUT: - a new list of EtaGroupElement objects having hopefully smaller norm ALGORITHM: We define the norm of an eta-product to be the `L^2` norm of its divisor (as an element of the free `\ZZ`-module with the cusps as basis and the standard inner product). Applying LLL-reduction to this gives a basis of hopefully more tractable elements. Of course we'd like to use the `L^1` norm as this is just twice the degree, which is a much more natural invariant, but `L^2` norm is easier to work with! EXAMPLES:: sage: EtaGroup(4).reduce_basis([ EtaProduct(4, {1:8,2:24,4:-32}), EtaProduct(4, {1:8, 4:-8})]) [Eta product of level 4 : (eta_1)^8 (eta_4)^-8, Eta product of level 4 : (eta_1)^-8 (eta_2)^24 (eta_4)^-16] """ from six.moves import range N = self.level() cusps = AllCusps(N) r = matrix(ZZ, [[et.order_at_cusp(c) for c in cusps] for et in long_etas]) V = FreeModule(ZZ, r.ncols()) A = V.submodule_with_basis([V(rw) for rw in r.rows()]) rred = r.LLL() short_etas = [] for shortvect in rred.rows(): bv = A.coordinates(shortvect) dict = {d: sum(bv[i] * long_etas[i].r(d) for i in range(r.nrows())) for d in divisors(N)} short_etas.append(self(dict)) return short_etas
def basiclemmavec(self, M): """ Finds a vector where the value of the quadratic form is coprime to M. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [2, 1, 5]) sage: Q.basiclemmavec(10) (6, 5) sage: Q(_) 227 """ V = FreeModule(self.base_ring(), self.dim()) mat = self.matrix() vec = [] mod = [] M0 = abs(M) if M0 == 1: return V(0) for i in range(self.dim()): M1 = prime_to_m_part(M0, self[i, i]) if M1 != 1: vec.append(V.gen(i)) mod.append(M1) M0 = M0 / M1 if M0 == 1: return tuple(CRT_vectors(vec, mod)) for i in range(self.dim()): for j in range(i): M1 = prime_to_m_part(M0, self[i, j]) if M1 != 1: vec.append(V.i + V.j) mod.append(M1) M0 = M0 / M1 if M0 == 1: return __crt_list(vec, mod) raise ValueError("not primitive form")
def __init__( self, degrees ) : r""" INPUT: - ``degrees`` -- A list or tuple of `n` positive integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading((1,2)) sage: g = DegreeGrading([5,2,3]) """ self.__degrees = tuple(degrees) self.__module = FreeModule(ZZ, len(degrees)) self.__module_basis = self.__module.basis()
def __init__(self, R, form, names=None): """ Initialize ``self``. TESTS:: sage: m = matrix([[-2,3],[3,4]]) sage: J = JordanAlgebra(m) sage: TestSuite(J).run() """ self._form = form self._M = FreeModule(R, form.ncols()) cat = MagmaticAlgebras(R).Commutative().Unital().FiniteDimensional().WithBasis() self._no_generic_basering_coercion = True # Remove once 16492 is fixed Parent.__init__(self, base=R, names=names, category=cat)
def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i,k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory(category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()]*len(index_set) for k,c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()}
def __init__( self, q): """ We initialize by a list of integers $[a_1,...,a_N]$. The lattice self is then $L = (L,\beta) = (G^{-1}\ZZ^n/\ZZ^n, G[x])$, where $G$ is the symmetric matrix $G=[a_1,...,a_n;*,a_{n+1},....;..;* ... * a_N]$, i.e.~the $a_j$ denote the elements above the diagonal of $G$. """ self.__form = q # We compute the rank N = len(q) assert is_square( 1+8*N) n = Integer( (-1+isqrt(1+8*N))/2) self.__rank = n # We set up the Gram matrix self.__G = matrix( IntegerRing(), n, n) i = j = 0 for a in q: self.__G[i,j] = self.__G[j,i] = Integer(a) if j < n-1: j += 1 else: i += 1 j = i # We compute the level Gi = self.__G**-1 self.__Gi = Gi a = lcm( map( lambda x: x.denominator(), Gi.list())) I = Gi.diagonal() b = lcm( map( lambda x: (x/2).denominator(), I)) self.__level = lcm( a, b) # We define the undelying module and the ambient space self.__module = FreeModule( IntegerRing(), n) self.__space = self.__module.ambient_vector_space() # We compute a shadow vector self.__shadow_vector = self.__space([(a%2)/2 for a in self.__G.diagonal()])*Gi # We define a basis M = Matrix( IntegerRing(), n, n, 1) self.__basis = self.__module.basis() # We prepare a cache self.__dual_vectors = None self.__values = None self.__chi = {}
def __init__(self, n, q, D, secret_dist='uniform', m=None): r""" Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianDistributionIntegerSampler` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLES: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb,ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist))
def clean(pts, k): r""" Return a list of points with the same convex hull as in ``pts`` but with potentially less points. INPUT: - ``pts`` -- list or tuple of points - ``k`` -- number of nonredundant point set in the cleaning (in the case of matrices, a wise choice is nvars - dim + 1) """ dim = len(pts[0]) if len(pts) < 2*k: return pts F = FreeModule(RDF, dim) pts = [(p,F(p)) for p in pts] go = 1 approx_ch = set() while go < 3: print "new loop, {} points".format(len(pts)) T = set() n = 0 for _ in range(k*k): z = F.random_element() m_min = m_max = z.dot_product(pts[0][0]) p_min = p_max = pts[0][0] for p,pa in pts[1:]: m = z.dot_product(pa) if m > m_max: p_max = p m_max = m elif m < m_min: p_min = p m_min = m T.add(p_min) T.add(p_max) n += 1 approx_ch.update(T) if len(approx_ch) > 2*k: T = sample(list(approx_ch), k) else: while len(T) < k: T.update(x[0] for x in sample(pts,k-len(T))) gs = Generator_System() for p in T: gs.insert(point(Linear_Expression(p,0))) poly_T = C_Polyhedron(gs) new_pts = [] for p,pa in pts: if p in T: new_pts.append((p,pa)) continue gs = Generator_System() gs.insert(point(Linear_Expression(p,0))) poly_p = C_Polyhedron(gs) if poly_T.contains(poly_p): go = 0 else: new_pts.append((p,pa)) go += 1 pts = new_pts print "approx ch with {} points".format(len(approx_ch)) return [x[0] for x in pts]
def modform_cusp_info(calc, S, l, precLimit): """ This goes through all the cusps and compares the space given by `(f|R)[S]` with the space of Elliptic modular forms expansion at those cusps. """ assert l == S.det() assert list(calc.curlS) == [S] D = calc.D HermWeight = calc.HermWeight reducedCurlFSize = calc.matrixColumnCount herm_modform_fe_expannsion = FreeModule(QQ, reducedCurlFSize) if not Integer(l).is_squarefree(): # The calculation of the cusp expansion space takes very long here, thus # we skip them for now. return None for cusp in Gamma0(l).cusps(): if cusp == Infinity: continue M = cusp_matrix(cusp) try: gamma, R, tM = solveR(M, S, space=CurlO(D)) except Exception: print (M, S) raise R.set_immutable() # for caching, we need it hashable herm_modforms = herm_modform_fe_expannsion.echelonized_basis_matrix().transpose() ell_R_denom, ell_R_order, M_R = calcMatrixTrans(calc, R) CycloDegree_R = CyclotomicField(ell_R_order).degree() print "M_R[0] nrows, ell_R_denom, ell_R_order, Cyclo degree:", \ M_R[0].nrows(), ell_R_denom, ell_R_order, CycloDegree_R # The maximum precision we can use is M_R[0].nrows(). # However, that can be quite huge (e.g. 600). ce_prec = min(precLimit, M_R[0].nrows()) ce = cuspExpansions(level=l, weight=2*HermWeight, prec=ce_prec) ell_M_denom, ell_M = ce.expansion_at(SL2Z(M)) print "ell_M_denom, ell_M nrows:", ell_M_denom, ell_M.nrows() ell_M_order = ell_R_order # not sure here. just try the one from R. toCyclPowerBase would fail if this doesn't work # CyclotomicField(l / prod(l.prime_divisors())) should also work. # Transform to same denom. denom_lcm = int(lcm(ell_R_denom, ell_M_denom)) ell_M = addRows(ell_M, denom_lcm / ell_M_denom) M_R = [addRows(M_R_i, denom_lcm / ell_R_denom) for M_R_i in M_R] ell_R_denom = ell_M_denom = denom_lcm print "new denom:", denom_lcm assert ell_R_denom == ell_M_denom # ell_M rows are the elliptic FE. M_R[i] columns are the elliptic FE. # We expect that M_R gives a higher precision for the ell FE. I'm not sure # if this is always true but we expect it here (maybe not needed, though). print "precision of M_R[0], ell_M, wanted:", M_R[0].nrows(), ell_M.ncols(), ce_prec assert ell_M.ncols() >= ce_prec prec = min(M_R[0].nrows(), ell_M.ncols()) # cut to have same precision M_R = [M_R_i[:prec,:] for M_R_i in M_R] ell_M = ell_M[:,:prec] assert ell_M.ncols() == M_R[0].nrows() == prec print "M_R[0] rank, herm rank, mult rank:", \ M_R[0].rank(), herm_modforms.rank(), (M_R[0] * herm_modforms).rank() ell_R = [M_R_i * herm_modforms for M_R_i in M_R] # I'm not sure on this. Seems to be true and it simplifies things in the following. assert ell_M_order <= ell_R_order, "{0}".format((ell_M_order, ell_R_order)) assert ell_R_order % ell_M_order == 0, "{0}".format((ell_M_order, ell_R_order)) # Transform to same Cyclomotic Field in same power base. ell_M2 = toCyclPowerBase(ell_M, ell_M_order) ell_R2 = toLowerCyclBase(ell_R, ell_R_order, ell_M_order) # We must work with the matrix. maybe we should transform hf_M instead to a # higher order field instead, if this ever fails (I'm not sure). assert ell_R2 is not None assert len(ell_M2) == len(ell_R2) # They should have the same power base & same degree now. print "ell_M2[0], ell_R2[0] rank with order %i:" % ell_M_order, ell_M2[0].rank(), ell_R2[0].rank() assert len(M_R) == len(ell_M2) for i in range(len(ell_M2)): ell_M_space = ell_M2[i].row_space() ell_R_space = ell_R2[i].column_space() merged = ell_M_space.intersection(ell_R_space) herm_modform_fe_expannsion_Ci = M_R[i].solve_right( merged.basis_matrix().transpose() ) herm_modform_fe_expannsion_Ci_module = herm_modform_fe_expannsion_Ci.column_module() herm_modform_fe_expannsion_Ci_module += M_R[i].right_kernel() extra_check_on_herm_superspace( vs=herm_modform_fe_expannsion_Ci_module, D=D, B_cF=calc.B_cF, HermWeight=HermWeight ) herm_modform_fe_expannsion = herm_modform_fe_expannsion.intersection( herm_modform_fe_expannsion_Ci_module ) print "power", i, merged.dimension(), herm_modform_fe_expannsion_Ci_module.dimension(), \ herm_modform_fe_expannsion.dimension() current_dimension = herm_modform_fe_expannsion.dimension() return herm_modform_fe_expannsion
def __init__(self, n, q, D, secret_dist='uniform', m=None): r""" Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianDistributionIntegerSampler` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLES: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb, ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood." % (secret_dist))
class ConvexHullPalp(ConvexHull): r""" Compute convex hull using PALP. Note: the points must be lattice points (ie integer coordinates) and generate the ambient vector space. """ _name = 'PALP' def __init__(self, dim): from sage.modules.free_module import FreeModule self._free_module = FreeModule(ZZ, int(dim)) def __eq__(self, other): return type(self) is type(other) and self._free_module == other._free_module def __call__(self, pts): filename = tmp_filename() pts = list(pts) n = len(pts) if n <= 2: return tuple(pts) d = len(pts[0]) assert d == self._free_module.rank() # PALP only works with full dimension polyhedra!! ppts = [x-pts[0] for x in pts] U = self._free_module.submodule(ppts) d2 = U.rank() if d2 != d: # not full dim # we compute two matrices # M1: small space -> big space (makes decomposition) # M2: big space -> small space (i.e. basis of the module) # warning: matrices act on row vectors, i.e. left action from sage.matrix.constructor import matrix from sage.modules.free_module import FreeModule V2 = FreeModule(ZZ,d2) M1 = U.matrix() assert M1.nrows() == d2 assert M1.ncols() == d M2 = matrix(QQ,d) M2[:d2,:] = M1 i = d2 U = U.change_ring(QQ) F = self._free_module.change_ring(QQ) for b in F.basis(): if b not in U: M2.set_row(i, b) U = F.submodule(U.basis() + [b]) i += 1 assert i == self._free_module.rank() M2 = (~M2)[:,:d2] assert M2.nrows() == d assert M2.ncols() == d2 assert (M1*M2).is_one() pts2 = [p * M2 for p in ppts] else: pts2 = pts d2 = d with open(filename, "w") as output: output.write("{} {}\n".format(n,d2)) for p in pts2: output.write(" ".join(map(str,p))) output.write("\n") args = ['poly.x', '-v', filename] try: palp_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=None, cwd=str(SAGE_TMP)) except OSError: raise RuntimeError("Problem with calling PALP") ans, err = palp_proc.communicate() ret_code = palp_proc.poll() if ret_code: raise RuntimeError("PALP return code is {} from input {}".format(ret_code, pts)) a = ans.split('\n') try: dd,nn = a[0].split(' ')[:2] dd = int(dd) nn = int(nn) if dd > nn: dd,nn = nn,dd except (TypeError,ValueError): raise RuntimeError("PALP got wrong:\n{}".format(ans)) if d2 != int(dd): raise RuntimeError("dimension changed... have d={} but PALP answered dd={} and nn={}".format(d2,dd,nn)) n2 = int(nn) coords = [] for i in xrange(1,d2+1): coords.append(map(ZZ,a[i].split())) new_pts = zip(*coords) if d2 != d: new_pts = [pts[0] + V2(p)*M1 for p in new_pts] t = [self._free_module(p) for p in new_pts] for v in t: v.set_immutable() t.sort() return tuple(t)
def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, *, ambient_space=None, base_ring=None): r""" Construct a suitable parent class for polyhedra INPUT: - ``base_ring`` -- A ring. Currently there are backends for `\ZZ`, `\QQ`, and `\RDF`. - ``ambient_dim`` -- integer. The ambient space dimension. - ``ambient_space`` -- A free module. - ``backend`` -- string. The name of the backend for computations. There are several backends implemented: * ``backend="ppl"`` uses the Parma Polyhedra Library * ``backend="cdd"`` uses CDD * ``backend="normaliz"`` uses normaliz * ``backend="polymake"`` uses polymake * ``backend="field"`` a generic Sage implementation OUTPUT: A parent class for polyhedra over the given base ring if the backend supports it. If not, the parent base ring can be larger (for example, `\QQ` instead of `\ZZ`). If there is no implementation at all, a ``ValueError`` is raised. EXAMPLES:: sage: from sage.geometry.polyhedron.parent import Polyhedra sage: Polyhedra(AA, 3) Polyhedra in AA^3 sage: Polyhedra(ZZ, 3) Polyhedra in ZZ^3 sage: type(_) <class 'sage.geometry.polyhedron.parent.Polyhedra_ZZ_ppl_with_category'> sage: Polyhedra(QQ, 3, backend='cdd') Polyhedra in QQ^3 sage: type(_) <class 'sage.geometry.polyhedron.parent.Polyhedra_QQ_cdd_with_category'> CDD does not support integer polytopes directly:: sage: Polyhedra(ZZ, 3, backend='cdd') Polyhedra in QQ^3 Using a more general form of the constructor:: sage: V = VectorSpace(QQ, 3) sage: Polyhedra(V) is Polyhedra(QQ, 3) True sage: Polyhedra(V, backend='field') is Polyhedra(QQ, 3, 'field') True sage: Polyhedra(backend='field', ambient_space=V) is Polyhedra(QQ, 3, 'field') True sage: M = FreeModule(ZZ, 2) sage: Polyhedra(M, backend='ppl') is Polyhedra(ZZ, 2, 'ppl') True TESTS:: sage: Polyhedra(RR, 3, backend='field') Traceback (most recent call last): ... ValueError: the 'field' backend for polyhedron can not be used with non-exact fields sage: Polyhedra(RR, 3) Traceback (most recent call last): ... ValueError: no default backend for computations with Real Field with 53 bits of precision sage: Polyhedra(QQ[I], 2) Traceback (most recent call last): ... ValueError: invalid base ring: Number Field in I with defining polynomial x^2 + 1 with I = 1*I cannot be coerced to a real field """ if ambient_space_or_base_ring is not None: if ambient_space_or_base_ring in Rings(): base_ring = ambient_space_or_base_ring else: ambient_space = ambient_space_or_base_ring if ambient_space is not None: if ambient_space not in Modules: # There is no category of free modules, unfortunately # (see https://trac.sagemath.org/ticket/30164)... raise ValueError('ambient_space must be a free module') if base_ring is None: base_ring = ambient_space.base_ring() if ambient_dim is None: try: ambient_dim = ambient_space.rank() except AttributeError: # ... so we test whether it is free using the existence of # a rank method raise ValueError('ambient_space must be a free module') if ambient_space is not FreeModule(base_ring, ambient_dim): raise NotImplementedError( 'ambient_space must be a standard free module') if backend is None: if base_ring is ZZ or base_ring is QQ: backend = 'ppl' elif base_ring is RDF: backend = 'cdd' elif base_ring.is_exact(): # TODO: find a more robust way of checking that the coefficients are indeed # real numbers if not RDF.has_coerce_map_from(base_ring): raise ValueError( "invalid base ring: {} cannot be coerced to a real field". format(base_ring)) backend = 'field' else: raise ValueError( "no default backend for computations with {}".format( base_ring)) from sage.symbolic.ring import SR if backend == 'ppl' and base_ring is QQ: return Polyhedra_QQ_ppl(base_ring, ambient_dim, backend) elif backend == 'ppl' and base_ring is ZZ: return Polyhedra_ZZ_ppl(base_ring, ambient_dim, backend) elif backend == 'normaliz' and base_ring is QQ: return Polyhedra_QQ_normaliz(base_ring, ambient_dim, backend) elif backend == 'normaliz' and base_ring is ZZ: return Polyhedra_ZZ_normaliz(base_ring, ambient_dim, backend) elif backend == 'normaliz' and (base_ring is SR or base_ring.is_exact()): return Polyhedra_normaliz(base_ring, ambient_dim, backend) elif backend == 'cdd' and base_ring in (ZZ, QQ): return Polyhedra_QQ_cdd(QQ, ambient_dim, backend) elif backend == 'cdd' and base_ring is RDF: return Polyhedra_RDF_cdd(RDF, ambient_dim, backend) elif backend == 'polymake': return Polyhedra_polymake(base_ring.fraction_field(), ambient_dim, backend) elif backend == 'field': if not base_ring.is_exact(): raise ValueError( "the 'field' backend for polyhedron can not be used with non-exact fields" ) return Polyhedra_field(base_ring.fraction_field(), ambient_dim, backend) else: raise ValueError('No such backend (=' + str(backend) + ') implemented for given basering (=' + str(base_ring) + ').')
def normal_cone(self): r""" Return the (closure of the) normal cone of the triangulation. Recall that a regular triangulation is one that equals the "crease lines" of a convex piecewise-linear function. This support function is not unique, for example, you can scale it by a positive constant. The set of all piecewise-linear functions with fixed creases forms an open cone. This cone can be interpreted as the cone of normal vectors at a point of the secondary polytope, which is why we call it normal cone. See [GKZ1994]_ Section 7.1 for details. OUTPUT: The closure of the normal cone. The `i`-th entry equals the value of the piecewise-linear function at the `i`-th point of the configuration. For an irregular triangulation, the normal cone is empty. In this case, a single point (the origin) is returned. EXAMPLES:: sage: triangulation = polytopes.hypercube(2).triangulate(engine='internal') sage: triangulation (<0,1,3>, <1,2,3>) sage: N = triangulation.normal_cone(); N 4-d cone in 4-d lattice sage: N.rays() ( 0, 0, 0, -1), ( 0, 0, 1, 1), ( 0, 0, -1, -1), ( 1, 0, 0, 1), (-1, 0, 0, -1), ( 0, 1, 0, -1), ( 0, -1, 0, 1) in Ambient free module of rank 4 over the principal ideal domain Integer Ring sage: N.dual().rays() (1, -1, 1, -1) in Ambient free module of rank 4 over the principal ideal domain Integer Ring TESTS:: sage: polytopes.simplex(2).triangulate().normal_cone() 3-d cone in 3-d lattice sage: _.dual().is_trivial() True """ if not self.point_configuration().base_ring().is_subring(QQ): raise NotImplementedError( 'Only base rings ZZ and QQ are supported') from ppl import Constraint_System, Linear_Expression, C_Polyhedron from sage.matrix.constructor import matrix from sage.arith.all import lcm pc = self.point_configuration() cs = Constraint_System() for facet in self.interior_facets(): s0, s1 = self._boundary_simplex_dictionary()[facet] p = set(s0).difference(facet).pop() q = set(s1).difference(facet).pop() origin = pc.point(p).reduced_affine_vector() base_indices = [i for i in s0 if i != p] base = matrix([ pc.point(i).reduced_affine_vector() - origin for i in base_indices ]) sol = base.solve_left(pc.point(q).reduced_affine_vector() - origin) relation = [0] * pc.n_points() relation[p] = sum(sol) - 1 relation[q] = 1 for i, base_i in enumerate(base_indices): relation[base_i] = -sol[i] rel_denom = lcm([QQ(r).denominator() for r in relation]) relation = [ZZ(r * rel_denom) for r in relation] ex = Linear_Expression(relation, 0) cs.insert(ex >= 0) from sage.modules.free_module import FreeModule ambient = FreeModule(ZZ, self.point_configuration().n_points()) if cs.empty(): cone = C_Polyhedron(ambient.dimension(), 'universe') else: cone = C_Polyhedron(cs) from sage.geometry.cone import _Cone_from_PPL return _Cone_from_PPL(cone, lattice=ambient)
def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i, k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory( category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()] * len(index_set) for k, c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()}
class LWE(SageObject): """ Learning with Errors (LWE) oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, n, q, D, secret_dist='uniform', m=None): """ Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianSamplerRejection` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret; one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distrbution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLE: First, we construct a noise distribution with standard deviation 3.0:: sage: D = DiscreteGaussianSamplerRejection(3.0) Next, we construct our oracle:: sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb, ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood." % (secret_dist)) def _repr_(self): """ EXAMPLE:: sage: D = DiscreteGaussianSamplerRejection(3.0) sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), (-3, 3), None) """ if type(self.secret_dist) == str: return "LWE(%d, %d, %s, '%s', %s)" % ( self.n, self.K.order(), self.D, self.secret_dist, self.m) else: return "LWE(%d, %d, %s, %s, %s)" % (self.n, self.K.order(), self.D, self.secret_dist, self.m) def __call__(self): """ EXAMPLE:: sage: LWE(10, 401, DiscreteGaussianSamplerRejection(3))() ((309, 347, 198, 194, 336, 360, 264, 123, 368, 398), 198) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i += 1 a = self.FM.random_element() return a, a.dot_product(self.__s) + self.K(self.D())
class LWE(SageObject): """ Learning with Errors (LWE) oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, n, q, D, secret_dist='uniform', m=None): """ Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianSamplerRejection` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLE: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.crypto.lwe import DiscreteGaussianSampler sage: D = DiscreteGaussianSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ,secret_dist) self.__s = vector(self.K, self.n, [randint(lb,ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist)) def _repr_(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE sage: D = DiscreteGaussianSampler(3.0) sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), (-3, 3), None) """ if isinstance(self.secret_dist, str): return "LWE(%d, %d, %s, '%s', %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m) else: return "LWE(%d, %d, %s, %s, %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m) def __call__(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE sage: LWE(10, 401, DiscreteGaussianSampler(3))() ((309, 347, 198, 194, 336, 360, 264, 123, 368, 398), 198) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i+=1 a = self.FM.random_element() return a, a.dot_product(self.__s) + self.K(self.D())
def stratification(L): r""" Return a stratification of the Lie algebra if one exists. INPUT: - ``L`` -- a Lie algebra OUTPUT: A grading of the Lie algebra `\mathfrak{g}` over the integers such that the layer `\mathfrak{g}_1` generates the full Lie algebra. EXAMPLES:: A stratification for a free nilpotent Lie algebra is the one based on the length of the defining bracket:: sage: from lie_gradings.gradings.grading import stratification sage: from lie_gradings.gradings.utilities import in_new_basis sage: L = LieAlgebra(QQ, 3, step=3) sage: strat = stratification(L) sage: strat Grading over Additive abelian group isomorphic to Z of Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Rational Field with nonzero layers (1) : (X_1, X_2, X_3) (2) : (X_12, X_13, X_23) (3) : (X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) The main use case is when the original basis of the stratifiable Lie algebra is not adapted to a stratification. Consider the following quotient Lie algebra:: sage: X_1, X_2, X_3 = L.basis().list()[:3] sage: Q = L.quotient(L[X_2, X_3]) sage: Q Lie algebra quotient L/I of dimension 10 over Rational Field where L: Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Rational Field I: Ideal (X_23) We switch to a basis which does not define a stratification:: sage: Y_1 = Q(X_1) sage: Y_2 = Q(X_2) + Q[X_1, X_2] sage: Y_3 = Q(X_3) sage: basis = [Y_1, Y_2, Y_3] + Q.basis().list()[3:] sage: Y_labels = ["Y_%d"%(k+1) for k in range(len(basis))] sage: K = in_new_basis(Q, basis,Y_labels) sage: K.inject_variables() Defining Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10 sage: K[Y_2, Y_3] Y_9 sage: K[[Y_1, Y_3], Y_2] Y_9 We may reconstruct a stratification in the new basis without any knowledge of the original stratification:: sage: stratification(K) Grading over Additive abelian group isomorphic to Z of Nilpotent Lie algebra on 10 generators (Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10) over Rational Field with nonzero layers (1) : (Y_1, Y_2 - Y_4, Y_3) (2) : (Y_4, Y_5) (3) : (Y_10, Y_6, Y_7, Y_8, Y_9) sage: K[Y_1, Y_2 - Y_4] Y_4 sage: K[Y_1, Y_3] Y_5 sage: K[Y_2 - Y_4, Y_3] 0 A non-stratifiable Lie algebra raises an error:: sage: L = LieAlgebra(QQ, {('X_1','X_3'): {'X_4': 1}, ....: ('X_1','X_4'): {'X_5': 1}, ....: ('X_2','X_3'): {'X_5': 1}}, ....: names='X_1,X_2,X_3,X_4,X_5') sage: stratification(L) Traceback (most recent call last): ... ValueError: Lie algebra on 5 generators (X_1, X_2, X_3, X_4, X_5) over Rational Field is not a stratifiable Lie algebra """ lcs = L.lower_central_series(submodule=True) quots = [V.quotient(W) for V, W in zip(lcs, lcs[1:])] # find a basis adapted to the filtration by the lower central series adapted_basis = [] weights = [] for k, q in enumerate(quots): weights += [k + 1] * q.dimension() for v in q.basis(): b = q.lift(v) adapted_basis.append(b) # define a submodule to compute structural # coefficients in the filtration adapted basis try: m = L.module() except AttributeError: m = FreeModule(L.base_ring(), L.dimension()) sm = m.submodule_with_basis(adapted_basis) # form the linear system Ax=b of constraints from the Leibniz rule paramspace = [(k, h) for k in range(L.dimension()) for h in range(L.dimension()) if weights[h] > weights[k]] Arows = [] bvec = [] zerovec = m.zero() for i in range(L.dimension()): Y_i = adapted_basis[i] w_i = weights[i] for j in range(i + 1, L.dimension()): Y_j = adapted_basis[j] w_j = weights[j] Y_ij = L.bracket(Y_i, Y_j) c_ij = sm.coordinate_vector(Y_ij.to_vector()) bcomp = L.zero() for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] bcomp += (w_k - w_i - w_j) * c_ij[k] * Y_k bv = bcomp.to_vector() Acomp = {} for k, h in paramspace: w_k = weights[k] Y_h = adapted_basis[h] if k == i: Acomp[(k, h)] = L.bracket(Y_h, Y_j).to_vector() elif k == j: Acomp[(k, h)] = L.bracket(Y_i, Y_h).to_vector() elif w_k >= w_i + w_j: Acomp[(k, h)] = -c_ij[k] * Y_h for r in range(L.dimension()): Arows.append( [Acomp.get((k, h), zerovec)[r] for k, h in paramspace]) bvec.append(bv[r]) A = matrix(L.base_ring(), Arows) b = vector(L.base_ring(), bvec) # solve the linear system Ax=b if possible try: coeffs_flat = A.solve_right(b) except ValueError: raise ValueError("%s is not a stratifiable Lie algebra" % L) coeffs = {(k, h): ckh for (k, h), ckh in zip(paramspace, coeffs_flat)} # define the matrix of the derivation determined by the solution # in the adapted basis cols = [] for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] hspace = [h for (l, h) in paramspace if l == k] Yk_im = w_k * Y_k + sum( (coeffs[(k, h)] * adapted_basis[h] for h in hspace), L.zero()) cols.append(sm.coordinate_vector(Yk_im.to_vector())) der = matrix(L.base_ring(), cols).transpose() # the layers of the stratification are V_k = ker(der - kI) layers = {} for k in range(len(quots)): degree = k + 1 B = der - degree * matrix.identity(L.dimension()) adapted_kernel = B.right_kernel() # convert back to the original basis Vk_basis = [sm.from_vector(X) for X in adapted_kernel.basis()] layers[(degree, )] = [ L.from_vector(v) for v in m.submodule(Vk_basis).basis() ] return grading(L, layers, magma=AdditiveAbelianGroup([0]), projections=True)
def maximal_grading(L): r""" Return a maximal grading of a Lie algebra defined over an algebraically closed field. A maximal grading of a Lie algebra `\mathfrak{g}` is the finest possible grading of `\mathfrak{g}` over torsion free abelian groups. If `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^k} \mathfrak{g}_n` is a maximal grading, then there exists no other grading `\mathfrak{g} = \bigoplus_{a\in A} \mathfrak{g}_a` over a torsion free abelian group `A` such that every `\mathfrak{g}_a` is contained in some `\mathfrak{g}_n`. EXAMPLES: A maximal grading of an abelian Lie algebra puts each basis element into an independent layer:: sage: import sys, pathlib sage: sys.path.append(str(pathlib.Path().absolute())) sage: from lie_gradings.gradings.grading import maximal_grading sage: L = LieAlgebra(QQbar, 4, abelian=True) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z + Z of Abelian Lie algebra on 4 generators (L[0], L[1], L[2], L[3]) over Algebraic Field with nonzero layers (1, 0, 0, 0) : (L[3],) (0, 1, 0, 0) : (L[2],) (0, 0, 1, 0) : (L[1],) (0, 0, 0, 1) : (L[0],) A maximal grading of a free nilpotent Lie algebra decomposes the Lie algebra based on how many times each generator appears in the defining Lie bracket:: sage: L = LieAlgebra(QQbar, 3, step=3) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z of Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Algebraic Field with nonzero layers (1, 0, 0) : (X_1,) (0, 1, 0) : (X_2,) (1, 1, 0) : (X_12,) (1, 2, 0) : (X_122,) (2, 1, 0) : (X_112,) (0, 0, 1) : (X_3,) (0, 1, 1) : (X_23,) (0, 1, 2) : (X_233,) (0, 2, 1) : (X_223,) (1, 0, 1) : (X_13,) (1, 0, 2) : (X_133,) (1, 1, 1) : (X_123, X_132) (2, 0, 1) : (X_113,) """ # define utilities to convert from matrices to vectors and back R = L.base_ring() n = L.dimension() MS = MatrixSpace(R, n, n) def matrix_to_vec(A): return vector(R, sum((list(Ar) for Ar in A.rows()), [])) db = L.derivations_basis() def derivation_lincomb(vec): return sum((vk * dk for vk, dk in zip(vec, db)), MS.zero()) # iteratively construct larger and larger tori of derivations t = [] while True: # compute the centralizer of the torus in the derivation algebra ker = FreeModule(R, len(db)) for der in t: # form the matrix of ad(der) with rows # the images of basis derivations A = matrix([matrix_to_vec(der * X - X * der) for X in db]) ker = ker.intersection(A.left_kernel()) cb = [derivation_lincomb(v) for v in ker.basis()] # check the basis of the centralizer for semisimple parts outside of t gl = FreeModule(R, n * n) t_submodule = gl.submodule([matrix_to_vec(der) for der in t]) for A in cb: As, An = jordan_decomposition(A) if matrix_to_vec(As) not in t_submodule: # extend the torus by As t.append(As) break else: # no new elements found, so the torus is maximal break # compute the eigenspace intersections to get the concrete grading common_eigenspaces = [([], FreeModule(R, n))] for A in t: new_eigenspaces = [] eig = A.right_eigenspaces() for ev, V in common_eigenspaces: for ew, W in eig: VW = V.intersection(W) if VW.dimension() > 0: new_eigenspaces.append((ev + [ew], VW)) common_eigenspaces = new_eigenspaces if not t: # zero dimensional maximal torus # the only grading is the trivial grading magma = AdditiveAbelianGroup([]) layers = {magma.zero(): L.basis().list()} return grading(L, layers, magma=magma) # define a grading with layers indexed by tuples of eigenvalues cm = get_coercion_model() all_eigenvalues = sum((ev for ev, V in common_eigenspaces), []) k = len(common_eigenspaces[0][0]) evR = cm.common_parent(*all_eigenvalues) layers = { tuple(ev): [L.from_vector(v) for v in V.basis()] for ev, V in common_eigenspaces } magma = evR.cartesian_product(*[evR] * (k - 1)) gr = grading(L, layers, magma=magma) # convert to a grading over Z^k return gr.universal_realization()
class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. """ def __init__(self, R, form, names=None): """ Initialize ``self``. TESTS:: sage: m = matrix([[-2,3],[3,4]]) sage: J = JordanAlgebra(m) sage: TestSuite(J).run() """ self._form = form self._M = FreeModule(R, form.ncols()) cat = MagmaticAlgebras(R).Commutative().Unital().FiniteDimensional().WithBasis() self._no_generic_basering_coercion = True # Remove once 16492 is fixed Parent.__init__(self, base=R, names=names, category=cat) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[-2,3],[3,4]]) sage: JordanAlgebra(m) Jordan algebra over Integer Ring given by the symmetric bilinear form: [-2 3] [ 3 4] """ return "Jordan algebra over {} given by the symmetric bilinear" \ " form:\n{}".format(self.base_ring(), self._form) def _element_constructor_(self, *args): """ Construct an element of ``self`` from ``s``. Here ``s`` can be a pair of an element of `R` and an element of `M`, or an element of `R`, or an element of `M`, or an element of a(nother) Jordan algebra given by a symmetric bilinear form. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0, 0) sage: J((-4, (2, 5))) -4 + (2, 5) sage: J((-4, (ZZ^2)((2, 5)))) -4 + (2, 5) sage: J(2, (-2, 3)) 2 + (-2, 3) sage: J(2, (ZZ^2)((-2, 3))) 2 + (-2, 3) sage: J(-1, 1, 0) -1 + (1, 0) sage: J((ZZ^2)((1, 3))) 0 + (1, 3) sage: m = matrix([[2]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0) sage: J((-4, (2,))) -4 + (2) sage: J(2, (-2,)) 2 + (-2) sage: J(-1, 1) -1 + (1) sage: J((ZZ^1)((3,))) 0 + (3) sage: m = Matrix(QQ, []) sage: J = JordanAlgebra(m) sage: J(2) 2 + () sage: J((-4, ())) -4 + () sage: J(2, ()) 2 + () sage: J(-1) -1 + () sage: J((ZZ^0)(())) 0 + () """ R = self.base_ring() if len(args) == 1: s = args[0] if isinstance(s, JordanAlgebraSymmetricBilinear.Element): if s.parent() is self: return s return self.element_class(self, R(s._s), self._M(s._v)) if isinstance(s, (list, tuple)): if len(s) != 2: raise ValueError("must be length 2") return self.element_class(self, R(s[0]), self._M(s[1])) if s in self._M: return self.element_class(self, R.zero(), self._M(s)) return self.element_class(self, R(s), self._M.zero()) if len(args) == 2 and (isinstance(args[1], (list, tuple)) or args[1] in self._M): return self.element_class(self, R(args[0]), self._M(args[1])) if len(args) == self._form.ncols() + 1: return self.element_class(self, R(args[0]), self._M(args[1:])) raise ValueError("unable to construct an element from the given data") @cached_method def basis(self): """ Return a basis of ``self``. The basis returned begins with the unity of `R` and continues with the standard basis of `M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ R = self.base_ring() ret = (self.element_class(self, R.one(), self._M.zero()),) ret += tuple(self.element_class(self, R.zero(), x) for x in self._M.basis()) return Family(ret) algebra_generators = basis def gens(self): """ Return the generators of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ return tuple(self.algebra_generators()) @cached_method def zero(self): """ Return the element 0. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.zero() 0 + (0, 0) """ return self.element_class(self, self.base_ring().zero(), self._M.zero()) @cached_method def one(self): """ Return the element 1 if it exists. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.one() 1 + (0, 0) """ return self.element_class(self, self.base_ring().one(), self._M.zero()) class Element(AlgebraElement): """ An element of a Jordan algebra defined by a symmetric bilinear form. """ def __init__(self, parent, s, v): """ Initialize ``self``. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: TestSuite(a + 2*b - c).run() """ self._s = s self._v = v AlgebraElement.__init__(self, parent) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + 2*b - c 1 + (2, -1) """ return "{} + {}".format(self._s, self._v) def _latex_(self): r""" Return a latex representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: latex(a + 2*b - c) 1 + \left(2,\,-1\right) """ from sage.misc.latex import latex return "{} + {}".format(latex(self._s), latex(self._v)) def __bool__(self): """ Return if ``self`` is non-zero. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: bool(1) True sage: bool(b) True sage: bool(a + 2*b - c) True """ return bool(self._s) or bool(self._v) __nonzero__ = __bool__ def __eq__(self, other): """ Check equality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x == J((4, (-1, 3))) True sage: a == x False sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c == x False """ if not isinstance(other, JordanAlgebraSymmetricBilinear.Element): return False if other.parent() != self.parent(): return False return self._s == other._s and self._v == other._v def __ne__(self, other): """ Check inequality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x != J((4, (-1, 3))) False sage: a != x True sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c != x True """ return not self == other def _add_(self, other): """ Add ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + b 1 + (1, 0) sage: b + c 0 + (1, 1) """ return self.__class__(self.parent(), self._s + other._s, self._v + other._v) def _neg_(self): """ Negate ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: -(a + b - 2*c) -1 + (-1, 2) """ return self.__class__(self.parent(), -self._s, -self._v) def _sub_(self, other): """ Subtract ``other`` from ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a - b 1 + (-1, 0) sage: b - c 0 + (1, -1) """ return self.__class__(self.parent(), self._s - other._s, self._v - other._v) def _mul_(self, other): """ Multiply ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 12 + (6, 2) sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 21 + (6, 2) """ P = self.parent() return self.__class__(P, self._s * other._s + (self._v * P._form * other._v.column())[0], other._s * self._v + self._s * other._v) def _lmul_(self, other): """ Multiply ``self`` by the scalar ``other`` on the left. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (a + b - c) * 2 2 + (2, -2) """ return self.__class__(self.parent(), self._s * other, self._v * other) def _rmul_(self, other): """ Multiply ``self`` with the scalar ``other`` by the right action. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 2 * (a + b - c) 2 + (2, -2) """ return self.__class__(self.parent(), other * self._s, other * self._v) def monomial_coefficients(self, copy=True): """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding coefficients. INPUT: - ``copy`` -- ignored EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: elt = a + 2*b - c sage: elt.monomial_coefficients() {0: 1, 1: 2, 2: -1} """ d = {0: self._s} for i,c in enumerate(self._v): d[i+1] = c return d def trace(self): r""" Return the trace of ``self``. The trace of an element `\alpha + x \in M^*` is given by `t(\alpha + x) = 2 \alpha`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.trace() 8 """ return 2 * self._s def norm(self): r""" Return the norm of ``self``. The norm of an element `\alpha + x \in M^*` is given by `n(\alpha + x) = \alpha^2 - (x, x)`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c; x 4 + (-1, 3) sage: x.norm() 13 """ return self._s * self._s - (self._v * self.parent()._form * self._v.column())[0] def bar(self): r""" Return the result of the bar involution of ``self``. The bar involution `\bar{\cdot}` is the `R`-linear endomorphism of `M^*` defined by `\bar{1} = 1` and `\bar{x} = -x` for `x \in M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.bar() 4 + (1, -3) We check that it is an algebra morphism:: sage: y = 2*a + 2*b - c sage: x.bar() * y.bar() == (x*y).bar() True """ return self.__class__(self.parent(), self._s, -self._v)
class LWE(SageObject): """ Learning with Errors (LWE) oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, n, q, D, secret_dist='uniform', m=None): r""" Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianDistributionIntegerSampler` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLES: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) and sample 1000 samples:: sage: L = [] sage: def add_samples(): ....: global L ....: L += [lwe() for _ in range(1000)] sage: add_samples() To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = lambda : [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: from numpy import std sage: while abs(std([e if e <= 200 else e-401 for e in S()]) - 3.0) > 0.01: ....: add_samples() If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb, ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood." % (secret_dist)) def _repr_(self): """ EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: from sage.crypto.lwe import LWE sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, (-3, 3), None) """ if isinstance(self.secret_dist, str): return "LWE(%d, %d, %s, '%s', %s)" % ( self.n, self.K.order(), self.D, self.secret_dist, self.m) else: return "LWE(%d, %d, %s, %s, %s)" % (self.n, self.K.order(), self.D, self.secret_dist, self.m) def __call__(self): """ EXAMPLES:: sage: from sage.crypto.lwe import DiscreteGaussianDistributionIntegerSampler, LWE sage: LWE(10, 401, DiscreteGaussianDistributionIntegerSampler(3))()[0].parent() Vector space of dimension 10 over Ring of integers modulo 401 sage: LWE(10, 401, DiscreteGaussianDistributionIntegerSampler(3))()[1].parent() Ring of integers modulo 401 """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i += 1 a = self.FM.random_element() return a, a.dot_product(self.__s) + self.K(self.D())
def torsion_free_gradings(L): r""" Return a complete list of gradings of the Lie algebra over torsion free abelian groups. The list is guaranteed to be complete in the following sense: If `\mathfrak{g} = \bigoplus_{a\in A} \mathfrak{g}_a` is any grading of the Lie algebra `\mathfrak{g}` over a torsion free abelian group `A`, then there exists - a grading `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^m} \mathfrak{g}_n`, - an automorphism `\Phi\in\mathrm{Aut}(\mathfrak{g})`, and - a homomorphism `\varphi\colon\mathbb{Z}^m\to A` such that the grading `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^m} \Phi(\mathfrak{g}_{\varphi(n)})` is exactly the same as the original `A`-grading. However, the list is not guaranteed to be reduced up to automorphism, so the above choices are not in general unique. EXAMPLES: We list all gradings of the Heisenberg Lie algebra over torsion free abelian groups:: sage: from lie_gradings.gradings.grading import torsion_free_gradings sage: L = lie_algebras.Heisenberg(QQ, 1) sage: torsion_free_gradings(L) [Grading over Additive abelian group isomorphic to Z + Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1, 0) : (p1,) (0, 1) : (q1,) (1, 1) : (z,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (p1, z) (0) : (q1,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (q1, z) (0) : (p1,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (p1, q1) (2) : (z,) , Grading over Trivial group of Heisenberg algebra of rank 1 over Rational Field with nonzero layers () : (p1, q1, z) ] """ maxgrading = maximal_grading(L) V = FreeModule(ZZ, len(maxgrading.magma().gens())) weights = maxgrading.layers().keys() # The torsion-free gradings are enumerated by torsion-free quotients # of the grading group of the maximal grading. diffset = set([tuple(b - a) for a, b in combinations(weights, 2)]) subspaces = [] for d in range(len(diffset) + 1): for B in combinations(diffset, d): W = V.submodule(B) if W not in subspaces: subspaces.append(W) # for each subspace, define the quotient grading projected_gradings = [] for W in subspaces: Q = V.quotient(W) # check if quotient is not torsion-free if any(qi > 0 for qi in Q.invariants()): continue quot_layers = {} for n in weights: pi_n = tuple(Q(V(tuple(n)))) if pi_n not in quot_layers: quot_layers[pi_n] = [] quot_layers[pi_n].extend(maxgrading.layers()[n]) # expand away denominators to get an integer vector grading A = AdditiveAbelianGroup(Q.invariants()) denoms = [ pi_n_k.denominator() for pi_n in quot_layers for pi_n_k in pi_n ] mult = lcm(denoms) proj_layers = { tuple(mult * pi_nk for pi_nk in pi_n): l for pi_n, l in quot_layers.items() } proj_grading = grading(L, proj_layers, magma=A, projections=True) projected_gradings.append(proj_grading) return projected_gradings
class LieAlgebraWithStructureCoefficients(FinitelyGeneratedLieAlgebra, IndexedGenerators): r""" A Lie algebra with a set of specified structure coefficients. The structure coefficients are specified as a dictionary `d` whose keys are pairs of basis indices, and whose values are dictionaries which in turn are indexed by basis indices. The value of `d` at a pair `(u, v)` of basis indices is the dictionary whose `w`-th entry (for `w` a basis index) is the coefficient of `b_w` in the Lie bracket `[b_u, b_v]` (where `b_x` means the basis element with index `x`). INPUT: - ``R`` -- a ring, to be used as the base ring - ``s_coeff`` -- a dictionary, indexed by pairs of basis indices (see below), and whose values are dictionaries which are indexed by (single) basis indices and whose values are elements of `R` - ``names`` -- list or tuple of strings - ``index_set`` -- (default: ``names``) list or tuple of hashable and comparable elements OUTPUT: A Lie algebra over ``R`` which (as an `R`-module) is free with a basis indexed by the elements of ``index_set``. The `i`-th basis element is displayed using the name ``names[i]``. If we let `b_i` denote this `i`-th basis element, then the Lie bracket is given by the requirement that the `b_k`-coefficient of `[b_i, b_j]` is ``s_coeff[(i, j)][k]`` if ``s_coeff[(i, j)]`` exists, otherwise ``-s_coeff[(j, i)][k]`` if ``s_coeff[(j, i)]`` exists, otherwise `0`. EXAMPLES: We create the Lie algebra of `\QQ^3` under the Lie bracket defined by `\times` (cross-product):: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}) sage: (x,y,z) = L.gens() sage: L.bracket(x, y) z sage: L.bracket(y, x) -z TESTS: We can input structure coefficients that fail the Jacobi identity, but the test suite will call us out on it:: sage: Fake = LieAlgebra(QQ, 'x,y,z', {('x','y'):{'z':3}, ('y','z'):{'z':1}, ('z','x'):{'y':1}}) sage: TestSuite(Fake).run() Failure in _test_jacobi_identity: ... Old tests !!!!!placeholder for now!!!!!:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.basis() Finite family {'x': x, 'y': y} """ @staticmethod def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: L1 = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: L2 = LieAlgebra(QQ, 'x,y', {('y','x'): {'x':-1}}) sage: L1 is L2 True Check that we convert names to the indexing set:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}, index_set=list(range(3))) sage: (x,y,z) = L.gens() sage: L[x,y] L[2] """ names, index_set = standardize_names_index_set(names, index_set) # Make sure the structure coefficients are given by the index set if names is not None and names != tuple(index_set): d = {x: index_set[i] for i,x in enumerate(names)} get_pairs = lambda X: X.items() if isinstance(X, dict) else X try: s_coeff = {(d[k[0]], d[k[1]]): [(d[x], y) for x,y in get_pairs(s_coeff[k])] for k in s_coeff} except KeyError: # At this point we assume they are given by the index set pass s_coeff = LieAlgebraWithStructureCoefficients._standardize_s_coeff(s_coeff, index_set) if s_coeff.cardinality() == 0: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) if (names is None and len(index_set) <= 1) or len(names) <= 1: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) return super(LieAlgebraWithStructureCoefficients, cls).__classcall__( cls, R, s_coeff, names, index_set, **kwds) @staticmethod def _standardize_s_coeff(s_coeff, index_set): """ Helper function to standardize ``s_coeff`` into the appropriate form (dictionary indexed by pairs, whose values are dictionaries). Strips items with coefficients of 0 and duplicate entries. This does not check the Jacobi relation (nor antisymmetry if the cardinality is infinite). EXAMPLES:: sage: from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients sage: d = {('y','x'): {'x':-1}} sage: LieAlgebraWithStructureCoefficients._standardize_s_coeff(d, ('x', 'y')) Finite family {('x', 'y'): (('x', 1),)} """ # Try to handle infinite basis (once/if supported) #if isinstance(s_coeff, AbstractFamily) and s_coeff.cardinality() == infinity: # return s_coeff index_to_pos = {k: i for i,k in enumerate(index_set)} sc = {} # Make sure the first gen is smaller than the second in each key for k in s_coeff.keys(): v = s_coeff[k] if isinstance(v, dict): v = v.items() if index_to_pos[k[0]] > index_to_pos[k[1]]: key = (k[1], k[0]) vals = tuple((g, -val) for g, val in v if val != 0) else: if not index_to_pos[k[0]] < index_to_pos[k[1]]: if k[0] == k[1]: if not all(val == 0 for g, val in v): raise ValueError("elements {} are equal but their bracket is not set to 0".format(k)) continue key = tuple(k) vals = tuple((g, val) for g, val in v if val != 0) if key in sc.keys() and sorted(sc[key]) != sorted(vals): raise ValueError("two distinct values given for one and the same bracket") if vals: sc[key] = vals return Family(sc) def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i,k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory(category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()]*len(index_set) for k,c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()} # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator def structure_coefficients(self, include_zeros=False): """ Return the dictionary of structure coefficients of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'x':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): x} sage: S = L.structure_coefficients(True); S Finite family {('x', 'y'): x, ('x', 'z'): 0, ('y', 'z'): 0} sage: S['x','z'].parent() is L True TESTS: Check that :trac:`23373` is fixed:: sage: L = lie_algebras.sl(QQ, 2) sage: sorted(L.structure_coefficients(True), key=str) [-2*E[-alpha[1]], -2*E[alpha[1]], h1] """ if not include_zeros: pos_to_index = dict(enumerate(self._indices)) return Family({(pos_to_index[k[0]], pos_to_index[k[1]]): self.element_class(self, self._s_coeff[k]) for k in self._s_coeff}) ret = {} zero = self._M.zero() for i,x in enumerate(self._indices): for j, y in enumerate(self._indices[i+1:]): if (i, j+i+1) in self._s_coeff: elt = self._s_coeff[i, j+i+1] elif (j+i+1, i) in self._s_coeff: elt = -self._s_coeff[j+i+1, i] else: elt = zero ret[x,y] = self.element_class(self, elt) # +i+1 for offset return Family(ret) def dimension(self): """ Return the dimension of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.dimension() 2 """ return self.basis().cardinality() def module(self, sparse=True): """ Return ``self`` as a free module. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}}) sage: L.module() Sparse vector space of dimension 3 over Rational Field """ return FreeModule(self.base_ring(), self.dimension(), sparse=sparse) @cached_method def zero(self): """ Return the element `0` in ``self``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.zero() 0 """ return self.element_class(self, self._M.zero()) def monomial(self, k): """ Return the monomial indexed by ``k``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.monomial('x') x """ return self.element_class(self, self._M.basis()[self._index_to_pos[k]]) def term(self, k, c=None): """ Return the term indexed by ``i`` with coefficient ``c``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.term('x', 4) 4*x """ if c is None: c = self.base_ring().one() else: c = self.base_ring()(c) return self.element_class(self, c * self._M.basis()[self._index_to_pos[k]]) def from_vector(self, v): """ Return an element of ``self`` from the vector ``v``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.from_vector([1, 2, -2]) x + 2*y - 2*z """ return self.element_class(self, self._M(v)) def some_elements(self): """ Return some elements of ``self``. EXAMPLES:: sage: L = lie_algebras.three_dimensional(QQ, 4, 1, -1, 2) sage: L.some_elements() [X, Y, Z, X + Y + Z] """ return list(self.basis()) + [self.sum(self.basis())] def change_ring(self, R): r""" Return a Lie algebra with identical structure coefficients over ``R``. INPUT: - ``R`` -- a ring EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(ZZ, {('x','y'): {'z':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): z} sage: LQQ = L.change_ring(QQ) sage: LQQ.structure_coefficients() Finite family {('x', 'y'): z} sage: LSR = LQQ.change_ring(SR) sage: LSR.structure_coefficients() Finite family {('x', 'y'): z} """ return LieAlgebraWithStructureCoefficients( R, self.structure_coefficients(), names=self.variable_names(), index_set=self.indices()) class Element(StructureCoefficientsElement): def _sorted_items_for_printing(self): """ Return a list of pairs ``(k, c)`` used in printing. .. WARNING:: The internal representation order is fixed, whereas this depends on ``"sorting_key"`` print option as it is used only for printing. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: elt = x + y/2 - z; elt x + 1/2*y - z sage: elt._sorted_items_for_printing() [('x', 1), ('y', 1/2), ('z', -1)] sage: key = {'x': 2, 'y': 1, 'z': 0} sage: L.print_options(sorting_key=key.__getitem__) sage: elt._sorted_items_for_printing() [('z', -1), ('y', 1/2), ('x', 1)] sage: elt -z + 1/2*y + x """ print_options = self.parent().print_options() pos_to_index = dict(enumerate(self.parent()._indices)) v = [(pos_to_index[k], c) for k, c in iteritems(self.value)] try: v.sort(key=lambda monomial_coeff: print_options['sorting_key'](monomial_coeff[0]), reverse=print_options['sorting_reverse']) except Exception: # Sorting the output is a plus, but if we can't, no big deal pass return v
def __init__(self, B, sigma=1, c=None, precision=None): r""" Construct a discrete Gaussian sampler over the lattice `Λ(B)` with parameter ``sigma`` and center `c`. INPUT: - ``B`` -- a basis for the lattice, one of the following: - an integer matrix, - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or - an object where ``matrix(B)`` succeeds, e.g. a list of vectors. - ``sigma`` -- Gaussian parameter `σ>0`. - ``c`` -- center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - ``precision`` -- bit precision `≥ 53`. EXAMPLE:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: n = 2; sigma = 3.0; m = 5000 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c 56.2162803067524 sage: l = [D() for _ in xrange(m)] sage: v = vector(ZZ, n, (-3,-3)) sage: l.count(v), ZZ(round(m*f(v)/c)) (39, 33) sage: target = vector(ZZ, n, (0,0)) sage: l.count(target), ZZ(round(m*f(target)/c)) (116, 89) sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) sage: D = DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0) over lattice with basis <BLANKLINE> [2 1 1] [1 2 1] [1 1 2] sage: D() (0, 1, -1) """ precision = DiscreteGaussianDistributionLatticeSampler.compute_precision( precision, sigma) self._RR = RealField(precision) self._sigma = self._RR(sigma) try: B = matrix(B) except ValueError: pass try: B = B.matrix() except AttributeError: pass self.B = B self._G = B.gram_schmidt()[0] try: c = vector(ZZ, B.ncols(), c) except TypeError: try: c = vector(QQ, B.ncols(), c) except TypeError: c = vector(RR, B.ncols(), c) self._c = c self.f = lambda x: exp(-(vector(ZZ, B.ncols(), x) - c).norm()**2 / (2 * self._sigma**2)) # deal with trivial case first, it is common if self._G == 1 and self._c == 0: self._c_in_lattice = True D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) self.D = tuple([D for _ in range(self.B.nrows())]) self.VS = FreeModule(ZZ, B.nrows()) return w = B.solve_left(c) if w in ZZ**B.nrows(): self._c_in_lattice = True D = [] for i in range(self.B.nrows()): sigma_ = self._sigma / self._G[i].norm() D.append( DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) self.D = tuple(D) self.VS = FreeModule(ZZ, B.nrows()) else: self._c_in_lattice = False
class FreeAlgebraQuotient(UniqueRepresentation, Algebra, object): @staticmethod def __classcall__(cls, A, mons, mats, names): """ Used to support unique representation. EXAMPLES:: sage: H = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] # indirect doctest sage: H1 = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] sage: H is H1 True """ new_mats = [] for M in mats: M = M.parent()(M) M.set_immutable() new_mats.append(M) return super(FreeAlgebraQuotient, cls).__classcall__(cls, A, tuple(mons), tuple(new_mats), tuple(names)) Element = FreeAlgebraQuotientElement def __init__(self, A, mons, mats, names): """ Returns a quotient algebra defined via the action of a free algebra A on a (finitely generated) free module. The input for the quotient algebra is a list of monomials (in the underlying monoid for A) which form a free basis for the module of A, and a list of matrices, which give the action of the free generators of A on this monomial basis. EXAMPLES: Quaternion algebra defined in terms of three generators:: sage: n = 3 sage: A = FreeAlgebra(QQ,n,'i') sage: F = A.monoid() sage: i, j, k = F.gens() sage: mons = [ F(1), i, j, k ] sage: M = MatrixSpace(QQ,4) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]), M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ] sage: H3.<i,j,k> = FreeAlgebraQuotient(A,mons,mats) sage: x = 1 + i + j + k sage: x 1 + i + j + k sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*k Same algebra defined in terms of two generators, with some penalty on already slow arithmetic. :: sage: n = 2 sage: A = FreeAlgebra(QQ,n,'x') sage: F = A.monoid() sage: i, j = F.gens() sage: mons = [ F(1), i, j, i*j ] sage: r = len(mons) sage: M = MatrixSpace(QQ,r) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]) ] sage: H2.<i,j> = A.quotient(mons,mats) sage: k = i*j sage: x = 1 + i + j + k sage: x 1 + i + j + i*j sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*i*j TEST:: sage: TestSuite(H2).run() """ if not is_FreeAlgebra(A): raise TypeError("Argument A must be an algebra.") R = A.base_ring() # if not R.is_field(): # TODO: why? # raise TypeError, "Base ring of argument A must be a field." n = A.ngens() assert n == len(mats) self.__free_algebra = A self.__ngens = n self.__dim = len(mons) self.__module = FreeModule(R, self.__dim) self.__matrix_action = mats self.__monomial_basis = mons # elements of free monoid Algebra.__init__(self, R, names, normalize=True) def __eq__(self, right): """ Return True if all defining properties of self and right match up. EXAMPLES:: sage: HQ = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] sage: HZ = sage.algebras.free_algebra_quotient.hamilton_quatalg(ZZ)[0] sage: HQ == HQ True sage: HQ == HZ False sage: HZ == QQ False """ return isinstance(right, FreeAlgebraQuotient) and \ self.ngens() == right.ngens() and \ self.rank() == right.rank() and \ self.module() == right.module() and \ self.matrix_action() == right.matrix_action() and \ self.monomial_basis() == right.monomial_basis() def _element_constructor_(self, x): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._element_constructor_(i) is i True sage: a = H._element_constructor_(1); a 1 sage: a in H True sage: a = H._element_constructor_([1,2,3,4]); a 1 + 2*i + 3*j + 4*k """ if isinstance(x, FreeAlgebraQuotientElement) and x.parent() is self: return x return self.element_class(self, x) def _coerce_map_from_(self, S): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._coerce_map_from_(H) True sage: H._coerce_map_from_(QQ) True sage: H._coerce_map_from_(GF(7)) False """ return S == self or self.__free_algebra.has_coerce_map_from(S) def _repr_(self): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._repr_() "Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Rational Field" """ R = self.base_ring() n = self.__ngens r = self.__module.dimension() x = self.variable_names() return "Free algebra quotient on %s generators %s and dimension %s over %s" % ( n, x, r, R) def gen(self, i): """ The i-th generator of the algebra. EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H.gen(0) i sage: H.gen(2) k An IndexError is raised if an invalid generator is requested:: sage: H.gen(3) Traceback (most recent call last): ... IndexError: Argument i (= 3) must be between 0 and 2. Negative indexing into the generators is not supported:: sage: H.gen(-1) Traceback (most recent call last): ... IndexError: Argument i (= -1) must be between 0 and 2. """ n = self.__ngens if i < 0 or not i < n: raise IndexError("Argument i (= %s) must be between 0 and %s." % (i, n - 1)) R = self.base_ring() F = self.__free_algebra.monoid() n = self.__ngens return self.element_class(self, {F.gen(i): R(1)}) def ngens(self): """ The number of generators of the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].ngens() 3 """ return self.__ngens def dimension(self): """ The rank of the algebra (as a free module). EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].dimension() 4 """ return self.__dim def matrix_action(self): """ EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].matrix_action() ( [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1] [-1 0 0 0] [ 0 0 0 1] [ 0 0 -1 0] [ 0 0 0 -1] [-1 0 0 0] [ 0 1 0 0] [ 0 0 1 0], [ 0 -1 0 0], [-1 0 0 0] ) """ return self.__matrix_action def monomial_basis(self): """ EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monomial_basis() (1, i0, i1, i2) """ return self.__monomial_basis def rank(self): """ The rank of the algebra (as a free module). EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].rank() 4 """ return self.__dim def module(self): """ The free module of the algebra. sage: H = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0]; H Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Rational Field sage: H.module() Vector space of dimension 4 over Rational Field """ return self.__module def monoid(self): """ The free monoid of generators of the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monoid() Free monoid on 3 generators (i0, i1, i2) """ return self.__free_algebra.monoid() def monomial_basis(self): """ The free monoid of generators of the algebra as elements of a free monoid. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monomial_basis() (1, i0, i1, i2) """ return self.__monomial_basis def free_algebra(self): """ The free algebra generating the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].free_algebra() Free Algebra on 3 generators (i0, i1, i2) over Rational Field """ return self.__free_algebra
class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. """ def __init__(self, R, form, names=None): """ Initialize ``self``. TESTS:: sage: m = matrix([[-2,3],[3,4]]) sage: J = JordanAlgebra(m) sage: TestSuite(J).run() """ self._form = form self._M = FreeModule(R, form.ncols()) cat = MagmaticAlgebras( R).Commutative().Unital().FiniteDimensional().WithBasis() self._no_generic_basering_coercion = True # Remove once 16492 is fixed Parent.__init__(self, base=R, names=names, category=cat) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[-2,3],[3,4]]) sage: JordanAlgebra(m) Jordan algebra over Integer Ring given by the symmetric bilinear form: [-2 3] [ 3 4] """ return "Jordan algebra over {} given by the symmetric bilinear" \ " form:\n{}".format(self.base_ring(), self._form) def _element_constructor_(self, *args): """ Construct an element of ``self`` from ``s``. Here ``s`` can be a pair of an element of `R` and an element of `M`, or an element of `R`, or an element of `M`, or an element of a(nother) Jordan algebra given by a symmetric bilinear form. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0, 0) sage: J((-4, (2, 5))) -4 + (2, 5) sage: J((-4, (ZZ^2)((2, 5)))) -4 + (2, 5) sage: J(2, (-2, 3)) 2 + (-2, 3) sage: J(2, (ZZ^2)((-2, 3))) 2 + (-2, 3) sage: J(-1, 1, 0) -1 + (1, 0) sage: J((ZZ^2)((1, 3))) 0 + (1, 3) sage: m = matrix([[2]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0) sage: J((-4, (2,))) -4 + (2) sage: J(2, (-2,)) 2 + (-2) sage: J(-1, 1) -1 + (1) sage: J((ZZ^1)((3,))) 0 + (3) sage: m = Matrix(QQ, []) sage: J = JordanAlgebra(m) sage: J(2) 2 + () sage: J((-4, ())) -4 + () sage: J(2, ()) 2 + () sage: J(-1) -1 + () sage: J((ZZ^0)(())) 0 + () """ R = self.base_ring() if len(args) == 1: s = args[0] if isinstance(s, JordanAlgebraSymmetricBilinear.Element): if s.parent() is self: return s return self.element_class(self, R(s._s), self._M(s._v)) if isinstance(s, (list, tuple)): if len(s) != 2: raise ValueError("must be length 2") return self.element_class(self, R(s[0]), self._M(s[1])) if s in self._M: return self.element_class(self, R.zero(), self._M(s)) return self.element_class(self, R(s), self._M.zero()) if len(args) == 2 and (isinstance(args[1], (list, tuple)) or args[1] in self._M): return self.element_class(self, R(args[0]), self._M(args[1])) if len(args) == self._form.ncols() + 1: return self.element_class(self, R(args[0]), self._M(args[1:])) raise ValueError("unable to construct an element from the given data") @cached_method def basis(self): """ Return a basis of ``self``. The basis returned begins with the unity of `R` and continues with the standard basis of `M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ R = self.base_ring() ret = (self.element_class(self, R.one(), self._M.zero()), ) ret += tuple( map(lambda x: self.element_class(self, R.zero(), x), self._M.basis())) return Family(ret) algebra_generators = basis def gens(self): """ Return the generators of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ return tuple(self.algebra_generators()) @cached_method def zero(self): """ Return the element 0. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.zero() 0 + (0, 0) """ return self.element_class(self, self.base_ring().zero(), self._M.zero()) @cached_method def one(self): """ Return the element 1 if it exists. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.one() 1 + (0, 0) """ return self.element_class(self, self.base_ring().one(), self._M.zero()) class Element(AlgebraElement): """ An element of a Jordan algebra defined by a symmetric bilinear form. """ def __init__(self, parent, s, v): """ Initialize ``self``. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: TestSuite(a + 2*b - c).run() """ self._s = s self._v = v AlgebraElement.__init__(self, parent) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + 2*b - c 1 + (2, -1) """ return "{} + {}".format(self._s, self._v) def _latex_(self): r""" Return a latex representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: latex(a + 2*b - c) 1 + \left(2,\,-1\right) """ from sage.misc.latex import latex return "{} + {}".format(latex(self._s), latex(self._v)) def __nonzero__(self): """ Return if ``self`` is non-zero. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 1.__nonzero__() True sage: b.__nonzero__() True sage: (a + 2*b - c).__nonzero__() True """ return self._s.__nonzero__() or self._v.__nonzero__() def __eq__(self, other): """ Check equality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x == J((4, (-1, 3))) True sage: a == x False sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c == x False """ if not isinstance(other, JordanAlgebraSymmetricBilinear.Element): return False if other.parent() != self.parent(): return False return self._s == other._s and self._v == other._v def __ne__(self, other): """ Check inequality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x != J((4, (-1, 3))) False sage: a != x True sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c != x True """ return not self == other def _add_(self, other): """ Add ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + b 1 + (1, 0) sage: b + c 0 + (1, 1) """ return self.__class__(self.parent(), self._s + other._s, self._v + other._v) def _neg_(self): """ Negate ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: -(a + b - 2*c) -1 + (-1, 2) """ return self.__class__(self.parent(), -self._s, -self._v) def _sub_(self, other): """ Subtract ``other`` from ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a - b 1 + (-1, 0) sage: b - c 0 + (1, -1) """ return self.__class__(self.parent(), self._s - other._s, self._v - other._v) def _mul_(self, other): """ Multiply ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 12 + (6, 2) sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 21 + (6, 2) """ P = self.parent() return self.__class__( P, self._s * other._s + (self._v * P._form * other._v.column())[0], other._s * self._v + self._s * other._v) def _lmul_(self, other): """ Multiply ``self`` by the scalar ``other`` on the left. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (a + b - c) * 2 2 + (2, -2) """ return self.__class__(self.parent(), self._s * other, self._v * other) def _rmul_(self, other): """ Multiply ``self`` with the scalar ``other`` by the right action. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 2 * (a + b - c) 2 + (2, -2) """ return self.__class__(self.parent(), other * self._s, other * self._v) def monomial_coefficients(self, copy=True): """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding coefficients. INPUT: - ``copy`` -- ignored EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: elt = a + 2*b - c sage: elt.monomial_coefficients() {0: 1, 1: 2, 2: -1} """ d = {0: self._s} for i, c in enumerate(self._v): d[i + 1] = c return d def trace(self): r""" Return the trace of ``self``. The trace of an element `\alpha + x \in M^*` is given by `t(\alpha + x) = 2 \alpha`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.trace() 8 """ return 2 * self._s def norm(self): r""" Return the norm of ``self``. The norm of an element `\alpha + x \in M^*` is given by `n(\alpha + x) = \alpha^2 - (x, x)`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c; x 4 + (-1, 3) sage: x.norm() 13 """ return self._s * self._s - (self._v * self.parent()._form * self._v.column())[0] def bar(self): r""" Return the result of the bar involution of ``self``. The bar involution `\bar{\cdot}` is the `R`-linear endomorphism of `M^*` defined by `\bar{1} = 1` and `\bar{x} = -x` for `x \in M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.bar() 4 + (1, -3) We check that it is an algebra morphism:: sage: y = 2*a + 2*b - c sage: x.bar() * y.bar() == (x*y).bar() True """ return self.__class__(self.parent(), self._s, -self._v)
def __init__(self, A, mons, mats, names): """ Returns a quotient algebra defined via the action of a free algebra A on a (finitely generated) free module. The input for the quotient algebra is a list of monomials (in the underlying monoid for A) which form a free basis for the module of A, and a list of matrices, which give the action of the free generators of A on this monomial basis. EXAMPLES: Quaternion algebra defined in terms of three generators:: sage: n = 3 sage: A = FreeAlgebra(QQ,n,'i') sage: F = A.monoid() sage: i, j, k = F.gens() sage: mons = [ F(1), i, j, k ] sage: M = MatrixSpace(QQ,4) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]), M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ] sage: H3.<i,j,k> = FreeAlgebraQuotient(A,mons,mats) sage: x = 1 + i + j + k sage: x 1 + i + j + k sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*k Same algebra defined in terms of two generators, with some penalty on already slow arithmetic. :: sage: n = 2 sage: A = FreeAlgebra(QQ,n,'x') sage: F = A.monoid() sage: i, j = F.gens() sage: mons = [ F(1), i, j, i*j ] sage: r = len(mons) sage: M = MatrixSpace(QQ,r) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]) ] sage: H2.<i,j> = A.quotient(mons,mats) sage: k = i*j sage: x = 1 + i + j + k sage: x 1 + i + j + i*j sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*i*j TEST:: sage: TestSuite(H2).run() """ if not is_FreeAlgebra(A): raise TypeError("Argument A must be an algebra.") R = A.base_ring() # if not R.is_field(): # TODO: why? # raise TypeError, "Base ring of argument A must be a field." n = A.ngens() assert n == len(mats) self.__free_algebra = A self.__ngens = n self.__dim = len(mons) self.__module = FreeModule(R, self.__dim) self.__matrix_action = mats self.__monomial_basis = mons # elements of free monoid Algebra.__init__(self, R, names, normalize=True)
def __init__(self, dim): from sage.modules.free_module import FreeModule self._free_module = FreeModule(ZZ, int(dim))
class FreeAlgebraQuotient(UniqueRepresentation, Algebra, object): @staticmethod def __classcall__(cls, A, mons, mats, names): """ Used to support unique representation. EXAMPLES:: sage: H = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] # indirect doctest sage: H1 = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] sage: H is H1 True """ new_mats = [] for M in mats: M = M.parent()(M) M.set_immutable() new_mats.append(M) return super(FreeAlgebraQuotient, cls).__classcall__(cls, A, tuple(mons), tuple(new_mats), tuple(names)) Element = FreeAlgebraQuotientElement def __init__(self, A, mons, mats, names): """ Returns a quotient algebra defined via the action of a free algebra A on a (finitely generated) free module. The input for the quotient algebra is a list of monomials (in the underlying monoid for A) which form a free basis for the module of A, and a list of matrices, which give the action of the free generators of A on this monomial basis. EXAMPLES: Quaternion algebra defined in terms of three generators:: sage: n = 3 sage: A = FreeAlgebra(QQ,n,'i') sage: F = A.monoid() sage: i, j, k = F.gens() sage: mons = [ F(1), i, j, k ] sage: M = MatrixSpace(QQ,4) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]), M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ] sage: H3.<i,j,k> = FreeAlgebraQuotient(A,mons,mats) sage: x = 1 + i + j + k sage: x 1 + i + j + k sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*k Same algebra defined in terms of two generators, with some penalty on already slow arithmetic. :: sage: n = 2 sage: A = FreeAlgebra(QQ,n,'x') sage: F = A.monoid() sage: i, j = F.gens() sage: mons = [ F(1), i, j, i*j ] sage: r = len(mons) sage: M = MatrixSpace(QQ,r) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]) ] sage: H2.<i,j> = A.quotient(mons,mats) sage: k = i*j sage: x = 1 + i + j + k sage: x 1 + i + j + i*j sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*i*j TEST:: sage: TestSuite(H2).run() """ if not is_FreeAlgebra(A): raise TypeError("Argument A must be an algebra.") R = A.base_ring() # if not R.is_field(): # TODO: why? # raise TypeError, "Base ring of argument A must be a field." n = A.ngens() assert n == len(mats) self.__free_algebra = A self.__ngens = n self.__dim = len(mons) self.__module = FreeModule(R,self.__dim) self.__matrix_action = mats self.__monomial_basis = mons # elements of free monoid Algebra.__init__(self, R, names, normalize=True) def __eq__(self, right): """ Return True if all defining properties of self and right match up. EXAMPLES:: sage: HQ = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0] sage: HZ = sage.algebras.free_algebra_quotient.hamilton_quatalg(ZZ)[0] sage: HQ == HQ True sage: HQ == HZ False sage: HZ == QQ False """ return isinstance(right, FreeAlgebraQuotient) and \ self.ngens() == right.ngens() and \ self.rank() == right.rank() and \ self.module() == right.module() and \ self.matrix_action() == right.matrix_action() and \ self.monomial_basis() == right.monomial_basis() def _element_constructor_(self, x): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._element_constructor_(i) is i True sage: a = H._element_constructor_(1); a 1 sage: a in H True sage: a = H._element_constructor_([1,2,3,4]); a 1 + 2*i + 3*j + 4*k """ if isinstance(x, FreeAlgebraQuotientElement) and x.parent() is self: return x return self.element_class(self,x) def _coerce_map_from_(self,S): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._coerce_map_from_(H) True sage: H._coerce_map_from_(QQ) True sage: H._coerce_map_from_(GF(7)) False """ return S==self or self.__free_algebra.has_coerce_map_from(S) def _repr_(self): """ EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H._repr_() "Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Rational Field" """ R = self.base_ring() n = self.__ngens r = self.__module.dimension() x = self.variable_names() return "Free algebra quotient on %s generators %s and dimension %s over %s"%(n,x,r,R) def gen(self, i): """ The i-th generator of the algebra. EXAMPLES:: sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ) sage: H.gen(0) i sage: H.gen(2) k An IndexError is raised if an invalid generator is requested:: sage: H.gen(3) Traceback (most recent call last): ... IndexError: Argument i (= 3) must be between 0 and 2. Negative indexing into the generators is not supported:: sage: H.gen(-1) Traceback (most recent call last): ... IndexError: Argument i (= -1) must be between 0 and 2. """ n = self.__ngens if i < 0 or not i < n: raise IndexError("Argument i (= %s) must be between 0 and %s."%(i, n-1)) R = self.base_ring() F = self.__free_algebra.monoid() n = self.__ngens return self.element_class(self,{F.gen(i):R(1)}) def ngens(self): """ The number of generators of the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].ngens() 3 """ return self.__ngens def dimension(self): """ The rank of the algebra (as a free module). EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].dimension() 4 """ return self.__dim def matrix_action(self): """ EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].matrix_action() ( [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1] [-1 0 0 0] [ 0 0 0 1] [ 0 0 -1 0] [ 0 0 0 -1] [-1 0 0 0] [ 0 1 0 0] [ 0 0 1 0], [ 0 -1 0 0], [-1 0 0 0] ) """ return self.__matrix_action def monomial_basis(self): """ EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monomial_basis() (1, i0, i1, i2) """ return self.__monomial_basis def rank(self): """ The rank of the algebra (as a free module). EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].rank() 4 """ return self.__dim def module(self): """ The free module of the algebra. sage: H = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0]; H Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Rational Field sage: H.module() Vector space of dimension 4 over Rational Field """ return self.__module def monoid(self): """ The free monoid of generators of the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monoid() Free monoid on 3 generators (i0, i1, i2) """ return self.__free_algebra.monoid() def monomial_basis(self): """ The free monoid of generators of the algebra as elements of a free monoid. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].monomial_basis() (1, i0, i1, i2) """ return self.__monomial_basis def free_algebra(self): """ The free algebra generating the algebra. EXAMPLES:: sage: sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0].free_algebra() Free Algebra on 3 generators (i0, i1, i2) over Rational Field """ return self.__free_algebra
def herm_modform_space(D, HermWeight, B_cF=10, parallelization=None, reduction_method_flags=-1): """ This calculates the vectorspace of Fourier expansions to Hermitian modular forms of weight `HermWeight` over \Gamma, where \Gamma = \Sp_2(\curlO) and \curlO is the maximal order of \QQ(\sqrt{D}). Each Fourier coefficient vector is indexed up to a precision \curlF which is given by `B_cF` such that for every [a,b,c] \in \curlF \subset \Lambda, we have 0 \le a,c \le B_cF. The function `herm_modform_indexset()` returns reduced matrices of that precision index set \curlF. """ if HermWeight % 3 != 0: raise TypeError, "the modulform is trivial/zero if HermWeight is not divisible by 3" # Transform these into native Python objects. We don't want to have # any Sage objects (e.g. Integer) here so that the cache index stays # unique. D = int(D) HermWeight = int(HermWeight) B_cF = int(B_cF) reduction_method_flags = int(reduction_method_flags) calc = C.Calc() calc.init(D = D, HermWeight = HermWeight, B_cF=B_cF) calc.calcReducedCurlF() reducedCurlFSize = calc.matrixColumnCount # Calculate the dimension of Hermitian modular form space. dim = herm_modform_space_dim(D=D, HermWeight=HermWeight) cacheIdx = (D, HermWeight, B_cF) if reduction_method_flags != -1: cacheIdx += (reduction_method_flags,) try: herm_modform_fe_expannsion, calc, curlS_denoms, pending_tasks = hermModformSpaceCache[cacheIdx] if not isinstance(calc, C.Calc): raise TypeError print "Resuming from %s" % hermModformSpaceCache._filename_for_key(cacheIdx) except (TypeError, ValueError, KeyError, EOFError): # old format or not cached or cache incomplete herm_modform_fe_expannsion = FreeModule(QQ, reducedCurlFSize) curlS_denoms = set() # the denominators of the visited matrices S pending_tasks = () current_dimension = herm_modform_fe_expannsion.dimension() verbose("current dimension: %i, wanted: %i" % (herm_modform_fe_expannsion.dimension(), dim)) if dim == 0: print "dim == 0 -> exit" return def task_iter_func(): # Iterate S \in Mat_2^T(\curlO), S > 0. while True: # Get the next S. # If calc.curlS is not empty, this is because we have recovered from a resume. if len(calc.curlS) == 0: S = calc.getNextS() else: assert len(calc.curlS) == 1 S = calc.curlS[0] l = S.det() l = toInt(l) curlS_denoms.add(l) verbose("trying S={0}, det={1}".format(S, l)) if reduction_method_flags & Method_Elliptic_reduction: yield CalcTask( func=modform_restriction_info, calc=calc, kwargs={"S":S, "l":l}) if reduction_method_flags & Method_EllipticCusp_reduction: precLimit = calcPrecisionDimension(B_cF=B_cF, S=S) yield CalcTask( func=modform_cusp_info, calc=calc, kwargs={"S":S, "l":l, "precLimit": precLimit}) calc.curlS_clearMatrices() # In the C++ internal curlS, clear previous matrices. task_iter = task_iter_func() if parallelization: parallelization.task_iter = task_iter if parallelization and pending_tasks: for func, name in pending_tasks: parallelization.exec_task(func=func, name=name) step_counter = 0 while True: if parallelization: new_task_count = 0 spaces = [] for task, exc, newspace in parallelization.get_all_ready_results(): if exc: raise exc if newspace is None: verbose("no data from %r" % task) continue if newspace.dimension() == reducedCurlFSize: verbose("no information gain from %r" % task) continue spacecomment = task assert newspace.dimension() >= dim, "%r, %r" % (task, newspace) if newspace.dimension() < herm_modform_fe_expannsion.dimension(): # Swap newspace with herm_modform_fe_expannsion. herm_modform_fe_expannsion, newspace = newspace, herm_modform_fe_expannsion current_dimension = herm_modform_fe_expannsion.dimension() if current_dimension == dim: if not isinstance(task, IntersectSpacesTask): verbose("warning: we expected IntersectSpacesTask for final dim but got: %r" % task) verbose("new dimension: %i, wanted: %i" % (current_dimension, dim)) if current_dimension <= 20: pprint(herm_modform_fe_expannsion.basis()) spacecomment = "<old base space>" spaces += [(spacecomment, newspace)] if spaces: parallelization.exec_task(IntersectSpacesTask(herm_modform_fe_expannsion, spaces)) new_task_count += 1 new_task_count += parallelization.maybe_queue_tasks() time.sleep(0.1) else: # no parallelization new_task_count = 1 if pending_tasks: # from some resuming task,_ = pending_tasks[0] pending_tasks = pending_tasks[1:] else: task = next(task_iter) newspace = task() if newspace is None: verbose("no data from %r" % task) if newspace is not None and newspace.dimension() == reducedCurlFSize: verbose("no information gain from %r" % task) newspace = None if newspace is not None: new_task_count += 1 spacecomment = task herm_modform_fe_expannsion_new = IntersectSpacesTask( herm_modform_fe_expannsion, [(spacecomment, newspace)])() if herm_modform_fe_expannsion_new is not None: herm_modform_fe_expannsion = herm_modform_fe_expannsion_new current_dimension = herm_modform_fe_expannsion.dimension() verbose("new dimension: %i, wanted: %i" % (current_dimension, dim)) if new_task_count > 0: step_counter += 1 if step_counter % 10 == 0: verbose("save state after %i steps to %s" % (step_counter, os.path.basename(hermModformSpaceCache._filename_for_key(cacheIdx)))) if parallelization: pending_tasks = parallelization.get_pending_tasks() hermModformSpaceCache[cacheIdx] = (herm_modform_fe_expannsion, calc, curlS_denoms, pending_tasks) if current_dimension == dim: verbose("finished!") break # Test for some other S with other not-yet-seen denominator. check_herm_modform_space( calc, herm_modform_space=herm_modform_fe_expannsion, used_curlS_denoms=curlS_denoms ) return herm_modform_fe_expannsion
def is_finite(self): r""" Check whether the group is finite. A group is finite if and only if it is conjugate to a (finite) subgroup of O(2). This is actually also true in higher dimensions. EXAMPLES:: sage: from flatsurf.geometry.finitely_generated_matrix_group import FinitelyGenerated2x2MatrixGroup sage: G = FinitelyGenerated2x2MatrixGroup([identity_matrix(2)]) sage: G.is_finite() True sage: t = matrix(2, [2,1,1,1]) sage: m1 = matrix([[0,1],[-1,0]]) sage: m2 = matrix([[1,-1],[1,0]]) sage: FinitelyGenerated2x2MatrixGroup([m1]).is_finite() True sage: FinitelyGenerated2x2MatrixGroup([t*m1*~t]).is_finite() True sage: FinitelyGenerated2x2MatrixGroup([m2]).is_finite() True sage: FinitelyGenerated2x2MatrixGroup([m1,m2]).is_finite() False sage: FinitelyGenerated2x2MatrixGroup([t*m1*~t,t*m2*~t]).is_finite() False sage: from flatsurf.geometry.polygon import number_field_elements_from_algebraics sage: c5 = QQbar.zeta(5).real() sage: s5 = QQbar.zeta(5).imag() sage: K, (c5,s5) = number_field_elements_from_algebraics([c5,s5]) sage: r = matrix(K, 2, [c5,-s5,s5,c5]) sage: FinitelyGenerated2x2MatrixGroup([m1,r]).is_finite() True sage: FinitelyGenerated2x2MatrixGroup([t*m1*~t,t*r*~t]).is_finite() True sage: FinitelyGenerated2x2MatrixGroup([m2,r]).is_finite() False sage: FinitelyGenerated2x2MatrixGroup([t*m2*~t, t*r*~t]).is_finite() False """ # determinant and trace tests # (the code actually check that each generator is of finite order) for m in self._generators: if (m.det() != 1 and m.det() != -1) or \ m.trace().abs() > 2 or \ (m.trace().abs() == 2 and (m[0,1] or m[1,0])): return False gens = [g for g in self._generators if not g.is_scalar()] if len(gens) <= 1: return True # now we try to find a non-trivial invariant quadratic form from sage.modules.free_module import FreeModule V = FreeModule(self._matrix_space.base_ring(), 3) for g in gens: V = V.intersection(invariant_quadratic_forms(g)) if not contains_definite_form(V): return False return True
class DegreeGrading( Grading_abstract ) : r""" This class implements a monomial grading for a polynomial ring `R[x_1, .., x_n]`. """ def __init__( self, degrees ) : r""" INPUT: - ``degrees`` -- A list or tuple of `n` positive integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading((1,2)) sage: g = DegreeGrading([5,2,3]) """ self.__degrees = tuple(degrees) self.__module = FreeModule(ZZ, len(degrees)) self.__module_basis = self.__module.basis() def ngens(self) : r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).ngens() 2 sage: DegreeGrading([5,2,3]).ngens() 3 """ return len(self.__degrees) def gen(self, i) : r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]).gen(0) 5 sage: DegreeGrading([5,2,3]).gen(3) Traceback (most recent call last): ... ValueError: Generator 3 does not exist. """ if i < len(self.__degrees) : return self.__degrees[i] raise ValueError("Generator %s does not exist." % (i,)) def gens(self) : r""" The gradings of the generators of the polynomial ring. OUTPUT: A tuple of integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).gens() (1, 2) sage: DegreeGrading([5,2,3]).gens() (5, 2, 3) """ return self.__degrees def index(self, x) : r""" The grading value of `x` with respect to this grading. INPUT: - `x` -- A tuple of length `n`. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.index((2,4,5)) 33 sage: g.index((2,3)) Traceback (most recent call last): ... ValueError: Tuple must have length 3. """ ## TODO: We shouldn't need this. #if len(x) == 0 : return infinity if len(x) != len(self.__degrees) : raise ValueError( "Tuple must have length %s." % (len(self.__degrees),)) return sum( map(mul, x, self.__degrees) ) def basis(self, index, vars = None) : r""" All monomials that are have given grading index involving only the given variables. INPUT: - ``index`` -- A grading index. - ``vars`` -- A list of integers from `0` to `n - 1` or ``None`` (default: ``None``); If ``None`` all variables will be considered. OUTPUT: A list of elements in `\Z^n`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.basis(2) [(0, 1, 0)] sage: g.basis(10, vars = [0]) [(2, 0, 0)] sage: g.basis(20, vars = [1,2]) [(0, 10, 0), (0, 7, 2), (0, 4, 4), (0, 1, 6)] sage: g.basis(-1) [] sage: g.basis(0) [(0, 0, 0)] """ if index < 0 : return [] elif index == 0 : return [self.__module.zero_vector()] if vars == None : vars = [m for (m,d) in enumerate(self.__degrees) if d <= index] if len(vars) == 0 : return [] res = [ self.__module_basis[vars[0]] + m for m in self.basis(index - self.__degrees[vars[0]], vars) ] res += self.basis(index, vars[1:]) return res def subgrading(self, gens) : r""" The grading of same type for the ring with only the variables given by ``gens``. INPUT: - ``gens`` - A list of integers. OUTPUT: An instance of :class:~`.DegreeGrading`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.subgrading([2]) Degree grading (3,) sage: g.subgrading([]) Degree grading () """ return DegreeGrading([self.__degrees[i] for i in gens]) def __contains__(self, x) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: 5 in g True sage: "t" in g False """ return isinstance(x, (int, Integer)) def __cmp__(self, other) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g == DegreeGrading([5,2,3]) True sage: g == DegreeGrading((2,4)) False """ c = cmp(type(self), type(other)) if c == 0 : c = cmp(self.__degrees, other.__degrees) return c def __hash__(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: hash( DegreeGrading([5,2,3]) ) -1302562269 # 32-bit 7573306103633312291 # 64-bit """ return hash(self.__degrees) def _repr_(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]) Degree grading (5, 2, 3) """ return "Degree grading %s" % str(self.__degrees) def _latex_(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: latex( DegreeGrading([5,2,3]) ) \text{Degree grading } \left(5, 2, 3\right) """ return r"\text{Degree grading }" + latex(self.__degrees)
class Lattice_class (SageObject): """ A class representing a lattice $L$. NOTE We build this class as a sort of wrapper around \code{FreeModule(ZZ,n)}. This is reflecting the fact that a lattice is not a free module, but a pair consisting of a free module and a scalar product. In addition, we avoid in this way to add new items to the Sage category system, which we better leave to the specialists. EXAMPLES sage: L = Lattice_class( [2,1,2]); L The ZZ-lattice (ZZ^2, x^tGy), where G = [2 1] [1 2] sage: L.is_even() True sage: L.gram_matrix() [2 1] [1 2] sage: L.dual_vectors() {(2/3, -1/3), (0, 0), (4/3, -2/3)} sage: L.representatives(2) {} sage: L.representatives(1) {(2/3, -1/3), (4/3, -2/3)} sage: L.values() {0: {(0, 0)}, 1: {(2/3, -1/3), (4/3, -2/3)}} sage: L.det() 3 sage: g = lambda n: [1] if 1 == n else [1] + (n-1)*[0] + g(n-1) sage: Z8 = Lattice_class( g(8)) sage: a,A8 = Z8.ev() sage: M8 = A8.fqm() sage: M8.jordan_decomposition().genus_symbol() '2^2' """ def __init__( self, q): """ We initialize by a list of integers $[a_1,...,a_N]$. The lattice self is then $L = (L,\beta) = (G^{-1}\ZZ^n/\ZZ^n, G[x])$, where $G$ is the symmetric matrix $G=[a_1,...,a_n;*,a_{n+1},....;..;* ... * a_N]$, i.e.~the $a_j$ denote the elements above the diagonal of $G$. """ self.__form = q # We compute the rank N = len(q) assert is_square( 1+8*N) n = Integer( (-1+isqrt(1+8*N))/2) self.__rank = n # We set up the Gram matrix self.__G = matrix( IntegerRing(), n, n) i = j = 0 for a in q: self.__G[i,j] = self.__G[j,i] = Integer(a) if j < n-1: j += 1 else: i += 1 j = i # We compute the level Gi = self.__G**-1 self.__Gi = Gi a = lcm( map( lambda x: x.denominator(), Gi.list())) I = Gi.diagonal() b = lcm( map( lambda x: (x/2).denominator(), I)) self.__level = lcm( a, b) # We define the undelying module and the ambient space self.__module = FreeModule( IntegerRing(), n) self.__space = self.__module.ambient_vector_space() # We compute a shadow vector self.__shadow_vector = self.__space([(a%2)/2 for a in self.__G.diagonal()])*Gi # We define a basis M = Matrix( IntegerRing(), n, n, 1) self.__basis = self.__module.basis() # We prepare a cache self.__dual_vectors = None self.__values = None self.__chi = {} def _latex_( self): return 'The ZZ-lattice $(ZZ^{%d}, x\'Gy)$, where $G = %s$' % (self.__rank, latex(self.__G)) def _repr_( self): return 'The ZZ-lattice (ZZ^%d, x^tGy), where G = \n%r' % (self.__rank, self.__G) def rank( self): return self.__rank def level( self): """ Return the smallest integer $l$ such that $lG[x]/2 \in \Z$ for all $x$ in $L^*$. """ return self.__level def basis( self): return self.__basis def module( self): """ Return the underlying module. """ return self.__module def hom( self, im_gens, codomain=None, check=True): # return self.module().hom( im_gens, codomain = codomain, check = check) if not codomain: raise NotImplementedError() A = matrix( im_gens) if codomain and True == check: assert self.gram_matrix() == A*codomain.gram_matrix()*A.transpose() return Embedding( self, matrix( im_gens), codomain) def space( self): """ Return the ambient vector space. """ return self.__space def is_positive( self): pass def is_even( self): I = self.gram_matrix().diagonal() for a in I: if is_odd(a): return False return True def is_maximal( self): pass def shadow_level( self): """ Return the level of $L_ev$, where $L_ev$ is the kernel of the map $x\mapsto e(G[x]/2)$. REMARK Lemma: If $L$ is odd, if $s$ is a shadow vector, and if $N$ denotes the denominator of $G[s]/2$, then the level of $L_ev$ equals the lcm of the order of $s$, $N$ and the level of $L$. (For the proof: the requested level is the smallest integer $l$ such that $lG[x]/2$ is integral for all $x$ in $L^*$ and $x$ in $s+L^*$ since $L_ev^* = L^* \cup s+L^*$. This $l$ is the smallest integer such that $lG[s]/2$, $lG[x]/2$ and $ls^tGx$ are integral for all $x$ in $L^*$.) """ if self.is_even(): return self.level() s = self.a_shadow_vector() N = self.beta(s).denominator() h = lcm( [x.denominator() for x in s]) return lcm( [h, N, self.level()]) def gram_matrix( self): return self.__G def beta(self, x, y = None): if None == y: return (x*self.gram_matrix()*x)/2 else: return x*self.gram_matrix()*y def det( self): return self.gram_matrix().det() def dual_vectors( self): """ Return a set of representatives for $L^#/L$. """ D,U,V = self.gram_matrix().smith_form() # hence D = U * self.gram_matrix() * V if None == self.__dual_vectors: W = V*D**-1 S = self.space() self.__dual_vectors = [ W*S(v) for v in mrange( D.diagonal())] return self.__dual_vectors def is_in_dual( self, y): """ Checks whether $y$ is in the dual of self. """ return self._is_int_vec( self.gram_matrix()*y) def a_shadow_vector( self): """ Return a vector in the shadow $L^\bullet$ of $L$. REMARK As shadow vector one can take the diagnal of the Gram matrix reduced modulo 2 and multiplied by $1/2G^{-1}$. Note that this vector $s$ is arbitrary (in particular, it does not satisfy in general $2s \in L$). """ return self.__shadow_vector def shadow_vectors( self): """ Return a list of representatives for the vectors in $L^\bullet/L$. """ if self.is_even(): return self.dual_vectors() R = self.dual_vectors() s = self.a_shadow_vector() return [s+r for r in R] def shadow_vectors_of_order_2( self): """ Return the list of representatives for those vectors $x$ in $L^\bullet/L$ such that $2x=0$. """ R = self.shadow_vectors() return [r for r in R if self._is_int_vec( 2*r)] def is_in_shadow( self, r): """ Checks whether $r$ is a shadow vector. """ c = self.a_shadow_vector() return self.is_in_dual( r - c) def o_invariant( self): """ Return $0$ if $L$ is even, otherwise the parity of $G[2*s]$, where $s$ is a shadow vector such that $2s$ is in $L$ (so that $(-1)^parity = e(G[2*s]/2)$. REMARK The o_invariant equals the parity of $n_2$, where $n_2$ is as in Lemma 3.1 of [Joli-I]. """ V = self.shadow_vectors_of_order_2() s = V[0] return Integer(4*(s*self.gram_matrix()*s))%2 def values( self): """ Return a dictionary N \mapsto set of representatives r in $L^\bullet/L$ which satisfy $\beta(r) \equiv n/l \bmod \ZZ$, where $l$ is the shadow_level (i.e. the level of $L_ev$). REMARK $\beta(r)+ \Z$ does only depend on $r+L$ if $r$ is a shadow vector, otherwise this is not necessarily true. Hence we return only shadow vectors here. """ if None == self.__values: self.__values = {} G = self.gram_matrix() l = self.shadow_level() R = self.dual_vectors() if not self.is_even(): s = self.a_shadow_vector() R = [s+r for r in R] for r in R: N = Integer( self.beta(r)*l) N = N%l if not self.__values.has_key(N): self.__values[N] = [r] else: self.__values[N] += [r] return self.__values def representatives( self, N): """ Return the subset of representatives $r$ in $L^\bullet/L$ which satisfy $\beta(r) \equiv N/level \bmod \ZZ$. """ v = self.values() return v.get(N%self.shadow_level(), []) def chi( self, t): """ Return the value of the Gauss sum $\sum_{x\in L^\bullet/L} e(tG[x]/2)/\sqrt D$, where $D = |L^\bullet/L|$. """ t = Integer(t)%self.shadow_level() if self.__chi.has_key(t): return self.__chi[t] l = self.shadow_level() z = QQbar.zeta(l) w = QQbar(self.det().sqrt()) V = self.values() self.__chi[t] = sum(len(V[a])*z**(t*a) for a in V)/w return self.__chi[t] def ev( self): """ Return a pair $alpha, L_{ev}$, where $L_{ev}$ is isomorphic to the kernel of $L\rightarrow \{\pm 1\}$, $x\mapsto e(\beta(x))$, and where $alpha$ is an embedding of $L_{ev}$ into $L$ whose image is this kernel. REMARK We have to find the kernel of the map $x\mapsto G[x] \equiv \sum_{j\in S}x_2 \bmod 2$. Here $S$ is the set of indices $i$ such that the $i$-diagonal element of $G$ is odd. A basis is given by $e_i$ ($i not\in S$) and $e_i + e_j$ ($i \in S$, $i\not=j$) and $2e_j$, where $j$ is any fixed index in $S$. """ if self.is_even(): return self.hom( self.basis(), self.module()), self D = self.gram_matrix().diagonal() S = [ i for i in range( len(D)) if is_odd(D[i])] j = min(S) e = self.basis() n = len(e) form = lambda i: e[i] if i not in S else 2*e[j] if i == j else e[i]+e[j] a = [ form(i) for i in range( n)] # A = matrix( a).transpose() # return A, Lattice_class( [ self.beta( a[i],a[j]) for i in range(n) for j in range(i,n)]) Lev = Lattice_class( [ self.beta( a[i],a[j]) for i in range(n) for j in range(i,n)]) alpha = Lev.hom( a, self.module()) return alpha, Lev def twist( self, a): """ Return the lattice self rescaled by the integer $a$. """ e = self.basis() n = len(e) return Lattice_class( [ a*self.beta( e[i],e[j]) for i in range(n) for j in range(i,n)]) def fqm( self): """ Return a pair $(f,M)$, where $M$ is the discriminant module of $L_ev$ up to isomorphism and $f:L\rightarrow M$ the canonical map. TODO Currently returns only M. """ if self.is_even(): return FiniteQuadraticModule( self.gram_matrix()) else: a,Lev = self.ev() return FiniteQuadraticModule( Lev.gram_matrix()) def ZN_embeddings( self): """ Return a list of all embeddings of $L$ into $\ZZ^N$ modulo the action of $O(\ZZ^N)$. """ def cs_range( f, subset = None): """ For a symmetric semi-positive integral matrix $f$, return a list of all integral $n$-vectors $v$ such that $x^tfx - (v*x)^2 >= 0$ for all $x$. """ n = f.dimensions()[0] b = vector( s.isqrt() for s in f.diagonal()) zv = vector([0]*n) box = [b - vector(t) for t in mrange( (2*b + vector([1]*n)).list()) if b - vector(t) > zv] if subset: box = [v for v in box if v in subset] rge = [v for v in box if min( (f - matrix(v).transpose()*matrix(v)).eigenvalues()) >=0] return rge def embs( f, subset = None): """ For a semipositive matrix $f$ return a list of all integral matrices $M$ such that $f=M^t * M$ modulo the left action of $\lim O(\ZZ^N)$ on the set of these matrices. """ res = [] csr = cs_range( f, subset) for v in csr: fv = f - matrix(v).transpose()*matrix(v) if 0 == fv: res.append( matrix(v)) else: tmp = embs( fv, csr[ csr.index( v):]) for e in tmp: res.append( e.insert_row(0, v)) return res l_embs = embs( self.gram_matrix()) import lattice_index return map( lambda a: self.hom( a.columns(), lattice_index.LatticeIndex( 'Z^'+ str(a.nrows()))), l_embs) def vectors_in_shell ( self, bound, max = 10000): """ Return the list of all $x\not=0$ in self such that $\beta(x) <= bound$. """ M = self.module() return map( lambda x: M(x), self._vectors_in_shell ( self.gram_matrix(), 2*bound, max)) def dual_vectors_in_shell ( self, bound, max = 10000): """ Return the list of all $x\not=0$ in the dual of self such that $\beta(x) <= bound$. """ l = self.level() F = l*self.__Gi S = self.space() return map( lambda x: S(self.__Gi*x), self._vectors_in_shell ( F, 2*l*bound, max)) def shadow_vectors_in_shell (self, bound, max = 10000): """ Return the list of all $x\not=0$ in the bullet of self such that $\beta(x) <= bound$. """ if self.is_even(): return self.dual_vectors_in_shell ( bound, max) a, Lev = L.ev() l = Lev.dual_vectors_in_shell ( bound, max) A = a.matrix() return filter( lambda x: self.is_in_shadow(x) ,[y*A for y in l]) @staticmethod def _vectors_in_shell( F, bound, max = 1000): """ For an (integral) positive symmetric matrix $F$ return a list of vectors such that $x^tFx <= bound$. The method returns only nonzero vectors and for each pair $v$ and $-v$ only one. NOTE A wrapper for the pari method qfminim(). """ # assert bound >=1, 'Pari bug: bound should be >= 2' if bound < 1: return [] p = F._pari_() po = p.qfminim( bound, max, flag = 0).sage() if max == po[0]: raise ArithmeticError( 'Increase max') ves = [vector([0]*F.nrows())] for x in po[2].columns(): ves += [x,-x] # return po[2].columns() return ves @staticmethod def _is_int_vec( v): for c in v: if c.denominator() > 1: return False return True
class LieAlgebraWithStructureCoefficients(FinitelyGeneratedLieAlgebra, IndexedGenerators): r""" A Lie algebra with a set of specified structure coefficients. The structure coefficients are specified as a dictionary `d` whose keys are pairs of basis indices, and whose values are dictionaries which in turn are indexed by basis indices. The value of `d` at a pair `(u, v)` of basis indices is the dictionary whose `w`-th entry (for `w` a basis index) is the coefficient of `b_w` in the Lie bracket `[b_u, b_v]` (where `b_x` means the basis element with index `x`). INPUT: - ``R`` -- a ring, to be used as the base ring - ``s_coeff`` -- a dictionary, indexed by pairs of basis indices (see below), and whose values are dictionaries which are indexed by (single) basis indices and whose values are elements of `R` - ``names`` -- list or tuple of strings - ``index_set`` -- (default: ``names``) list or tuple of hashable and comparable elements OUTPUT: A Lie algebra over ``R`` which (as an `R`-module) is free with a basis indexed by the elements of ``index_set``. The `i`-th basis element is displayed using the name ``names[i]``. If we let `b_i` denote this `i`-th basis element, then the Lie bracket is given by the requirement that the `b_k`-coefficient of `[b_i, b_j]` is ``s_coeff[(i, j)][k]`` if ``s_coeff[(i, j)]`` exists, otherwise ``-s_coeff[(j, i)][k]`` if ``s_coeff[(j, i)]`` exists, otherwise `0`. EXAMPLES: We create the Lie algebra of `\QQ^3` under the Lie bracket defined by `\times` (cross-product):: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}) sage: (x,y,z) = L.gens() sage: L.bracket(x, y) z sage: L.bracket(y, x) -z TESTS: We can input structure coefficients that fail the Jacobi identity, but the test suite will call us out on it:: sage: Fake = LieAlgebra(QQ, 'x,y,z', {('x','y'):{'z':3}, ('y','z'):{'z':1}, ('z','x'):{'y':1}}) sage: TestSuite(Fake).run() Failure in _test_jacobi_identity: ... Old tests !!!!!placeholder for now!!!!!:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.basis() Finite family {'y': y, 'x': x} """ @staticmethod def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: L1 = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: L2 = LieAlgebra(QQ, 'x,y', {('y','x'): {'x':-1}}) sage: L1 is L2 True Check that we convert names to the indexing set:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}, index_set=list(range(3))) sage: (x,y,z) = L.gens() sage: L[x,y] L[2] """ names, index_set = standardize_names_index_set(names, index_set) # Make sure the structure coefficients are given by the index set if names is not None and names != tuple(index_set): d = {x: index_set[i] for i,x in enumerate(names)} get_pairs = lambda X: X.items() if isinstance(X, dict) else X try: s_coeff = {(d[k[0]], d[k[1]]): [(d[x], y) for x,y in get_pairs(s_coeff[k])] for k in s_coeff} except KeyError: # At this point we assume they are given by the index set pass s_coeff = LieAlgebraWithStructureCoefficients._standardize_s_coeff(s_coeff, index_set) if s_coeff.cardinality() == 0: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) if (names is None and len(index_set) <= 1) or len(names) <= 1: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) return super(LieAlgebraWithStructureCoefficients, cls).__classcall__( cls, R, s_coeff, names, index_set, **kwds) @staticmethod def _standardize_s_coeff(s_coeff, index_set): """ Helper function to standardize ``s_coeff`` into the appropriate form (dictionary indexed by pairs, whose values are dictionaries). Strips items with coefficients of 0 and duplicate entries. This does not check the Jacobi relation (nor antisymmetry if the cardinality is infinite). EXAMPLES:: sage: from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients sage: d = {('y','x'): {'x':-1}} sage: LieAlgebraWithStructureCoefficients._standardize_s_coeff(d, ('x', 'y')) Finite family {('x', 'y'): (('x', 1),)} """ # Try to handle infinite basis (once/if supported) #if isinstance(s_coeff, AbstractFamily) and s_coeff.cardinality() == infinity: # return s_coeff index_to_pos = {k: i for i,k in enumerate(index_set)} sc = {} # Make sure the first gen is smaller than the second in each key for k in s_coeff.keys(): v = s_coeff[k] if isinstance(v, dict): v = v.items() if index_to_pos[k[0]] > index_to_pos[k[1]]: key = (k[1], k[0]) vals = tuple((g, -val) for g, val in v if val != 0) else: if not index_to_pos[k[0]] < index_to_pos[k[1]]: if k[0] == k[1]: if not all(val == 0 for g, val in v): raise ValueError("elements {} are equal but their bracket is not set to 0".format(k)) continue key = tuple(k) vals = tuple((g, val) for g, val in v if val != 0) if key in sc.keys() and sorted(sc[key]) != sorted(vals): raise ValueError("two distinct values given for one and the same bracket") if vals: sc[key] = vals return Family(sc) def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i,k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory(category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()]*len(index_set) for k,c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()} # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator def structure_coefficients(self, include_zeros=False): """ Return the dictionary of structure coefficients of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'x':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): x} sage: S = L.structure_coefficients(True); S Finite family {('x', 'y'): x, ('x', 'z'): 0, ('y', 'z'): 0} sage: S['x','z'].parent() is L True TESTS: Check that :trac:`23373` is fixed:: sage: L = lie_algebras.sl(QQ, 2) sage: sorted(L.structure_coefficients(True), key=str) [-2*E[-alpha[1]], -2*E[alpha[1]], h1] """ if not include_zeros: pos_to_index = dict(enumerate(self._indices)) return Family({(pos_to_index[k[0]], pos_to_index[k[1]]): self.element_class(self, self._s_coeff[k]) for k in self._s_coeff}) ret = {} zero = self._M.zero() for i,x in enumerate(self._indices): for j, y in enumerate(self._indices[i+1:]): if (i, j+i+1) in self._s_coeff: elt = self._s_coeff[i, j+i+1] elif (j+i+1, i) in self._s_coeff: elt = -self._s_coeff[j+i+1, i] else: elt = zero ret[x,y] = self.element_class(self, elt) # +i+1 for offset return Family(ret) def dimension(self): """ Return the dimension of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.dimension() 2 """ return self.basis().cardinality() def module(self, sparse=True): """ Return ``self`` as a free module. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}}) sage: L.module() Sparse vector space of dimension 3 over Rational Field """ return FreeModule(self.base_ring(), self.dimension(), sparse=sparse) @cached_method def zero(self): """ Return the element `0` in ``self``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.zero() 0 """ return self.element_class(self, self._M.zero()) def monomial(self, k): """ Return the monomial indexed by ``k``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.monomial('x') x """ return self.element_class(self, self._M.basis()[self._index_to_pos[k]]) def term(self, k, c=None): """ Return the term indexed by ``i`` with coefficient ``c``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.term('x', 4) 4*x """ if c is None: c = self.base_ring().one() else: c = self.base_ring()(c) return self.element_class(self, c * self._M.basis()[self._index_to_pos[k]]) def from_vector(self, v): """ Return an element of ``self`` from the vector ``v``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.from_vector([1, 2, -2]) x + 2*y - 2*z """ return self.element_class(self, self._M(v)) def some_elements(self): """ Return some elements of ``self``. EXAMPLES:: sage: L = lie_algebras.three_dimensional(QQ, 4, 1, -1, 2) sage: L.some_elements() [X, Y, Z, X + Y + Z] """ return list(self.basis()) + [self.sum(self.basis())] class Element(StructureCoefficientsElement): def _sorted_items_for_printing(self): """ Return a list of pairs ``(k, c)`` used in printing. .. WARNING:: The internal representation order is fixed, whereas this depends on ``"sorting_key"`` print option as it is used only for printing. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: elt = x + y/2 - z; elt x + 1/2*y - z sage: elt._sorted_items_for_printing() [('x', 1), ('y', 1/2), ('z', -1)] sage: key = {'x': 2, 'y': 1, 'z': 0} sage: L.print_options(sorting_key=key.__getitem__) sage: elt._sorted_items_for_printing() [('z', -1), ('y', 1/2), ('x', 1)] sage: elt -z + 1/2*y + x """ print_options = self.parent().print_options() pos_to_index = dict(enumerate(self.parent()._indices)) v = [(pos_to_index[k], c) for k, c in iteritems(self.value)] try: v.sort(key=lambda monomial_coeff: print_options['sorting_key'](monomial_coeff[0]), reverse=print_options['sorting_reverse']) except Exception: # Sorting the output is a plus, but if we can't, no big deal pass return v
def _persistent_homology(self, field=2, strict=True, verbose=False): """ Compute the homology intervals of the complex. INPUT: - ``field`` -- (default: 2) prime number modulo which the homology is computed - ``strict`` -- (default: ``True``) if ``False``, takes into account intervals of persistence 0 - ``verbose`` -- (default: ``False``) if ``True``, prints the progress of computation This method is called whenever Betti numbers or intervals are computed, and the result is cached. It returns the list of intervals. ALGORITHM: The computation behind persistent homology is a matrix reduction. The algorithm implemented is described in [ZC2005]_. EXAMPLES:: sage: X = FilteredSimplicialComplex([([0], 0), ([1], 0), ([0,1], 2)]) sage: X._persistent_homology()[0] [(0, 2), (0, +Infinity)] Some homology elements may have a lifespan or persistence of 0. They are usually discarded, but can be kept if necessary:: sage: X = FilteredSimplicialComplex() sage: X.insert([0,1],1) # opens a hole and closes it instantly sage: X._persistent_homology(strict=False)[0] [(1, 1), (1, +Infinity)] REFERENCES: - [ZC2005]_ TESTS: This complex is used as a running example in [ZC2005]_:: sage: l = [([0], 0), ([1], 0), ([2], 1), ([3], 1), ([0, 1], 1), ....: ([1, 2], 1), ([0, 3], 2), ([2, 3], 2), ([0, 2], 3), ....: ([0, 1, 2], 4), ([0, 2, 3], 5)] sage: X = FilteredSimplicialComplex(l) sage: X.persistence_intervals(0) [(0, 1), (1, 2), (0, +Infinity)] sage: X.persistence_intervals(1) [(3, 4), (2, 5)] sage: X.persistence_intervals(0, strict=False) [(0, 1), (1, 1), (1, 2), (0, +Infinity)] """ # first, order the simplices in lexico order # on dimension, value and then arbitrary order # defined by the Simplex class. def key(s): d = self._get_value(s) return (s.dimension(), d, s) simplices = list(self._filtration_dict) simplices.sort(key=key) # remember the index of each simplex in a dict self._index_of_simplex = {} n = len(simplices) for i in range(n): self._index_of_simplex[simplices[i]] = i self._field_int = field self._field = GF(field) self._chaingroup = FreeModule(self._field, rank_or_basis_keys=simplices) # Initialize data structures for the algo self._marked = [False] * n self._T = [None] * n intervals = [[] for i in range(self._dimension+1)] self.pairs = [] self._strict = strict self._verbose = verbose if self._verbose: print("Beginning first pass") for j in range(n): # if being verbose, print progress # every 1000 simplices. if self._verbose and j % 1000 == 0: print('{}/{}'.format(j, n)) s = simplices[j] d = self._remove_pivot_rows(s, simplices) if d == 0: self._marked[j] = True else: max_index = self._max_index(d) t = simplices[max_index] self._T[max_index] = (s, d) self._add_interval(t, s, intervals) if self._verbose: print("First pass over, beginning second pass") for j in range(n): if self._verbose and j % 1000 == 0: print('{}/{}'.format(j, n)) s = simplices[j] if self._marked[j] and not self._T[j]: self._add_interval(s, None, intervals) if self._verbose: print("Second pass over") return intervals
class DegreeGrading(Grading_abstract): r""" This class implements a monomial grading for a polynomial ring `R[x_1, .., x_n]`. """ def __init__(self, degrees): r""" INPUT: - ``degrees`` -- A list or tuple of `n` positive integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading((1,2)) sage: g = DegreeGrading([5,2,3]) """ self.__degrees = tuple(degrees) self.__module = FreeModule(ZZ, len(degrees)) self.__module_basis = self.__module.basis() def ngens(self): r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).ngens() 2 sage: DegreeGrading([5,2,3]).ngens() 3 """ return len(self.__degrees) def gen(self, i): r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]).gen(0) 5 sage: DegreeGrading([5,2,3]).gen(3) Traceback (most recent call last): ... ValueError: Generator 3 does not exist. """ if i < len(self.__degrees): return self.__degrees[i] raise ValueError("Generator %s does not exist." % (i, )) def gens(self): r""" The gradings of the generators of the polynomial ring. OUTPUT: A tuple of integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).gens() (1, 2) sage: DegreeGrading([5,2,3]).gens() (5, 2, 3) """ return self.__degrees def index(self, x): r""" The grading value of `x` with respect to this grading. INPUT: - `x` -- A tuple of length `n`. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.index((2,4,5)) 33 sage: g.index((2,3)) Traceback (most recent call last): ... ValueError: Tuple must have length 3. """ ## TODO: We shouldn't need this. #if len(x) == 0 : return infinity if len(x) != len(self.__degrees): raise ValueError("Tuple must have length %s." % (len(self.__degrees), )) return sum(map(mul, x, self.__degrees)) def basis(self, index, vars=None): r""" All monomials that are have given grading index involving only the given variables. INPUT: - ``index`` -- A grading index. - ``vars`` -- A list of integers from `0` to `n - 1` or ``None`` (default: ``None``); If ``None`` all variables will be considered. OUTPUT: A list of elements in `\Z^n`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.basis(2) [(0, 1, 0)] sage: g.basis(10, vars = [0]) [(2, 0, 0)] sage: g.basis(20, vars = [1,2]) [(0, 10, 0), (0, 7, 2), (0, 4, 4), (0, 1, 6)] sage: g.basis(-1) [] sage: g.basis(0) [(0, 0, 0)] """ if index < 0: return [] elif index == 0: return [self.__module.zero_vector()] if vars == None: vars = [m for (m, d) in enumerate(self.__degrees) if d <= index] if len(vars) == 0: return [] res = [ self.__module_basis[vars[0]] + m for m in self.basis(index - self.__degrees[vars[0]], vars) ] res += self.basis(index, vars[1:]) return res def subgrading(self, gens): r""" The grading of same type for the ring with only the variables given by ``gens``. INPUT: - ``gens`` - A list of integers. OUTPUT: An instance of :class:~`.DegreeGrading`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.subgrading([2]) Degree grading (3,) sage: g.subgrading([]) Degree grading () """ return DegreeGrading([self.__degrees[i] for i in gens]) def __contains__(self, x): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: 5 in g True sage: "t" in g False """ return isinstance(x, (int, Integer)) def __cmp__(self, other): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g == DegreeGrading([5,2,3]) True sage: g == DegreeGrading((2,4)) False """ c = cmp(type(self), type(other)) if c == 0: c = cmp(self.__degrees, other.__degrees) return c def __hash__(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: hash( DegreeGrading([5,2,3]) ) -1302562269 # 32-bit 7573306103633312291 # 64-bit """ return hash(self.__degrees) def _repr_(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]) Degree grading (5, 2, 3) """ return "Degree grading %s" % str(self.__degrees) def _latex_(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: latex( DegreeGrading([5,2,3]) ) \text{Degree grading } \left(5, 2, 3\right) """ return r"\text{Degree grading }" + latex(self.__degrees)
def __init__(self, A, mons, mats, names): """ Returns a quotient algebra defined via the action of a free algebra A on a (finitely generated) free module. The input for the quotient algebra is a list of monomials (in the underlying monoid for A) which form a free basis for the module of A, and a list of matrices, which give the action of the free generators of A on this monomial basis. EXAMPLES: Quaternion algebra defined in terms of three generators:: sage: n = 3 sage: A = FreeAlgebra(QQ,n,'i') sage: F = A.monoid() sage: i, j, k = F.gens() sage: mons = [ F(1), i, j, k ] sage: M = MatrixSpace(QQ,4) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]), M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ] sage: H3.<i,j,k> = FreeAlgebraQuotient(A,mons,mats) sage: x = 1 + i + j + k sage: x 1 + i + j + k sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*k Same algebra defined in terms of two generators, with some penalty on already slow arithmetic. :: sage: n = 2 sage: A = FreeAlgebra(QQ,n,'x') sage: F = A.monoid() sage: i, j = F.gens() sage: mons = [ F(1), i, j, i*j ] sage: r = len(mons) sage: M = MatrixSpace(QQ,r) sage: mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]) ] sage: H2.<i,j> = A.quotient(mons,mats) sage: k = i*j sage: x = 1 + i + j + k sage: x 1 + i + j + i*j sage: x**128 -170141183460469231731687303715884105728 + 170141183460469231731687303715884105728*i + 170141183460469231731687303715884105728*j + 170141183460469231731687303715884105728*i*j TEST:: sage: TestSuite(H2).run() """ if not is_FreeAlgebra(A): raise TypeError("Argument A must be an algebra.") R = A.base_ring() # if not R.is_field(): # TODO: why? # raise TypeError, "Base ring of argument A must be a field." n = A.ngens() assert n == len(mats) self.__free_algebra = A self.__ngens = n self.__dim = len(mons) self.__module = FreeModule(R,self.__dim) self.__matrix_action = mats self.__monomial_basis = mons # elements of free monoid Algebra.__init__(self, R, names, normalize=True)
def in_new_basis(L, basis, names, check=True, category=None): r""" Return an isomorphic copy of the Lie algebra in a different basis. INPUT: - ``L`` -- the Lie algebra - ``basis`` -- a list of elements of the Lie algebra - ``names`` -- a list of strings to use as names for the new basis - ``check`` -- (default:``True``) a boolean; if ``True``, verify that the list ``basis`` is indeed a basis of the Lie algebra - ``category`` -- (default:``None``) a subcategory of :class:`FiniteDimensionalLieAlgebrasWithBasis` to apply to the new Lie algebra. EXAMPLES: The method may be used to relabel the elements:: sage: import sys, pathlib sage: sys.path.append(str(pathlib.Path().absolute())) sage: from lie_gradings.gradings.utilities import in_new_basis sage: L.<X,Y> = LieAlgebra(QQ, {('X','Y'): {'Y': 1}}) sage: K.<A,B> = in_new_basis(L, [X, Y]) sage: K[A,B] B The new Lie algebra inherits nilpotency:: sage: L = lie_algebras.Heisenberg(QQ, 1) sage: X,Y,Z = L.basis() sage: L.category() Category of finite dimensional nilpotent lie algebras with basis over Rational Field sage: K.<A,B,C> = in_new_basis(L, [X + Y, Y - X, Z]) sage: K[A,B] 2*C sage: K[[A,B],A] 0 sage: K.is_nilpotent() True sage: K.category() Category of finite dimensional nilpotent lie algebras with basis over Rational Field Some properties such as being stratified may in general be lost when changing the basis, and are therefore not preserved:: sage: L.<X,Y,Z> = LieAlgebra(QQ, 2, step=2) sage: L.category() Category of finite dimensional stratified lie algebras with basis over Rational Field sage: K.<A,B,C> = in_new_basis(L, [Z, X, Y]) sage: K.category() Category of finite dimensional nilpotent lie algebras with basis over Rational Field If the property is known to be preserved, an extra category may be passed to the method:: sage: C = L.category() sage: K.<A,B,C> = in_new_basis(L, [Z, X, Y], category=C) sage: K.category() Category of finite dimensional stratified lie algebras with basis over Rational Field """ try: m = L.module() except AttributeError: m = FreeModule(L.base_ring(), L.dimension()) sm = m.submodule_with_basis([X.to_vector() for X in basis]) if check: # check that new basis is a basis A = matrix([X.to_vector() for X in basis]) if not A.is_invertible(): raise ValueError("%s is not a basis of the Lie algebra" % basis) # form dictionary of structure coefficients in the new basis sc = {} for (X, nX), (Y, nY) in combinations(zip(basis, names), 2): Z = X.bracket(Y) zvec = sm.coordinate_vector(Z.to_vector()) sc[(nX, nY)] = {nW: c for nW, c in zip(names, zvec)} C = LieAlgebras(L.base_ring()).FiniteDimensional().WithBasis() C = C.or_subcategory(category) if L.is_nilpotent(): C = C.Nilpotent() return LieAlgebra(L.base_ring(), sc, names=names, category=C)
def normal_cone(self): r""" Return the (closure of the) normal cone of the triangulation. Recall that a regular triangulation is one that equals the "crease lines" of a convex piecewise-linear function. This support function is not unique, for example, you can scale it by a positive constant. The set of all piecewise-linear functions with fixed creases forms an open cone. This cone can be interpreted as the cone of normal vectors at a point of the secondary polytope, which is why we call it normal cone. See [GKZ]_ Section 7.1 for details. OUTPUT: The closure of the normal cone. The `i`-th entry equals the value of the piecewise-linear function at the `i`-th point of the configuration. For an irregular triangulation, the normal cone is empty. In this case, a single point (the origin) is returned. EXAMPLES:: sage: triangulation = polytopes.n_cube(2).triangulate(engine='internal') sage: triangulation (<0,1,3>, <0,2,3>) sage: N = triangulation.normal_cone(); N 4-d cone in 4-d lattice sage: N.rays() (-1, 0, 0, 0), ( 1, 0, 1, 0), (-1, 0, -1, 0), ( 1, 0, 0, -1), (-1, 0, 0, 1), ( 1, 1, 0, 0), (-1, -1, 0, 0) in Ambient free module of rank 4 over the principal ideal domain Integer Ring sage: N.dual().rays() (-1, 1, 1, -1) in Ambient free module of rank 4 over the principal ideal domain Integer Ring TESTS:: sage: polytopes.n_simplex(2).triangulate().normal_cone() 3-d cone in 3-d lattice sage: _.dual().is_trivial() True """ if not self.point_configuration().base_ring().is_subring(QQ): raise NotImplementedError("Only base rings ZZ and QQ are supported") from sage.libs.ppl import Variable, Constraint, Constraint_System, Linear_Expression, C_Polyhedron from sage.matrix.constructor import matrix from sage.misc.misc import uniq from sage.rings.arith import lcm pc = self.point_configuration() cs = Constraint_System() for facet in self.interior_facets(): s0, s1 = self._boundary_simplex_dictionary()[facet] p = set(s0).difference(facet).pop() q = set(s1).difference(facet).pop() origin = pc.point(p).reduced_affine_vector() base_indices = [i for i in s0 if i != p] base = matrix([pc.point(i).reduced_affine_vector() - origin for i in base_indices]) sol = base.solve_left(pc.point(q).reduced_affine_vector() - origin) relation = [0] * pc.n_points() relation[p] = sum(sol) - 1 relation[q] = 1 for i, base_i in enumerate(base_indices): relation[base_i] = -sol[i] rel_denom = lcm([QQ(r).denominator() for r in relation]) relation = [ZZ(r * rel_denom) for r in relation] ex = Linear_Expression(relation, 0) cs.insert(ex >= 0) from sage.modules.free_module import FreeModule ambient = FreeModule(ZZ, self.point_configuration().n_points()) if cs.empty(): cone = C_Polyhedron(ambient.dimension(), "universe") else: cone = C_Polyhedron(cs) from sage.geometry.cone import _Cone_from_PPL return _Cone_from_PPL(cone, lattice=ambient)
def __init__(self, B, sigma=1, c=None, precision=None): r""" Construct a discrete Gaussian sampler over the lattice `Λ(B)` with parameter ``sigma`` and center `c`. INPUT: - ``B`` -- a basis for the lattice, one of the following: - an integer matrix, - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or - an object where ``matrix(B)`` succeeds, e.g. a list of vectors. - ``sigma`` -- Gaussian parameter `σ>0`. - ``c`` -- center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - ``precision`` -- bit precision `≥ 53`. EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: n = 2; sigma = 3.0 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c 56.2162803067524 sage: from collections import defaultdict sage: counter = defaultdict(Integer) sage: m = 0 sage: def add_samples(i): ....: global counter, m ....: for _ in range(i): ....: counter[D()] += 1 ....: m += 1 sage: v = vector(ZZ, n, (-3, -3)) sage: v.set_immutable() sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: add_samples(1000) sage: v = vector(ZZ, n, (0, 0)) sage: v.set_immutable() sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: add_samples(1000) sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) sage: D = DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0) over lattice with basis <BLANKLINE> [2 1 1] [1 2 1] [1 1 2] sage: D().parent() is D.c.parent() True """ precision = DiscreteGaussianDistributionLatticeSampler.compute_precision( precision, sigma) self._RR = RealField(precision) self._sigma = self._RR(sigma) try: B = matrix(B) except (TypeError, ValueError): pass try: B = B.matrix() except AttributeError: pass self.B = B self._G = B.gram_schmidt()[0] try: c = vector(ZZ, B.ncols(), c) except TypeError: try: c = vector(QQ, B.ncols(), c) except TypeError: c = vector(RR, B.ncols(), c) self._c = c self.f = lambda x: exp(-(vector(ZZ, B.ncols(), x) - c).norm()**2 / (2 * self._sigma**2)) # deal with trivial case first, it is common if self._G == 1 and self._c == 0: self._c_in_lattice = True D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) self.D = tuple([D for _ in range(self.B.nrows())]) self.VS = FreeModule(ZZ, B.nrows()) return w = B.solve_left(c) if w in ZZ**B.nrows(): self._c_in_lattice = True D = [] for i in range(self.B.nrows()): sigma_ = self._sigma / self._G[i].norm() D.append( DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) self.D = tuple(D) self.VS = FreeModule(ZZ, B.nrows()) else: self._c_in_lattice = False