Oscar and DynamicaPolynomials: Convert polynomial type for basis of invariant rings

I would like to know if there is a way to convert a type nf_elem to a polynomial. My goal with this code is to find all minimal monomials of degree d that obey a rotational symmetry, so I can directly construct a polynomial that is the sum of tunable coefficients weighting each of these minimal monomials. (The point of writing it in the following format is that it is easier to generalize for other problems at a later date, and it should be faster than doing brute force large-scale matrix calculations when I increase the size of R90.) In particular, I have developed an invariant ring and identified its basis for degrees 1-4 in the following manner:

using Oscar
using LinearAlgebra
using DynamicPolynomials

IntZeros = zeros(Int64,2,2);
R90 = vcat( hcat( Matrix(1I,2,2), IntZeros, IntZeros),
                 hcat( IntZeros, [0 -1;1 0], IntZeros),
                 hcat( IntZeros, IntZeros, [0 -1;1 0]) )

# Invariant terms of degree d
K, x = CyclotomicField(m, "x")
M90 = matrix(K, R90)
GM90 = matrix_group(M90)
IRm90 = invariant_ring(GM90)
basis1_char = Oscar.basis(IRm90,1)
basis2_char = Oscar.basis(IRm90,2)
basis3_char = Oscar.basis(IRm90,3)
basis4_char = Oscar.basis(IRm90,4)

I spent a long time reading through the documentation for Oscar to determine if there was some sort of function such as evaluate or convert that would allow me to convert the resulting entries in the basisd_char vector to polynomials. This, I discovered, either it is very well-obscured because the nf_elem type is being accessed in a C library hidden deep in the code, or it is not possible. I’m not an algebraist, so pardon if this was not the best way to approach this problem (particularly if there is a way to use the AbstractAlgebra.jl package instead); if there is a much better approach please let me know, thanks.

Sorry for the missing documentation. What you want is possible as follows:

julia> K, x = CyclotomicField(15, "x");

julia> Qt = parent(defining_polynomial(K));

julia> @polyvar t

julia> poly = Qt(x^2 + 1//2*x + 3);

julia> sum(Rational{BigInt}(coeff(poly, i)) * t^i for i in 0:Oscar.degree(poly))
3//1 + 1//2t + t²

Thanks for the reply and please pardon my tardiness in responding, I’ve been moving and traveling a lot lately. This solution would be fine, if my rings were not graded algebras. The CyclotomicGroup structure is used to set up a graded algebra, specifically the standard polynomial ring of degree m (in your example, m = 15). One can find the invariant polynomials following the method I outlined in the OG post, and this yields invariant polynomials in the variables x[1], x[2], …, x[m]. In my particular case, if I choose m = 4, and I want polynomials invariant under 90 degree rotation for each pair of variables (x[1], x[2]) and (x[3],x[4]), then the corresponding full minimal example is:

julia > using Oscar
julia > using DynamicPolynomials
julia > IntZeros = zeros(Int64,2,2);
julia > R90 = vcat( hcat( [0 -1;1 0], IntZeros),
                    hcat( IntZeros, [0 -1;1 0]) );
julia > K, x = CyclotomicField(4, "x");
julia > M90 = matrix(K, R90);
julia > GM90 = matrix_group(M90);
julia > IRm90 = invariant_ring(GM90);
julia > basis2_char = Oscar.basis(IRm90,2)
x[1]*x[3] + x[2]*x[4]
x[1]*x[4] - x[2]*x[3]

The closest analogy to the poly you have written in your example would be basis2_char[1].f, but this involves multiple “variables” and even if it involved only one variable it is not of the correct type at all. Once more, I am not an algebraist and am not totally familiar with how all these different methods fit together yet, so please do correct my understanding where it is incorrect.

In the meantime, whatever is happening under the hood with the code defining polynomial might be helpful for me to see how it is converting these elements from type nf_elem to something with a placeholder; however I am also having trouble finding the root code for this in oscar.jl. If I can track this down, maybe I can find a way to replace them with multiple place holders. In the meantime, I will keep looking at this, and if I come up with an answer before I hear back I will post it here. Thanks.

I think I misunderstood your first question.

  1. Do you want to turn the x[1]^2+x[2]^2 etc. into dynamic polynomials?
  2. What do you want to do if there if there is some m-th root of unity appearing as a coefficient? Do you want to replace this by the numerical value \approx e^{\frac{2\pi i}m}?
  1. Yes
  2. I don’t care if there is a coefficient on invariant polynomials, so long as I know how the coefficients scale relative to one another, i.e. x[1]^2 + x[2]^2 is as good as 0.5*x[1]^2 + 0.5*x[2]^2. There should be no mixture of real and imaginary coefficients in the case that I am considering. If for some reason there are, then either the real and imaginary parts have to satisfy the symmetry separately or it will be excluded from the basis. That’s an if-loop, should be ok to do after the conversion.

Here is some hack to turn one of these polynomials into a “dynamic polynomial”:

julia> map_coeff = c -> evalpoly(cispi(2/4), Rational{BigInt}.(collect(Oscar.coefficients(parent(defining_polynomial(K))(c)))))

julia> f = basis2_char[4]
x[3]^2 + x[4]^2

julia> z, = @polyvar z[1:4];

julia> ff = 0*z[1]; for (c, e) in zip(Oscar.coefficients(f), Oscar.exponents(f))
         ff += map_coeff(c)*prod(z[i]^e[i] for i in 1:4)

julia> ff
z₄² + z₃²
1 Like

That does exactly what I want it to do, thank you very much for helping me with the CS here. At some point, I will be testing this time-wise against a version where one just writes the matrices to select the invariant polynomials by hand (a good method, but not generalizable for many different types of matrices). Hopefully, this approach is not only more general but comparable in time (or only a little slower?) as well, particularly as the matrices get large, but I do not know off the top of my head. If you have intuition on this you would be willing to offer, would love to know. Otherwise, thank you again.