diff --git a/experimental/LieAlgebras/docs/src/lie_algebra_homs.md b/experimental/LieAlgebras/docs/src/lie_algebra_homs.md index 5f34b186d1ca..7783198b832b 100644 --- a/experimental/LieAlgebras/docs/src/lie_algebra_homs.md +++ b/experimental/LieAlgebras/docs/src/lie_algebra_homs.md @@ -19,6 +19,7 @@ the images of the basis elements of $L_1$ or a $\dim L_1 \times \dim L_2$ matrix hom(::LieAlgebra{C}, ::LieAlgebra{C}, ::Vector{<:LieAlgebraElem{C}}; check::Bool=true) where {C<:RingElement} hom(::LieAlgebra{C}, ::LieAlgebra{C}, ::MatElem{C}; check::Bool=true) where {C<:RingElement} identity_map(::LieAlgebra) +zero_map(::LieAlgebra{C}, ::LieAlgebra{C}) where {C<:RingElement} ``` ## Functions diff --git a/experimental/LieAlgebras/docs/src/module_homs.md b/experimental/LieAlgebras/docs/src/module_homs.md index 052bfc5df0a5..0dc72759e1e2 100644 --- a/experimental/LieAlgebras/docs/src/module_homs.md +++ b/experimental/LieAlgebras/docs/src/module_homs.md @@ -20,6 +20,7 @@ or a $\dim V_1 \times \dim V_2$ matrix. hom(::LieAlgebraModule{C}, ::LieAlgebraModule{C}, ::Vector{<:LieAlgebraModuleElem{C}}; check::Bool=true) where {C<:RingElement} hom(::LieAlgebraModule{C}, ::LieAlgebraModule{C}, ::MatElem{C}; check::Bool=true) where {C<:RingElement} identity_map(::LieAlgebraModule) +zero_map(::LieAlgebraModule{C}, ::LieAlgebraModule{C}) where {C<:RingElement} ``` ## Functions @@ -48,3 +49,16 @@ compose(::LieAlgebraModuleHom{T1,T2}, ::LieAlgebraModuleHom{T2,T3}) where {T1<:L is_isomorphism(::LieAlgebraModuleHom) inv(::LieAlgebraModuleHom) ``` + +### Hom constructions +Lie algebra module homomorphisms support `+` and `-` if they have the same domain and codomain. + +```@docs +canonical_injections(::LieAlgebraModule) +canonical_injection(::LieAlgebraModule, ::Int) +canonical_projections(::LieAlgebraModule) +canonical_projection(::LieAlgebraModule, ::Int) +hom_direct_sum(::LieAlgebraModule{C}, ::LieAlgebraModule{C}, ::Matrix{<:LieAlgebraModuleHom}) where {C<:RingElement} +hom_tensor(::LieAlgebraModule{C}, ::LieAlgebraModule{C}, ::Vector{<:LieAlgebraModuleHom}) where {C<:RingElement} +hom_power(::LieAlgebraModule{C}, ::LieAlgebraModule{C}, ::LieAlgebraModuleHom) where {C<:RingElement} +``` diff --git a/experimental/LieAlgebras/src/LieAlgebraHom.jl b/experimental/LieAlgebras/src/LieAlgebraHom.jl index 5805527ecf58..cbd78e90e3a3 100644 --- a/experimental/LieAlgebras/src/LieAlgebraHom.jl +++ b/experimental/LieAlgebras/src/LieAlgebraHom.jl @@ -26,9 +26,7 @@ h.matrix = mat::dense_matrix_type(coefficient_ring(L2)) h.header = MapHeader(L1, L2) if check - for x1 in basis(L1), x2 in basis(L1) - @req h(x1) * h(x2) == h(x1 * x2) "Not a homomorphism" - end + @req is_welldefined(h) "Not a homomorphism" end return h end @@ -51,6 +49,20 @@ function matrix(h::LieAlgebraHom{<:LieAlgebra,<:LieAlgebra{C2}}) where {C2<:Ring return (h.matrix)::dense_matrix_type(C2) end +@doc raw""" + is_welldefined(h::LieAlgebraHom) -> Bool + +Return `true` if `h` is a well-defined homomorphism of Lie algebras. +This function is used internally when calling `hom` with `check=true`. +""" +function is_welldefined(h::LieAlgebraHom) + L1 = domain(h) + for x1 in basis(L1), x2 in basis(L1) + h(x1) * h(x2) == h(x1 * x2) || return false + end + return true +end + ############################################################################### # # String I/O @@ -301,3 +313,30 @@ Lie algebra morphism function identity_map(L::LieAlgebra) return hom(L, L, basis(L); check=false) end + +@doc raw""" + zero_map(L1::LieAlgebra, L2::LieAlgebra) -> LieAlgebraHom + zero_map(L::LieAlgebra) -> LieAlgebraHom + +Construct the zero map from `L1` to `L2` or from `L` to `L`. + +# Examples +```jldoctest +julia> L = special_linear_lie_algebra(QQ, 3) +Special linear Lie algebra of degree 3 + of dimension 8 +over rational field + +julia> zero_map(L) +Lie algebra morphism + from special linear Lie algebra of degree 3 over QQ + to special linear Lie algebra of degree 3 over QQ +``` +""" +function zero_map(L1::LieAlgebra{C}, L2::LieAlgebra{C}) where {C<:RingElement} + return hom(L1, L2, zero_matrix(coefficient_ring(L2), dim(L1), dim(L2)); check=false) +end + +function zero_map(L::LieAlgebra) + return zero_map(L, L) +end diff --git a/experimental/LieAlgebras/src/LieAlgebraModule.jl b/experimental/LieAlgebras/src/LieAlgebraModule.jl index 07a32fd6f9e4..933e86c11413 100644 --- a/experimental/LieAlgebras/src/LieAlgebraModule.jl +++ b/experimental/LieAlgebras/src/LieAlgebraModule.jl @@ -352,46 +352,37 @@ where _correct_ depends on the case above. function (V::LieAlgebraModule{C})( a::Vector{T} ) where {T<:LieAlgebraModuleElem{C}} where {C<:RingElement} - if is_direct_sum(V) || is_tensor_product(V) + if is_direct_sum(V) @req length(a) == length(base_modules(V)) "Length of vector does not match." @req all(i -> parent(a[i]) === base_modules(V)[i], 1:length(a)) "Incompatible modules." - if is_direct_sum(V) - return V(vcat([coefficients(x) for x in a]...)) - elseif is_tensor_product(V) - mat = zero_matrix(coefficient_ring(V), 1, dim(V)) - for (i, inds) in enumerate(get_attribute(V, :ind_map)) - mat[1, i] += prod(a[j].mat[k] for (j, k) in enumerate(inds)) - end - return LieAlgebraModuleElem{C}(V, mat) - end - elseif is_exterior_power(V) || is_symmetric_power(V) || is_tensor_power(V) - @req length(a) == get_attribute(V, :power) "Length of vector does not match power." - @req all(x -> parent(x) === base_module(V), a) "Incompatible modules." - R = coefficient_ring(V) - mat = zero_matrix(R, 1, dim(V)) - if is_exterior_power(V) - for (i, _inds) in enumerate(get_attribute(V, :ind_map)), - (inds, sgn) in permutations_with_sign(_inds) - - mat[1, i] += sgn * prod(a[j].mat[k] for (j, k) in enumerate(inds); init=one(R)) - end - elseif is_symmetric_power(V) - for (i, _inds) in enumerate(get_attribute(V, :ind_map)), - inds in unique(permutations(_inds)) - - mat[1, i] += prod(a[j].mat[k] for (j, k) in enumerate(inds); init=one(R)) - end - elseif is_tensor_power(V) - for (i, inds) in enumerate(get_attribute(V, :ind_map)) - mat[1, i] += prod(a[j].mat[k] for (j, k) in enumerate(inds); init=one(R)) - end - end - return LieAlgebraModuleElem{C}(V, mat) + return sum(inji(ai) for (ai, inji) in zip(a, canonical_injections(V)); init=zero(V)) + elseif is_tensor_product(V) + pure = get_attribute(V, :tensor_pure_function) + return pure(a)::LieAlgebraModuleElem{C} + elseif is_exterior_power(V) + pure = get_attribute(V, :exterior_pure_function) + return pure(a)::LieAlgebraModuleElem{C} + elseif is_symmetric_power(V) + pure = get_attribute(V, :symmetric_pure_function) + return pure(a)::LieAlgebraModuleElem{C} + elseif is_tensor_power(V) + pure = get_attribute(V, :tensor_pure_function) + return pure(a)::LieAlgebraModuleElem{C} else throw(ArgumentError("Invalid input.")) end end +function (V::LieAlgebraModule{C})( + a::Tuple{T,Vararg{T}} +) where {T<:LieAlgebraModuleElem{C}} where {C<:RingElement} + return V(collect(a)) +end + +function (V::LieAlgebraModule{C})(_::Tuple{}) where {C<:RingElement} + return V(LieAlgebraModuleElem{C}[]) +end + ############################################################################### # # Arithmetic operations @@ -501,10 +492,10 @@ function action(x::LieAlgebraElem{C}, v::LieAlgebraModuleElem{C}) where {C<:Ring V = parent(v) return V( sum( - cx[i] * _matrix(v) * transpose(transformation_matrix(V, i)) for + cx[i] * _matrix(v) * transformation_matrix(V, i) for i in 1:dim(parent(x)) if !iszero(cx[i]); init=zero_matrix(coefficient_ring(V), 1, dim(V))::dense_matrix_type(C), - ), # equivalent to (x * v^T)^T, since we work with row vectors + ), ) end @@ -598,8 +589,15 @@ Returns the summands or tensor factors of `V`, if `V` has been constructed as a direct sum or tensor product of modules. """ function base_modules(V::LieAlgebraModule{C}) where {C<:RingElement} - @req is_direct_sum(V) || is_tensor_product(V) "Not a direct sum or tensor product module." - return get_attribute(V, :base_modules)::Vector{LieAlgebraModule{C}} + if is_tensor_product(V) + return get_attribute(V, :tensor_product)::Vector{LieAlgebraModule{C}} + elseif is_direct_sum(V) + return get_attribute(V, :direct_sum)::Vector{LieAlgebraModule{C}} + elseif is_tensor_power(V) + return [base_module(V) for _ in 1:get_attribute(V, :power)] + else + error("Not a direct sum or tensor product module.") + end end ############################################################################### @@ -615,7 +613,7 @@ Construct the the Lie algebra module over `L` of dimension `dimV` given by `transformation_matrices` and with basis element names `s`. * `transformation_matrices`: The action of the $i$-th basis element of `L` - on some element $v$ of the constructed module is given by left multiplication + on some element $v$ of the constructed module is given by right multiplication of the matrix `transformation_matrices[i]` to the coefficient vector of $v$. * `s`: A vector of basis element names. This is `[Symbol("v_$i") for i in 1:dimV]` by default. @@ -662,7 +660,7 @@ function abstract_module( transformation_matrices = [zero_matrix(coefficient_ring(L), dimV, dimV) for _ in 1:dim(L)] for i in 1:dim(L), j in 1:dimV - transformation_matrices[i][:, j] = transpose(dense_row(struct_consts[i, j], dimV)) + transformation_matrices[i][j, :] = dense_row(struct_consts[i, j], dimV) end return LieAlgebraModule{C}(L, dimV, transformation_matrices, Symbol.(s); check) @@ -718,6 +716,11 @@ Construct the standard module of the linear Lie algebra `L`. If `L` is a Lie subalgebra of $\mathfrak{gl}_n(R)$, then the standard module is $R^n$ with the action of $L$ given by left multiplication. +!!! note + This uses the left action of $L$, and converts that to internally use the equivalent + right action. + + # Examples ```jldoctest julia> L = special_linear_lie_algebra(QQ, 3); @@ -730,7 +733,7 @@ over special linear Lie algebra of degree 3 over QQ """ function standard_module(L::LinearLieAlgebra) dim_std_V = L.n - transformation_matrices = matrix_repr_basis(L) + transformation_matrices = transpose.(matrix_repr_basis(L)) s = [Symbol("v_$(i)") for i in 1:dim_std_V] std_V = LieAlgebraModule{elem_type(coefficient_ring(L))}( L, dim_std_V, transformation_matrices, s; check=false @@ -808,19 +811,21 @@ over special linear Lie algebra of degree 3 over QQ function direct_sum( V::LieAlgebraModule{C}, Vs::LieAlgebraModule{C}... ) where {C<:RingElement} - L = base_lie_algebra(V) + Vs = [V; Vs...] + + L = base_lie_algebra(Vs[1]) @req all(x -> base_lie_algebra(x) === L, Vs) "All modules must have the same base Lie algebra." - dim_direct_sum_V = dim(V) + sum(dim, Vs; init=0) + dim_direct_sum_V = sum(dim, Vs; init=0) transformation_matrices = map(1:dim(L)) do i - block_diagonal_matrix([transformation_matrix(Vj, i) for Vj in [V, Vs...]]) + block_diagonal_matrix([transformation_matrix(Vj, i) for Vj in Vs]) end - s = if length(Vs) == 0 - symbols(V) + s = if length(Vs) == 1 + symbols(Vs[1]) else [ - Symbol("$s^($j)") for (j, Vj) in enumerate([V, Vs...]) for + Symbol("$s^($j)") for (j, Vj) in enumerate(Vs) for s in (is_standard_module(Vj) ? symbols(Vj) : (x -> "($x)").(symbols(Vj))) ] end @@ -828,7 +833,7 @@ function direct_sum( direct_sum_V = LieAlgebraModule{C}( L, dim_direct_sum_V, transformation_matrices, s; check=false ) - set_attribute!(direct_sum_V, :type => :direct_sum, :base_modules => collect([V, Vs...])) + set_attribute!(direct_sum_V, :type => :direct_sum, :direct_sum => Vs) return direct_sum_V end @@ -836,10 +841,11 @@ end direct_sum(V, Vs...) @doc raw""" - tensor_product(V::LieAlgebraModule{C}...) -> LieAlgebraModule{C} - ⊗(V::LieAlgebraModule{C}...) -> LieAlgebraModule{C} + tensor_product(Vs::LieAlgebraModule{C}...) -> LieAlgebraModule{C} + ⊗(Vs::LieAlgebraModule{C}...) -> LieAlgebraModule{C} -Construct the tensor product of the modules `V...`. +Given modules $V_1,\dots,V_n$ over the same Lie algebra $L$, +construct their tensor product $V_1 \otimes \cdots \otimes \V_n$. # Examples ```jldoctest @@ -863,28 +869,33 @@ over special linear Lie algebra of degree 3 over QQ function tensor_product( V::LieAlgebraModule{C}, Vs::LieAlgebraModule{C}... ) where {C<:RingElement} - L = base_lie_algebra(V) + Vs = [V; Vs...] + L = base_lie_algebra(Vs[1]) @req all(x -> base_lie_algebra(x) === L, Vs) "All modules must have the same base Lie algebra." + R = coefficient_ring(Vs[1]) - dim_tensor_product_V = dim(V) * prod(dim, Vs; init=1) - ind_map = collect(reverse.(ProductIterator([1:dim(Vi) for Vi in reverse([V, Vs...])]))) + dim_tensor_product_V = prod(dim, Vs; init=1) + ind_map = collect(reverse.(ProductIterator([1:dim(Vi) for Vi in reverse(Vs)]))) transformation_matrices = map(1:dim(L)) do i - ys = [transformation_matrix(Vj, i) for Vj in [V, Vs...]] + ys = [transformation_matrix(Vj, i) for Vj in Vs] sum( - reduce(kronecker_product, (j == i ? ys[j] : one(ys[j]) for j in 1:(1 + length(Vs)))) for i in 1:(1 + length(Vs)) + kronecker_product( + kronecker_product(identity_matrix(R, prod(dim, Vs[1:(j - 1)]; init=1)), ys[j]), + identity_matrix(R, prod(dim, Vs[(j + 1):end]; init=1)), + ) for j in 1:length(Vs) ) end - s = if length(Vs) == 0 - symbols(V) + s = if length(Vs) == 1 + symbols(Vs[1]) else [ Symbol(join(s, " ⊗ ")) for s in reverse.( ProductIterator([ is_standard_module(Vi) ? symbols(Vi) : (x -> "($x)").(symbols(Vi)) for - Vi in reverse([V, Vs...]) + Vi in reverse(Vs) ]) ) ] @@ -893,12 +904,43 @@ function tensor_product( tensor_product_V = LieAlgebraModule{C}( L, dim_tensor_product_V, transformation_matrices, s; check=false ) + + function pure(as::LieAlgebraModuleElem{C}...) + @req length(as) == length(Vs) "Length of vector does not match." + @req all(i -> parent(as[i]) === Vs[i], 1:length(as)) "Incompatible modules." + mat = zero_matrix(R, 1, dim_tensor_product_V) + for (i, inds) in enumerate(ind_map) + mat[1, i] += prod(as[j].mat[k]::elem_type(R) for (j, k) in enumerate(inds)) + end + return tensor_product_V(mat) + end + function pure(as::Tuple) + return pure(as...)::LieAlgebraModuleElem{C} + end + function pure(as::Vector{LieAlgebraModuleElem{C}}) + return pure(as...)::LieAlgebraModuleElem{C} + end + + function inv_pure(a::LieAlgebraModuleElem{C}) + @req parent(a) === tensor_product_V "Incompatible modules." + if iszero(a) + return Tuple(zero(Vi) for Vi in Vs) + end + nz = findall(!iszero, coefficients(a)) + @req length(nz) == 1 "Non-pure tensor product element." + @req isone(coeff(a, nz[1])) "Non-pure tensor product element." + inds = ind_map[nz[1]] + return Tuple(basis(Vi, indi) for (Vi, indi) in zip(Vs, inds)) + end + set_attribute!( tensor_product_V, :type => :tensor_product, - :base_modules => collect([V, Vs...]), - :ind_map => ind_map, + :tensor_product => Vs, + :tensor_pure_function => pure, + :tensor_pure_preimage_function => inv_pure, ) + return tensor_product_V end @@ -928,20 +970,20 @@ over special linear Lie algebra of degree 3 over QQ function exterior_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} @req k >= 0 "Non-negative exponent needed" L = base_lie_algebra(V) - dim_pow_V = binomial(dim(V), k) + R = coefficient_ring(V) + dim_E = binomial(dim(V), k) ind_map = collect(combinations(1:dim(V), k)) T = tensor_power(V, k) - basis_change_E2T = zero_matrix(coefficient_ring(V), dim(T), dim_pow_V) - basis_change_T2E = zero_matrix(coefficient_ring(V), dim_pow_V, dim(T)) - T_ind_map = get_attribute(T, :ind_map) + E_to_T_mat = zero_matrix(coefficient_ring(V), dim_E, dim(T)) + T_to_E_mat = zero_matrix(coefficient_ring(V), dim(T), dim_E) for (i, _inds) in enumerate(ind_map), (inds, sgn) in permutations_with_sign(_inds) - j = findfirst(==(inds), T_ind_map) - basis_change_E2T[j, i] = sgn//factorial(k) - basis_change_T2E[i, j] = sgn + j = 1 + sum((ind - 1) * dim(V)^(k - 1) for (k, ind) in enumerate(reverse(inds)); init=0) + E_to_T_mat[i, j] = sgn//factorial(k) + T_to_E_mat[j, i] = sgn end transformation_matrices = map(1:dim(L)) do i - basis_change_T2E * transformation_matrix(T, i) * basis_change_E2T + E_to_T_mat * transformation_matrix(T, i) * T_to_E_mat end s = if k == 0 @@ -954,11 +996,52 @@ function exterior_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} [Symbol(join((x -> "($x)").(s), " ∧ ")) for s in combinations(symbols(V), k)] end - pow_V = LieAlgebraModule{C}(L, dim_pow_V, transformation_matrices, s; check=false) + E = LieAlgebraModule{C}(L, dim_E, transformation_matrices, s; check=false) + + E_to_T = hom(E, T, E_to_T_mat; check=false) + T_to_E = hom(T, E, T_to_E_mat; check=false) + + function pure(as::LieAlgebraModuleElem{C}...) + @req length(as) == k "Length of vector does not match." + @req all(a -> parent(a) === V, as) "Incompatible modules." + mat = zero_matrix(R, 1, dim_E) + for (i, _inds) in enumerate(ind_map), (inds, sgn) in permutations_with_sign(_inds) + mat[1, i] += + sgn * prod(as[j].mat[k]::elem_type(R) for (j, k) in enumerate(inds); init=one(R)) + end + return E(mat) + end + function pure(as::Tuple) + return pure(as...)::LieAlgebraModuleElem{C} + end + function pure(as::Vector{LieAlgebraModuleElem{C}}) + return pure(as...)::LieAlgebraModuleElem{C} + end + + function inv_pure(a::LieAlgebraModuleElem{C}) + @req parent(a) === E "Incompatible modules." + if iszero(a) + return Tuple(zero(V) for _ in 1:k) + end + nz = findall(!iszero, coefficients(a)) + @req length(nz) == 1 "Non-pure exterior power element." + @req isone(coeff(a, nz[1])) "Non-pure exterior power element." + inds = ind_map[nz[1]] + return Tuple(basis(V, i) for i in inds) + end + set_attribute!( - pow_V, :type => :exterior_power, :power => k, :base_module => V, :ind_map => ind_map + E, + :type => :exterior_power, + :power => k, + :base_module => V, + :exterior_pure_function => pure, + :exterior_pure_preimage_function => inv_pure, + :embedding_tensor_power => T, + :embedding_tensor_power_embedding => E_to_T, + :embedding_tensor_power_projection => T_to_E, ) - return pow_V + return E end @doc raw""" @@ -984,20 +1067,20 @@ over special linear Lie algebra of degree 3 over QQ function symmetric_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} @req k >= 0 "Non-negative exponent needed" L = base_lie_algebra(V) - dim_pow_V = binomial(dim(V) + k - 1, k) + R = coefficient_ring(V) + dim_S = binomial(dim(V) + k - 1, k) ind_map = collect(multicombinations(1:dim(V), k)) T = tensor_power(V, k) - basis_change_S2T = zero_matrix(coefficient_ring(V), dim(T), dim_pow_V) - basis_change_T2S = zero_matrix(coefficient_ring(V), dim_pow_V, dim(T)) - T_ind_map = get_attribute(T, :ind_map) + S_to_T_mat = zero_matrix(coefficient_ring(V), dim_S, dim(T)) + T_to_S_mat = zero_matrix(coefficient_ring(V), dim(T), dim_S) for (i, _inds) in enumerate(ind_map), inds in permutations(_inds) - j = findfirst(==(inds), T_ind_map) - basis_change_S2T[j, i] += 1//factorial(k) - basis_change_T2S[i, j] = 1 + j = 1 + sum((ind - 1) * dim(V)^(k - 1) for (k, ind) in enumerate(reverse(inds)); init=0) + S_to_T_mat[i, j] += 1//factorial(k) + T_to_S_mat[j, i] = 1 end transformation_matrices = map(1:dim(L)) do i - basis_change_T2S * transformation_matrix(T, i) * basis_change_S2T + S_to_T_mat * transformation_matrix(T, i) * T_to_S_mat end s = if k == 0 @@ -1030,11 +1113,53 @@ function symmetric_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} ] end - pow_V = LieAlgebraModule{C}(L, dim_pow_V, transformation_matrices, s; check=false) + S = LieAlgebraModule{C}(L, dim_S, transformation_matrices, s; check=false) + + S_to_T = hom(S, T, S_to_T_mat; check=false) + T_to_S = hom(T, S, T_to_S_mat; check=false) + + function pure(as::LieAlgebraModuleElem{C}...) + @req length(as) == k "Length of vector does not match." + @req all(a -> parent(a) === V, as) "Incompatible modules." + mat = zero_matrix(R, 1, dim_S) + for (i, _inds) in enumerate(ind_map), inds in unique(permutations(_inds)) + mat[1, i] += prod( + as[j].mat[k]::elem_type(R) for (j, k) in enumerate(inds); init=one(R) + ) + end + return S(mat) + end + function pure(as::Tuple) + return pure(as...)::LieAlgebraModuleElem{C} + end + function pure(as::Vector{LieAlgebraModuleElem{C}}) + return pure(as...)::LieAlgebraModuleElem{C} + end + + function inv_pure(a::LieAlgebraModuleElem{C}) + @req parent(a) === S "Incompatible modules." + if iszero(a) + return Tuple(zero(V) for _ in 1:k) + end + nz = findall(!iszero, coefficients(a)) + @req length(nz) == 1 "Non-pure exterior power element." + @req isone(coeff(a, nz[1])) "Non-pure exterior power element." + inds = ind_map[nz[1]] + return Tuple(basis(V, i) for i in inds) + end + set_attribute!( - pow_V, :type => :symmetric_power, :power => k, :base_module => V, :ind_map => ind_map + S, + :type => :symmetric_power, + :power => k, + :base_module => V, + :symmetric_pure_function => pure, + :symmetric_pure_preimage_function => inv_pure, + :embedding_tensor_power => T, + :embedding_tensor_power_embedding => S_to_T, + :embedding_tensor_power_projection => T_to_S, ) - return pow_V + return S end @doc raw""" @@ -1060,14 +1185,18 @@ over special linear Lie algebra of degree 3 over QQ function tensor_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} @req k >= 0 "Non-negative exponent needed" L = base_lie_algebra(V) - dim_pow_V = dim(V)^k + R = coefficient_ring(V) + dim_T = dim(V)^k ind_map = k > 0 ? reverse.(collect(ProductIterator(1:dim(V), k))) : [Int[]] transformation_matrices = map(1:dim(L)) do i y = transformation_matrix(V, i) sum( - reduce(kronecker_product, (j == i ? y : one(y) for j in 1:k)) for i in 1:k; - init=zero_matrix(coefficient_ring(V), dim_pow_V, dim_pow_V), + kronecker_product( + kronecker_product(identity_matrix(R, dim(V)^(j - 1)), y), + identity_matrix(R, dim(V)^(k - j)), + ) for j in 1:k; + init=zero_matrix(R, dim_T, dim_T), ) end @@ -1081,9 +1210,45 @@ function tensor_power(V::LieAlgebraModule{C}, k::Int) where {C<:RingElement} [Symbol(join((x -> "($x)").(s), " ⊗ ")) for s in reverse.(ProductIterator(symbols(V), k))] end - pow_V = LieAlgebraModule{C}(L, dim_pow_V, transformation_matrices, s; check=false) + T = LieAlgebraModule{C}(L, dim_T, transformation_matrices, s; check=false) + + function pure(as::LieAlgebraModuleElem{C}...) + @req length(as) == k "Length of vector does not match." + @req all(a -> parent(a) === V, as) "Incompatible modules." + mat = zero_matrix(R, 1, dim_T) + for (i, inds) in enumerate(ind_map) + mat[1, i] += prod( + as[j].mat[k]::elem_type(R) for (j, k) in enumerate(inds); init=one(R) + ) + end + return T(mat) + end + function pure(as::Tuple) + return pure(as...)::LieAlgebraModuleElem{C} + end + function pure(as::Vector{LieAlgebraModuleElem{C}}) + return pure(as...)::LieAlgebraModuleElem{C} + end + + function inv_pure(a::LieAlgebraModuleElem{C}) + @req parent(a) === T "Incompatible modules." + if iszero(a) + return Tuple(zero(V) for _ in 1:k) + end + nz = findall(!iszero, coefficients(a)) + @req length(nz) == 1 "Non-pure tensor power element." + @req isone(coeff(a, nz[1])) "Non-pure tensor power element." + inds = ind_map[nz[1]] + return Tuple(basis(V, i) for i in inds) + end + set_attribute!( - pow_V, :type => :tensor_power, :power => k, :base_module => V, :ind_map => ind_map + T, + :type => :tensor_power, + :power => k, + :base_module => V, + :tensor_pure_function => pure, + :tensor_pure_preimage_function => inv_pure, ) - return pow_V + return T end diff --git a/experimental/LieAlgebras/src/LieAlgebraModuleHom.jl b/experimental/LieAlgebras/src/LieAlgebraModuleHom.jl index fcbe1e8e8fc0..618d42478015 100644 --- a/experimental/LieAlgebras/src/LieAlgebraModuleHom.jl +++ b/experimental/LieAlgebras/src/LieAlgebraModuleHom.jl @@ -31,9 +31,7 @@ h.matrix = mat::dense_matrix_type(coefficient_ring(V2)) h.header = MapHeader(V1, V2) if check - for x in basis(base_lie_algebra(V1)), v in basis(V1) - @req x * h(v) == h(x * v) "Not a homomorphism" - end + @req is_welldefined(h) "Not a homomorphism" end return h end @@ -58,6 +56,20 @@ function matrix( return (h.matrix)::dense_matrix_type(C2) end +@doc raw""" + is_welldefined(h::LieAlgebraModuleHom) -> Bool + +Return `true` if `h` is a well-defined homomorphism of Lie algebra modules. +This function is used internally when calling `hom` with `check=true`. +""" +function is_welldefined(h::LieAlgebraModuleHom) + V1 = domain(h) + for x in basis(base_lie_algebra(V1)), v in basis(V1) + x * h(v) == h(x * v) || return false + end + return true +end + ############################################################################### # # String I/O @@ -285,3 +297,233 @@ Lie algebra module morphism function identity_map(V::LieAlgebraModule) return hom(V, V, basis(V); check=false) end + +@doc raw""" + zero_map(V1::LieAlgebraModule, V2::LieAlgebraModule) -> LieAlgebraModuleHom + zero_map(V::LieAlgebraModule) -> LieAlgebraModuleHom + +Construct the zero map from `V1` to `V2` or from `V` to `V`. + +# Examples +```jldoctest +julia> L = special_linear_lie_algebra(QQ, 3); + +julia> V = standard_module(L) +Standard module + of dimension 3 +over special linear Lie algebra of degree 3 over QQ + +julia> zero_map(V) +Lie algebra module morphism + from standard module of dimension 3 over sl_3 + to standard module of dimension 3 over sl_3 +``` +""" +function zero_map(V1::LieAlgebraModule{C}, V2::LieAlgebraModule{C}) where {C<:RingElement} + return hom(V1, V2, zero_matrix(coefficient_ring(V2), dim(V1), dim(V2)); check=false) +end + +function zero_map(V::LieAlgebraModule) + return zero_map(V, V) +end + +############################################################################### +# +# Hom constructions +# +############################################################################### + +function Base.:-(h::LieAlgebraModuleHom) + return hom(domain(h), codomain(h), -matrix(h); check=false) +end + +function Base.:+( + h1::LieAlgebraModuleHom{T1,T2}, h2::LieAlgebraModuleHom{T1,T2} +) where {T1<:LieAlgebraModule,T2<:LieAlgebraModule} + @req domain(h1) === domain(h2) "Maps must have the same domain" + @req codomain(h1) === codomain(h2) "Maps must have the same codomain" + return hom(domain(h1), codomain(h1), matrix(h1) + matrix(h2); check=false) +end + +function Base.:-( + h1::LieAlgebraModuleHom{T1,T2}, h2::LieAlgebraModuleHom{T1,T2} +) where {T1<:LieAlgebraModule,T2<:LieAlgebraModule} + @req domain(h1) === domain(h2) "Maps must have the same domain" + @req codomain(h1) === codomain(h2) "Maps must have the same codomain" + return hom(domain(h1), codomain(h1), matrix(h1) - matrix(h2); check=false) +end + +@doc raw""" + canonical_injections(V::LieAlgebraModule) -> Vector{LieAlgebraModuleHom} + +Return the canonical injections from all components into $V$ +where $V$ has been constructed as $V_1 \oplus \cdot \oplus V_n$. +""" +function canonical_injections(V::LieAlgebraModule) + @req is_direct_sum(V) "Module must be a direct sum" + return [canonical_injection(V, i) for i in 1:length(base_modules(V))] +end + +@doc raw""" + canonical_injection(V::LieAlgebraModule, i::Int) -> LieAlgebraModuleHom + +Return the canonical injection $V_i \to V$ +where $V$ has been constructed as $V_1 \oplus \cdot \oplus V_n$. +""" +function canonical_injection(V::LieAlgebraModule, i::Int) + @req is_direct_sum(V) "Module must be a direct sum" + Vs = base_modules(V) + @req 0 < i <= length(Vs) "Index out of bound" + j = sum(dim(Vs[l]) for l in 1:(i - 1); init=0) + emb = hom(Vs[i], V, [basis(V, l + j) for l in 1:dim(Vs[i])]; check=false) + return emb +end + +@doc raw""" + canonical_projections(V::LieAlgebraModule) -> Vector{LieAlgebraModuleHom} + +Return the canonical projections from $V$ to all components +where $V$ has been constructed as $V_1 \oplus \cdot \oplus V_n$. +""" +function canonical_projections(V::LieAlgebraModule) + @req is_direct_sum(V) "Module must be a direct sum" + return [canonical_projection(V, i) for i in 1:length(base_modules(V))] +end + +@doc raw""" + canonical_projection(V::LieAlgebraModule, i::Int) -> LieAlgebraModuleHom + +Return the canonical projection $V \to V_i$ +where $V$ has been constructed as $V_1 \oplus \cdot \oplus V_n$. +""" +function canonical_projection(V::LieAlgebraModule, i::Int) + @req is_direct_sum(V) "Module must be a direct sum" + Vs = base_modules(V) + @req 0 < i <= length(Vs) "Index out of bound" + j = sum(dim(Vs[l]) for l in 1:(i - 1); init=0) + proj = hom( + V, + Vs[i], + [ + [zero(Vs[i]) for l in 1:j] + basis(Vs[i]) + [zero(Vs[i]) for l in (j + dim(Vs[i]) + 1):dim(V)] + ]; + check=false, + ) + return proj +end + +@doc raw""" + hom_direct_sum(V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Matrix{<:LieAlgebraModuleHom}) -> LieAlgebraModuleHom + hom_direct_sum(V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Vector{<:LieAlgebraModuleHom}) -> LieAlgebraModuleHom + +Given modules `V` and `W` which are direct sums with `r` respective `s` summands, +say $M = M_1 \oplus \cdots \oplus M_r$, $N = N_1 \oplus \cdots \oplus N_s$, and given a $r \times s$ matrix +`hs` of homomorphisms $h_{ij} : V_i \to W_j$, return the homomorphism +$V \to W$ with $ij$-components $h_{ij}$. + +If `hs` is a vector, then it is interpreted as a diagonal matrix. +""" +function hom_direct_sum( + V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Matrix{<:LieAlgebraModuleHom} +) where {C<:RingElement} + @req is_direct_sum(V) "First module must be a direct sum" + @req is_direct_sum(W) "Second module must be a direct sum" + Vs = base_modules(V) + Ws = base_modules(W) + @req length(Vs) == size(hs, 1) "Length mismatch" + @req length(Ws) == size(hs, 2) "Length mismatch" + @req all( + domain(hs[i, j]) === Vs[i] && codomain(hs[i, j]) === Ws[j] for i in 1:size(hs, 1), + j in 1:size(hs, 2) + ) "Domain/codomain mismatch" + + Winjs = canonical_injections(W) + Vprojs = canonical_projections(V) + function map_basis(v) + return sum( + Winjs[j](sum(hs[i, j](Vprojs[i](v)) for i in 1:length(Vs); init=zero(Ws[j]))) for + j in 1:length(Ws); + init=zero(W), + ) + end + return hom(V, W, map(map_basis, basis(V)); check=false) +end + +function hom_direct_sum( + V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Vector{<:LieAlgebraModuleHom} +) where {C<:RingElement} + @req is_direct_sum(V) "First module must be a direct sum" + @req is_direct_sum(W) "Second module must be a direct sum" + Vs = base_modules(V) + Ws = base_modules(W) + @req length(Vs) == length(Ws) == length(hs) "Length mismatch" + @req all(i -> domain(hs[i]) === Vs[i] && codomain(hs[i]) === Ws[i], 1:length(hs)) "Domain/codomain mismatch" + + return hom(V, W, diagonal_matrix(matrix.(hs)); check=false) +end + +@doc raw""" + hom_tensor(V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Vector{<:LieAlgebraModuleHom}) -> LieAlgebraModuleHom + +Given modules `V` and `W` which are tensor products with the same number of factors, +say $V = V_1 \otimes \cdots \otimes V_r$, $W = W_1 \otimes \cdots \otimes W_r$, +and given a vector `hs` of homomorphisms $a_i : V_i \to W_i$, return +$a_1 \otimes \cdots \otimes a_r$. + +This works for $r$th tensor powers as well. +""" +function hom_tensor( + V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, hs::Vector{<:LieAlgebraModuleHom} +) where {C<:RingElement} + @req is_tensor_product(V) || is_tensor_power(V) "First module must be a tensor product or power" + @req is_tensor_product(W) || is_tensor_power(W) "Second module must be a tensor product or power" + Vs = base_modules(V) + Ws = base_modules(W) + @req length(Vs) == length(Ws) == length(hs) "Length mismatch" + @req all(i -> domain(hs[i]) === Vs[i] && codomain(hs[i]) === Ws[i], 1:length(hs)) "Domain/codomain mismatch" + + mat = reduce(kronecker_product, [matrix(hi) for hi in hs]) + return hom(V, W, mat; check=false) +end + +@doc raw""" + hom_power(V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, h::LieAlgebraModuleHom) -> LieAlgebraModuleHom + +Given modules `V` and `W` which are exterior/symmetric/tensor powers of the same kind with the same exponent, +say, e.g., $V = S^k V'$, $W = S^k W'$, and given a homomorphism $h : V' \to W'$, return +$S^k h: V \to W$ (analogous for other types of powers). +""" +function hom_power( + V::LieAlgebraModule{C}, W::LieAlgebraModule{C}, h::LieAlgebraModuleHom +) where {C<:RingElement} + if is_exterior_power(V) + @req is_exterior_power(W) "First module is an exterior power, but second module is not" + type = :ext + elseif is_symmetric_power(V) + @req is_symmetric_power(W) "First module is a symmetric power, but second module is not" + type = :sym + elseif is_tensor_power(V) + @req is_tensor_power(W) "First module is a tensor power, but second module is not" + type = :tensor + else + throw(ArgumentError("First module must be a power module")) + end + @req get_attribute(V, :power) == get_attribute(W, :power) "Exponent mismatch" + @req domain(h) === base_module(V) && codomain(h) === base_module(W) "Domain/codomain mismatch" + + TV = type == :tensor ? V : get_attribute(V, :embedding_tensor_power) + TW = type == :tensor ? W : get_attribute(W, :embedding_tensor_power) + + mat = reduce(kronecker_product, [matrix(h) for _ in 1:get_attribute(V, :power)]) + TV_to_TW = hom(TV, TW, mat; check=false) + + if type == :tensor + return TV_to_TW + else + V_to_TV = get_attribute(V, :embedding_tensor_power_embedding) + TW_to_W = get_attribute(W, :embedding_tensor_power_projection) + return V_to_TV * TV_to_TW * TW_to_W + end +end diff --git a/experimental/LieAlgebras/src/LieAlgebras.jl b/experimental/LieAlgebras/src/LieAlgebras.jl index 8e4e9f934d43..18a2daf3bb66 100644 --- a/experimental/LieAlgebras/src/LieAlgebras.jl +++ b/experimental/LieAlgebras/src/LieAlgebras.jl @@ -4,6 +4,11 @@ using ..Oscar import Oscar: GAPWrap, IntegerUnion, MapHeader +import Hecke: + canonical_injection, canonical_injections, canonical_projection, canonical_projections # Until https://github.com/thofma/Hecke.jl/pull/1196 is available +export canonical_injection, + canonical_injections, canonical_projection, canonical_projections # Until https://github.com/thofma/Hecke.jl/pull/1196 is available + # not importet in Oscar using AbstractAlgebra: CacheDictType, ProductIterator, get_cached!, ordinal_number_string @@ -31,6 +36,7 @@ import ..Oscar: gen, gens, hom, + hom_tensor, identity_map, ideal, image, @@ -41,6 +47,7 @@ import ..Oscar: is_perfect, is_simple, is_solvable, + is_welldefined, kernel, matrix, ngens, @@ -50,6 +57,7 @@ import ..Oscar: symbols, symmetric_power, tensor_product, + zero_map, ⊕, ⊗ @@ -76,6 +84,8 @@ export derived_algebra export exterior_power export general_linear_lie_algebra export highest_weight_module +export hom_direct_sum +export hom_power export is_direct_sum export is_dual export is_exterior_power @@ -135,6 +145,8 @@ export derived_algebra export exterior_power export general_linear_lie_algebra export highest_weight_module +export hom_direct_sum +export hom_power export is_direct_sum export is_dual export is_exterior_power @@ -153,3 +165,6 @@ export symmetric_power export tensor_power export trivial_module export universal_enveloping_algebra + +export canonical_injection, + canonical_injections, canonical_projection, canonical_projections # Until https://github.com/thofma/Hecke.jl/pull/1196 is available diff --git a/experimental/LieAlgebras/test/LieAlgebraHom-test.jl b/experimental/LieAlgebras/test/LieAlgebraHom-test.jl index fdd4b9eee169..e9abcf4343df 100644 --- a/experimental/LieAlgebras/test/LieAlgebraHom-test.jl +++ b/experimental/LieAlgebras/test/LieAlgebraHom-test.jl @@ -7,11 +7,7 @@ @test domain(h) == L1 @test codomain(h) == L2 @test matrix(h) == matrix(QQ, [0 1 0 0; 0 0 1 0; 1 0 0 -1]) - for x1 in basis(L1) - for x2 in basis(L1) - @test h(x1 * x2) == h(x1) * h(x2) - end - end + @test is_welldefined(h) end @testset "Image and kernel" begin @@ -37,6 +33,33 @@ @test h(ideal(L, [x2, x3, x4])) == sub(L, [x3]) end + @testset "Identity and zero map" begin + L1 = special_linear_lie_algebra(QQ, 3) + L2 = general_linear_lie_algebra(QQ, 3) + + h = identity_map(L1) + @test domain(h) == L1 + @test codomain(h) == L1 + @test matrix(h) == identity_matrix(QQ, dim(L1)) + @test is_welldefined(h) + for x in basis(L1) + @test h(x) == x + end + @test image(h) == sub(L1) + @test kernel(h) == ideal(L1, [zero(L1)]) + + h = zero_map(L1, L2) + @test domain(h) == L1 + @test codomain(h) == L2 + @test matrix(h) == zero_matrix(QQ, dim(L1), dim(L2)) + @test is_welldefined(h) + for x in basis(L1) + @test h(x) == zero(L2) + end + @test image(h) == sub(L2, [zero(L2)]) + @test kernel(h) == ideal(L1) + end + @testset "Composition" begin L1 = special_linear_lie_algebra(QQ, 2) L2 = special_linear_lie_algebra(QQ, 3) @@ -76,6 +99,8 @@ @test is_isomorphism(identity_map(L1)) @test identity_map(L1) == inv(identity_map(L1)) + + @test !is_isomorphism(zero_map(L1)) end @testset "Hum72, Exercise 2.10" begin diff --git a/experimental/LieAlgebras/test/LieAlgebraModule-test.jl b/experimental/LieAlgebras/test/LieAlgebraModule-test.jl index 7c5b299fbb46..512d632ce0c1 100644 --- a/experimental/LieAlgebras/test/LieAlgebraModule-test.jl +++ b/experimental/LieAlgebras/test/LieAlgebraModule-test.jl @@ -324,26 +324,36 @@ end type_V = module_type_bools(V) for k in 1:3 - pow_V = exterior_power(V, k) + E = exterior_power(V, k) @test type_V == module_type_bools(V) # construction of pow_V should not change type of V - @test base_module(pow_V) === V - @test dim(pow_V) == binomial(dim(V), k) - @test length(repr(pow_V)) < 10^4 # outputs tend to be excessively long due to recursion + @test base_module(E) === V + @test dim(E) == binomial(dim(V), k) + @test length(repr(E)) < 10^4 # outputs tend to be excessively long due to recursion - @test module_type_bools(pow_V) == (false, false, false, false, true, false, false) # exterior_power + @test module_type_bools(E) == (false, false, false, false, true, false, false) # exterior_power if k == 1 x = L(rand(-10:10, dim(L))) a = V(rand(-10:10, dim(V))) - @test pow_V([x * a]) == x * pow_V([a]) + @test E([x * a]) == x * E([a]) elseif k == 2 a = V(rand(-10:10, dim(V))) b = V(rand(-10:10, dim(V))) - @test !iszero(pow_V([a, b])) - @test iszero(pow_V([a, b]) + pow_V([b, a])) - @test !iszero(pow_V([a, b]) - pow_V([b, a])) - @test iszero(pow_V([a, a])) + @test !iszero(E([a, b])) + @test iszero(E([a, b]) + E([b, a])) + @test !iszero(E([a, b]) - E([b, a])) + @test iszero(E([a, a])) end + + T = get_attribute(E, :embedding_tensor_power) + E_to_T = get_attribute(E, :embedding_tensor_power_embedding) + T_to_E = get_attribute(E, :embedding_tensor_power_projection) + @test T == tensor_power(V, k) + @test domain(E_to_T) === E + @test codomain(E_to_T) === T + @test domain(T_to_E) === T + @test codomain(T_to_E) === E + @test compose(E_to_T, T_to_E) == identity_map(E) end end @@ -353,37 +363,47 @@ end type_V = module_type_bools(V) for k in 1:3 - pow_V = symmetric_power(V, k) + S = symmetric_power(V, k) @test type_V == module_type_bools(V) # construction of pow_V should not change type of V - @test base_module(pow_V) === V - @test dim(pow_V) == binomial(dim(V) + k - 1, k) - @test length(repr(pow_V)) < 10^4 # outputs tend to be excessively long due to recursion + @test base_module(S) === V + @test dim(S) == binomial(dim(V) + k - 1, k) + @test length(repr(S)) < 10^4 # outputs tend to be excessively long due to recursion - @test module_type_bools(pow_V) == (false, false, false, false, false, true, false) # symmetric_power + @test module_type_bools(S) == (false, false, false, false, false, true, false) # symmetric_power if k == 1 x = L(rand(-10:10, dim(L))) a = V(rand(-10:10, dim(V))) - @test pow_V([x * a]) == x * pow_V([a]) + @test S([x * a]) == x * S([a]) elseif k == 2 a = V(rand(-10:10, dim(V))) b = V(rand(-10:10, dim(V))) - @test !iszero(pow_V([a, b])) - @test !iszero(pow_V([a, b]) + pow_V([b, a])) - @test iszero(pow_V([a, b]) - pow_V([b, a])) - @test !iszero(pow_V([a, a])) + @test !iszero(S([a, b])) + @test !iszero(S([a, b]) + S([b, a])) + @test iszero(S([a, b]) - S([b, a])) + @test !iszero(S([a, a])) end + + T = get_attribute(S, :embedding_tensor_power) + S_to_T = get_attribute(S, :embedding_tensor_power_embedding) + T_to_S = get_attribute(S, :embedding_tensor_power_projection) + @test T == tensor_power(V, k) + @test domain(S_to_T) === S + @test codomain(S_to_T) === T + @test domain(T_to_S) === T + @test codomain(T_to_S) === S + @test compose(S_to_T, T_to_S) == identity_map(S) end end @testset "tensor_power" begin L = special_orthogonal_lie_algebra(QQ, 4) V = standard_module(L) - type_V = module_type_bools(V) + T = module_type_bools(V) for k in 1:3 pow_V = tensor_power(V, k) - @test type_V == module_type_bools(V) # construction of pow_V should not change type of V + @test T == module_type_bools(V) # construction of pow_V should not change type of V @test base_module(pow_V) === V @test dim(pow_V) == dim(V)^k @test length(repr(pow_V)) < 10^4 # outputs tend to be excessively long due to recursion diff --git a/experimental/LieAlgebras/test/LieAlgebraModuleHom-test.jl b/experimental/LieAlgebras/test/LieAlgebraModuleHom-test.jl index ba404dd09b5c..5cd052799f3e 100644 --- a/experimental/LieAlgebras/test/LieAlgebraModuleHom-test.jl +++ b/experimental/LieAlgebras/test/LieAlgebraModuleHom-test.jl @@ -17,11 +17,7 @@ 0 0 1 0 0 0 ], ) - for x in basis(L) - for v in basis(V1) - @test h(x * v) == x * h(v) - end - end + @test is_welldefined(h) end @testset "Image and kernel" begin @@ -37,6 +33,35 @@ @test image(h, v6) == h(v6) == v3 end + @testset "Identity and zero map" begin + L = special_linear_lie_algebra(QQ, 3) + stdV = standard_module(L) + V1 = symmetric_power(stdV, 3) + V2 = exterior_power(stdV, 2) + + h = identity_map(V1) + @test domain(h) == V1 + @test codomain(h) == V1 + @test matrix(h) == identity_matrix(QQ, dim(V1)) + @test is_welldefined(h) + for v in basis(V1) + @test h(v) == v + end + # @test image(h) == ... + # @test kernel(h) == ... + + h = zero_map(V1, V2) + @test domain(h) == V1 + @test codomain(h) == V2 + @test matrix(h) == zero_matrix(QQ, dim(V1), dim(V2)) + @test is_welldefined(h) + for v in basis(V1) + @test h(v) == zero(V2) + end + # @test image(h) == ... + # @test kernel(h) == ... + end + @testset "Composition" begin L = special_orthogonal_lie_algebra(QQ, 3) V1 = standard_module(L) @@ -55,6 +80,7 @@ for x in basis(V1) @test h(x) == g(f(x)) end + @test is_welldefined(h) end @testset "Inverses" begin @@ -69,8 +95,101 @@ h = hom(V, V, [v4, v5, v6, v1, v2, v3]) @test is_isomorphism(h) + @test is_welldefined(inv(h)) @test h == inv(h) @test identity_map(V) == compose(h, inv(h)) @test identity_map(V) == compose(inv(h), h) end + + @testset "Direct sum constructions" begin + L = special_orthogonal_lie_algebra(QQ, 4) + Vs = [ + standard_module(L), dual(standard_module(L)), exterior_power(standard_module(L), 2) + ] + V = direct_sum(Vs...) + + @test canonical_injections(V) == [canonical_injection(V, i) for i in 1:length(Vs)] + @test canonical_projections(V) == [canonical_projection(V, i) for i in 1:length(Vs)] + + @test all(is_welldefined, canonical_injections(V)) + @test all(is_welldefined, canonical_projections(V)) + + # direct sum universal properties + for i in 1:length(Vs) + @test canonical_injection(V, i) * canonical_projection(V, i) == identity_map(Vs[i]) + end + @test sum( + proj * inj for (proj, inj) in zip(canonical_projections(V), canonical_injections(V)) + ) == identity_map(V) + end + + @testset "hom_direct_sum" begin + L = special_orthogonal_lie_algebra(QQ, 4) + V11 = standard_module(L) + V12 = dual(standard_module(L)) + V21 = exterior_power(standard_module(L), 2) + V22 = dual(dual(standard_module(L))) + V1 = direct_sum(V11, V12) + V2 = direct_sum(V21, V22) + h11 = hom(V11, V21, [zero(V21) for _ in basis(V11)]) + h12 = hom(V11, V22, basis(V22)) + h21 = hom(V12, V21, [zero(V21) for _ in basis(V12)]) + h22 = hom(V12, V22, [zero(V22) for _ in basis(V12)]) + + h = hom_direct_sum(V1, V2, [h11 h12; h21 h22]) + @test domain(h) == V1 + @test codomain(h) == V2 + @test is_welldefined(h) + #@test image(h) == image(canonical_injection(V2, 2)) + #@test kernel(h) == kernel(canonical_injjection(V1, 1)) + end + + @testset "hom_tensor" begin + L = special_orthogonal_lie_algebra(QQ, 4) + V11 = standard_module(L) + V12 = dual(standard_module(L)) + V21 = dual(dual(standard_module(L))) + V22 = exterior_power(standard_module(L), 2) + V1 = tensor_product(V11, V12) + V2 = tensor_product(V21, V22) + h1 = hom(V11, V21, basis(V21)) + h2 = hom(V12, V22, [zero(V22) for _ in basis(V12)]) + + h = hom_tensor(V1, V2, [h1, h2]) + @test domain(h) == V1 + @test codomain(h) == V2 + @test is_welldefined(h) + + V11 = standard_module(L) + V12 = dual(standard_module(L)) + V2i = dual(dual(standard_module(L))) + V1 = tensor_product(V11, V12) + V2 = tensor_power(V2i, 2) + h1 = hom(V11, V2i, basis(V2i)) + h2 = hom(V12, V2i, [zero(V2i) for _ in basis(V12)]) + + h = hom_tensor(V1, V2, [h1, h2]) + @test domain(h) == V1 + @test codomain(h) == V2 + @test is_welldefined(h) + end + + @testset "hom_power ($f_power)" for f_power in + [exterior_power, symmetric_power, tensor_power] + for k in 1:3 + L = special_orthogonal_lie_algebra(QQ, 4) + Vb = standard_module(L) + Wb = dual(dual(standard_module(L))) + V = f_power(Vb, k) + W = f_power(Wb, k) + hb = hom(Vb, Wb, [2 * b for b in basis(Wb)]) + + h = hom_power(V, W, hb) + @test domain(h) == V + @test codomain(h) == W + @test is_welldefined(h) + vs = [Vb(rand(-10:10, dim(Vb))) for _ in 1:k] + @test h(V(vs)) == W(hb.(vs)) + end + end end