From 7f234c529248349cf1a3c109d4f2fc90cb5d5613 Mon Sep 17 00:00:00 2001 From: Tommy Hofmann Date: Thu, 29 Aug 2024 10:40:34 +0200 Subject: [PATCH] feat: bring algebras into shape - documentation - tests - renaming --- docs/src/.vitepress/config.mts | 8 + src/AlgAss.jl | 2 +- src/AlgAss/AbsAlgAss.jl | 17 + ...{AlgAss.jl => StructureConstantAlgebra.jl} | 319 ++++++++++++------ src/exports.jl | 2 + test/AlgAss.jl | 2 +- ...{AlgAss.jl => StructureConstantAlgebra.jl} | 37 +- 7 files changed, 274 insertions(+), 113 deletions(-) rename src/AlgAss/{AlgAss.jl => StructureConstantAlgebra.jl} (90%) rename test/AlgAss/{AlgAss.jl => StructureConstantAlgebra.jl} (80%) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index f2d3143574..910aecd99d 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -88,6 +88,14 @@ export default defineConfig({ { text: 'Discriminant groups', link: 'manual/quad_forms/discriminant_group'}, ] }, + { text: 'Algebras', + collapsed: true, + items: [ + { text: 'Introduction', link: 'manual/algebras/intro'}, + { text: 'Basics', link: 'manual/algebras/basics'}, + { text: 'Structure constant algebras', link: 'manual/algebras/structureconstant'}, + ] + }, { text: 'Elliptic curves', collapsed: true, diff --git a/src/AlgAss.jl b/src/AlgAss.jl index c5c2fae399..bbca50f225 100644 --- a/src/AlgAss.jl +++ b/src/AlgAss.jl @@ -1,7 +1,7 @@ include("AlgAss/Map.jl") include("AlgAss/AbsAlgAss.jl") include("AlgAss/AlgQuat.jl") -include("AlgAss/AlgAss.jl") +include("AlgAss/StructureConstantAlgebra.jl") include("AlgAss/radical.jl") include("AlgAss/ChangeRing.jl") include("AlgAss/AlgGrp.jl") diff --git a/src/AlgAss/AbsAlgAss.jl b/src/AlgAss/AbsAlgAss.jl index 2f7aecc1ae..4e761ce548 100644 --- a/src/AlgAss/AbsAlgAss.jl +++ b/src/AlgAss/AbsAlgAss.jl @@ -1,5 +1,22 @@ _base_ring(A::AbstractAssociativeAlgebra) = base_ring(A) +# document the following +#base_ring +#zero_algebra(T,...) +#iszero +#has_one +#dim +#@doc raw""" +# is_commutative(A::StructureConstantAlgebra) -> Bool +# +#Returns `true` if $A$ is a commutative ring and `false` otherwise. +#""" +#raw""" +# zero_algebra(R::Ring) -> StructureConstantAlgebra +# +#Return the zero ring as an associative $R$-algebra. +#""" + ################################################################################ # # Morphism types diff --git a/src/AlgAss/AlgAss.jl b/src/AlgAss/StructureConstantAlgebra.jl similarity index 90% rename from src/AlgAss/AlgAss.jl rename to src/AlgAss/StructureConstantAlgebra.jl index 81f8b27d94..e512631e76 100644 --- a/src/AlgAss/AlgAss.jl +++ b/src/AlgAss/StructureConstantAlgebra.jl @@ -1,5 +1,193 @@ add_assertion_scope(:StructureConstantAlgebra) +################################################################################ +# +# Derived types +# +################################################################################ + +elem_type(::Type{StructureConstantAlgebra{T}}) where {T} = AssociativeAlgebraElem{T, StructureConstantAlgebra{T}} + +# Definitions for orders +order_type(::Type{StructureConstantAlgebra{QQFieldElem}}) = AlgAssAbsOrd{StructureConstantAlgebra{QQFieldElem}, elem_type(StructureConstantAlgebra{QQFieldElem})} +order_type(::Type{StructureConstantAlgebra{T}}) where {T <: NumFieldElem} = AlgAssRelOrd{T, fractional_ideal_type(order_type(parent_type(T)))} +order_type(A::StructureConstantAlgebra) = order_type(typeof(A)) + +################################################################################ +# +# Constructors +# +################################################################################ + +@doc raw""" + structure_constant_algebra(R::Ring, sctable::Array{_, 3}; one::Vector = nothing, + check::Bool = true) + +Given an array with dimensions $(d, d, d)$ and a ring $R$, return the +$d$-dimensional structure constant algebra over $R$. The basis `e` of $R$ +satisfies `e[i] * e[j] = sum(sctable[i,j,k] * e[k] for k in 1:d)`. + +Unless `check = false`, this includes (time consuming) associativity and +distributivity checks. If `one` is given, record the element with the +supplied coordinate vector as the one element of the algebra. + +# Examples + +```jldoctest +julia> associative_algebra(QQ, reshape([1, 0, 0, 2, 0, 1, 1, 0], (2, 2, 2))) +Associative algebra of dimension 2 over Rational field +``` +""" +function structure_constant_algebra(R::Ring, sctable::Array{<:Any, 3}; one = nothing, + check::Bool = true) + return associative_algebra(R, sctable; one = one, check = check) +end + +structure_constant_algebra(R::Ring, mult_table::Array{T, 3}, one::Vector{T}; check::Bool = true) where T = StructureConstantAlgebra(R, mult_table, one; check) + +@doc raw""" + structure_constant_algebra(f::PolyRingElem) + +Given a polynomial $f$ over a commutative ring $R$, return the quotient ring +$R[X]/(f)$ as an algebra. + +# Examples + +```jldoctest +julia> structure_constant_algebra(f) +Associative algebra of dimension 2 over Rational field +``` +""" +structure_constant_algebra(f::PolyRingElem) = StructureConstantAlgebra(f) + +function associative_algebra(R::Ring, sctable::Array{<:Any, 3}; one = nothing, + check::Bool = true) + @req all(isequal(size(sctable, 1)), size(sctable)) "Multiplication must have dimensions have same length" + d = size(sctable, 1) + if one !== nothing + @req length(one) == size(sctable, 1) "Length ($(length(one))) of vector for one element must be $(d)" + end + + if (d > 0 && parent(d[1, 1, 1]) === R) || + (d == 0 && eltype(sctable) === elem_type(R)) + _sctable = sctable::Array{elem_type(R), 3} + else + _sctable = map(R, sctable)::Array{elem_type(R), 3} + end + + if one isa Vector + if length(one) > 0 + if parent(one[1]) === R + _one = one + else + _one = map(R, one)::Vector{elem_type(R)} + end + else + _one = elem_type(R)[]::Vector{elem_type(R)} + end + return StructureConstantAlgebra(R, _sctable, _one; check) + end + @assert one isa Nothing + return StructureConstantAlgebra(R, _sctable; check) +end + +associative_algebra(R::Ring, mult_table::Array{T, 3}, one::Vector{T}; check::Bool = true) where T = StructureConstantAlgebra(R, mult_table, one; check) + +function StructureConstantAlgebra(R::Ring, mult_table::Array{T, 3}, one::Vector{T}; check::Bool = get_assertion_level(:StructureConstantAlgebra) > 0) where {T} + if size(mult_table, 1) == 0 + return zero_algebra(R) + end + A = StructureConstantAlgebra{T}(R, mult_table, one) + if check + @req check_associativity(A) "Multiplication table does not define associative operation" + @req check_distributivity(A) "Multiplication table does not define distributive operation" + end + return A +end + +function StructureConstantAlgebra(R::Ring, mult_table::Array{T, 3}; check::Bool = get_assertion_level(:StructureConstantAlgebra) > 0) where {T} + @req all(isequal(size(mult_table, 1)), size(mult_table)) "Multiplication must have dimensions have same length" + if size(mult_table, 1) == 0 + return zero_algebra(R) + end + A = StructureConstantAlgebra{T}(R) + A.mult_table = mult_table + A.iszero = false + has_one, one = find_one(A) + A.has_one = has_one + if has_one + A.one = one + end + if check + @req check_associativity(A) "Multiplication table does not define associative operation" + @req check_distributivity(A) "Multiplication table does not define distributive operation" + end + return A +end + +# Does anyone actually use this? +function StructureConstantAlgebra(R::Ring, d::Int, arr::Vector{T}) where {T} + if d == 0 + return zero_algebra(R) + end + mult_table = Array{T, 3}(undef, d, d, d) + n = d^2 + for i in 1:d + for j in 1:d + for k in 1:d + mult_table[i, j, k] = arr[(i - 1) * n + (j - 1) * d + k] + end + end + end + return StructureConstantAlgebra(R, mult_table) +end + +@doc raw""" + structure_constant_algebra(K::SimpleNumField) -> StructureConstantAlgebra, Map + +Given a number field $L/K$, return $L$ as a $K$-algebra $A$ together with a +$K$-linear map $A \to L$. + +# Examples + +```jldoctest +julia> L, = quadratic_field(2); + +julia> structure_constant_algebra(L) +(Associative algebra of dimension 2 over Rational field, Map: associative algebra of dimension 2 over QQ -> L) +``` +""" +function structure_constant_algebra(K::SimpleNumField) + StructureConstantAlgebra(K) +end + +function StructureConstantAlgebra(K::SimpleNumField) + A = StructureConstantAlgebra(defining_polynomial(K)) + k = base_field(K) + m = AbsAlgAssToNfAbsMor(A, K, identity_matrix(k, dim(A)), identity_matrix(k, dim(A))) + A.maps_to_numberfields = [ (K, m) ] + return A, m +end + +################################################################################ +# +# Zero algebra +# +################################################################################ + +function zero_algebra(R::Ring) + A = StructureConstantAlgebra{elem_type(R)}(R) + A.iszero = true + A.is_commutative = 1 + A.has_one = true + A.one = elem_type(R)[] + A.mult_table = Array{elem_type(R), 3}(undef, 0, 0, 0) + return A +end + + +zero_algebra(::Type{StructureConstantAlgebra}, R::Ring) = return zero_algebra(R) + ################################################################################ # # Basic field access @@ -22,12 +210,16 @@ function denominator_of_multiplication_table(A::StructureConstantAlgebra{QQField end::ZZRingElem end +# use abstract doc base_ring(A::StructureConstantAlgebra{T}) where {T} = A.base_ring::parent_type(T) +# use abstract doc base_ring_type(::Type{StructureConstantAlgebra{T}}) where {T} = parent_type(T) +# use abstract doc has_one(A::StructureConstantAlgebra) = A.has_one +# use abstract doc iszero(A::StructureConstantAlgebra) = A.iszero function Generic.dim(A::StructureConstantAlgebra) @@ -37,23 +229,32 @@ function Generic.dim(A::StructureConstantAlgebra) return size(multiplication_table(A, copy = false), 1) end -degree(A::StructureConstantAlgebra) = dim(A) +@doc raw""" + structure_constant_table(A::StructureConstantAlgebra; copy::Bool = true) -> Array{_, 3} -elem_type(::Type{StructureConstantAlgebra{T}}) where {T} = AssociativeAlgebraElem{T, StructureConstantAlgebra{T}} +Given an algebra $A$, return the structure constant table of $A$. See +[`structure_constant_algebra`](@ref) for the defining property. -order_type(::StructureConstantAlgebra{QQFieldElem}) = AlgAssAbsOrd{StructureConstantAlgebra{QQFieldElem}, elem_type(StructureConstantAlgebra{QQFieldElem})} -order_type(::Type{StructureConstantAlgebra{QQFieldElem}}) = AlgAssAbsOrd{StructureConstantAlgebra{QQFieldElem}, elem_type(StructureConstantAlgebra{QQFieldElem})} +# Examples -order_type(::StructureConstantAlgebra{T}) where { T <: NumFieldElem } = AlgAssRelOrd{T, fractional_ideal_type(order_type(parent_type(T)))} -order_type(::Type{StructureConstantAlgebra{T}}) where { T <: NumFieldElem } = AlgAssRelOrd{T, fractional_ideal_type(order_type(parent_type(T)))} +```jldoctest +julia> A = associative_algebra(QQ, reshape([1, 0, 0, 2, 0, 1, 1, 0], (2, 2, 2))); -@doc raw""" - multiplication_table(A::StructureConstantAlgebra; copy::Bool = true) -> Array{RingElem, 3} +julia> structure_constant_table(A) +2×2×2 Array{QQFieldElem, 3}: +[:, :, 1] = + 1 0 + 0 2 -Given an algebra $A$ this function returns the multiplication table of $A$: -If the function returns $M$ and the basis of $A$ is $e_1,\dots, e_n$ then -it holds $e_i \cdot e_j = \sum_k M[i, j, k] \cdot e_k$. +[:, :, 2] = + 0 1 + 1 0 +``` """ +function structure_constant_table(A::StructureConstantAlgebra; copy::Bool = true) + return multiplication_table(A; copy = copy) +end + function multiplication_table(A::StructureConstantAlgebra; copy::Bool = true) if copy return deepcopy(A.mult_table) @@ -70,11 +271,6 @@ end is_commutative_known(A::StructureConstantAlgebra) = (A.is_commutative != 0) -@doc raw""" - is_commutative(A::StructureConstantAlgebra) -> Bool - -Returns `true` if $A$ is a commutative ring and `false` otherwise. -""" function is_commutative(A::StructureConstantAlgebra) if is_commutative_known(A) return A.is_commutative == 1 @@ -93,7 +289,7 @@ end ################################################################################ # -# Construction +# Finding the one # ################################################################################ @@ -128,88 +324,8 @@ function find_one(A::StructureConstantAlgebra) return true, one end -raw""" - zero_algebra(R::Ring) -> StructureConstantAlgebra - -Return the zero ring as an associative $R$-algebra. -""" -function zero_algebra(R::Ring) - A = StructureConstantAlgebra{elem_type(R)}(R) - A.iszero = true - A.is_commutative = 1 - A.has_one = true - A.one = elem_type(R)[] - A.mult_table = Array{elem_type(R), 3}(undef, 0, 0, 0) - return A -end - -raw""" - associative_algebra(R::Ring, mult_table::Array{T, 3}[, one::Vector{T}]; check::Bool = true) where T - - Associative Algebra over `R` with generators $e_1,\dots,e_d$ where `size(mult_table) == (d, d, d)` and $e_ie_j$ = `sum(mult_table[i,j,k]*e[k] for k in 1:d)`. - Unless `check = false`, this includes (time consuming) associativity and distributivity checks. - If `one` is given, record the element with the according coefficient vector as one element of the algebra. -""" -associative_algebra(R::Ring, mult_table::Array{<:Any, 3}; check::Bool = true) = StructureConstantAlgebra(R, mult_table; check) -associative_algebra(R::Ring, mult_table::Array{T, 3}, one::Vector{T}; check::Bool = true) where T = StructureConstantAlgebra(R, mult_table, one; check) - -function StructureConstantAlgebra(R::Ring, mult_table::Array{T, 3}, one::Vector{T}; check::Bool = get_assertion_level(:StructureConstantAlgebra) > 0) where {T} - @req all(isequal(size(mult_table, 1)), size(mult_table)) "Multiplication must have dimensions have same length" - - if size(mult_table, 1) == 0 - return zero_algebra(R) - end - A = StructureConstantAlgebra{T}(R, mult_table, one) - if check - @req check_associativity(A) "Multiplication table does not define associative operation" - @req check_distributivity(A) "Multiplication table does not define distributive operation" - end - return A -end - -function StructureConstantAlgebra(R::Ring, mult_table::Array{T, 3}; check::Bool = get_assertion_level(:StructureConstantAlgebra) > 0) where {T} - @req all(isequal(size(mult_table, 1)), size(mult_table)) "Multiplication must have dimensions have same length" - if size(mult_table, 1) == 0 - return zero_algebra(R) - end - A = StructureConstantAlgebra{T}(R) - A.mult_table = mult_table - A.iszero = false - has_one, one = find_one(A) - A.has_one = has_one - if has_one - A.one = one - end - if check - @req check_associativity(A) "Multiplication table does not define associative operation" - @req check_distributivity(A) "Multiplication table does not define distributive operation" - end - return A -end - -# Does anyone actually use this? -function StructureConstantAlgebra(R::Ring, d::Int, arr::Vector{T}) where {T} - if d == 0 - return zero_algebra(R) - end - mult_table = Array{T, 3}(undef, d, d, d) - n = d^2 - for i in 1:d - for j in 1:d - for k in 1:d - mult_table[i, j, k] = arr[(i - 1) * n + (j - 1) * d + k] - end - end - end - return StructureConstantAlgebra(R, mult_table) -end - -raw""" - associative_algebra(f::PolyRingElem) - -Associative algebra $R[x]/f$. -""" associative_algebra(f::PolyRingElem) = StructureConstantAlgebra(f) + function StructureConstantAlgebra(f::PolyRingElem) R = base_ring(parent(f)) n = degree(f) @@ -236,13 +352,6 @@ function StructureConstantAlgebra(f::PolyRingElem) return A end -function StructureConstantAlgebra(K::AbsSimpleNumField) - A = StructureConstantAlgebra(K.pol) - m = AbsAlgAssToNfAbsMor(A, K, identity_matrix(FlintQQ, dim(A)), identity_matrix(FlintQQ, dim(A))) - A.maps_to_numberfields = [ (K, m) ] - return A, m -end - # Reduces the rows of M in `rows` modulo N in place. # Assumes that N is in lowerleft HNF. function reduce_rows_mod_hnf!(M::ZZMatrix, N::ZZMatrix, rows::Vector{Int}) diff --git a/src/exports.jl b/src/exports.jl index d84061c036..71a3288cab 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -860,6 +860,8 @@ export stable_faltings_height export stable_subgroups export stable_submodules export strong_echelon_form +export structure_constant_algebra +export structure_constant_table export sub export subalgebra export subfields diff --git a/test/AlgAss.jl b/test/AlgAss.jl index 3199438ad3..7ed121234b 100644 --- a/test/AlgAss.jl +++ b/test/AlgAss.jl @@ -1,6 +1,6 @@ @testset "AlgAss" begin include("AlgAss/AbsAlgAss.jl") - include("AlgAss/AlgAss.jl") + include("AlgAss/StructureConstantAlgebra.jl") include("AlgAss/radical.jl") include("AlgAss/AlgGrp.jl") include("AlgAss/AlgMat.jl") diff --git a/test/AlgAss/AlgAss.jl b/test/AlgAss/StructureConstantAlgebra.jl similarity index 80% rename from test/AlgAss/AlgAss.jl rename to test/AlgAss/StructureConstantAlgebra.jl index 34b41e1574..9177c5cb4b 100644 --- a/test/AlgAss/AlgAss.jl +++ b/test/AlgAss/StructureConstantAlgebra.jl @@ -48,7 +48,32 @@ end # creation - @test_throws ArgumentError associative_algebra(QQ, map(QQ, reshape([1 2 1 2; 1 2 1 2], 2, 2, 2))) + @test_throws ArgumentError structure_constant_algebra(QQ, map(QQ, reshape([1 2 1 2; 1 2 1 2], 2, 2, 2))) + @test_throws ArgumentError structure_constant_algebra(QQ, QQFieldElem[2;;;]; one = QQFieldElem[2, 3]) + + A = @inferred associative_algebra(QQ, reshape([1, 0, 0, 2, 0, 1, 1, 0], (2, 2, 2))) + @test dim(A) == 2 + @test base_ring(A) === QQ + @test structure_constant_table(A) == reshape([1, 0, 0, 2, 0, 1, 1, 0], (2, 2, 2)) + @test structure_constant_table(A; copy = false) == reshape([1, 0, 0, 2, 0, 1, 1, 0], (2, 2, 2)) + + K, = quadratic_field(2) + A, m = structure_constant_algebra(K) + @test base_ring(A) === QQ + @test dim(A) == 2 + @test m(one(A)) == one(K) + @test preimage(m, one(K)) == one(A) + @test m(zero(A)) == zero(K) + @test preimage(m, zero(K)) == zero(A) + Kt, t = K["t"] + L, b = number_field(t^2 + 3, "b") + A, m = structure_constant_algebra(L) + @test base_ring(A) === K + @test dim(A) == 2 + @test m(one(A)) == one(L) + @test preimage(m, one(L)) == one(A) + @test m(zero(A)) == zero(L) + @test preimage(m, zero(L)) == zero(A) @testset "Change of ring" begin @@ -106,7 +131,7 @@ end # Extend from F_p^m to F_p^n Fqx, x = Fq["x"] f = x^2 + 5x + 2 - A = associative_algebra(f) + A = structure_constant_algebra(f) B, BtoA = Hecke._as_algebra_over_center(A) @test characteristic(base_ring(B)) == characteristic(Fq) @test absolute_degree(base_ring(B)) == degree(f)*degree(Fq) @@ -124,7 +149,7 @@ end mt[2, 1, 2] = Fp(1) mt[2, 2, 1] = Fp(1) mt[2, 2, 2] = Fp(1) - A = associative_algebra(Fp, mt) + A = structure_constant_algebra(Fp, mt) B, BtoA = Hecke._as_algebra_over_center(A) @test characteristic(base_ring(B)) == characteristic(Fp) @test degree(base_ring(B)) == dim(A) @@ -134,9 +159,9 @@ end # zero algebra - A = associative_algebra(QQ, Array{QQFieldElem}(undef, 0, 0, 0)) + A = structure_constant_algebra(QQ, Array{QQFieldElem}(undef, 0, 0, 0)) @test dim(A) == 0 - A = associative_algebra(QQ, Array{QQFieldElem}(undef, 0, 0, 0), QQFieldElem[]) + A = structure_constant_algebra(QQ, Array{QQFieldElem}(undef, 0, 0, 0), QQFieldElem[]) @test dim(A) == 0 end @@ -226,7 +251,7 @@ end K, a = number_field(x^2 - 2, "a") HH = Hecke.quaternion_algebra2(2, 3) - A = associative_algebra(K, map(K, HH.mult_table)) + A = structure_constant_algebra(K, map(K, HH.mult_table)) Ps = real_places(K) @test is_split(A, Ps[1]) @test is_split(A, Ps[2])