Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial bindings for generic rings #218

Merged
merged 18 commits into from
Sep 15, 2024

Conversation

oscarbenjamin
Copy link
Collaborator

This needs a lot more thought about how to design things but I thought it would be useful to put this up and maybe just merge as is on the basis that it is unstable/private.

The changes here are:

  • Include all the binding declarations for gr_* types and functions.
  • Add a new module flint.types._gr.
  • Add contexts for all scalar types.
  • Add a gr type that can represent the elements of any of the scalar contexts.

What I haven't added are polynomials and matrices (gr_mat, gr_poly, gr_mpoly) and also series (gr_series). I also haven't added many functions beyond arithmetic.

I don't think that the design here is really exactly what we would want because it uses the same gr type at the Python level for everything (fmpz, arb, ...) whereas I think that we want these to be different types at the Python level with different methods/attributes. More work is needed to figure out the right design but I think it might be like this:

  • Every type in Flint should have a context.
  • The contexts should all be based on the gr_ctx contexts and designed around the same structure as the generic rings code.
  • There should be different types for each domain with different attributes and methods but possibly the generic gr_ functions could be made available in a base class from which non-generic types like fmpz etc inherit.
  • However some types like poly, mpoly etc become more generic i.e. we maybe don't need a different type for each kind of matrix etc.
  • Some functions that are specific to e.g. matrices of a given domain could instead become context methods or functions rather than instance methods so that e.g. all matrices have the same methods and attributes.

Introducing a context based interface provides an opportunity to change the interface of many things and to solve many problems simultaneously:

  1. Can fix the non-prime modulus problem for nmod.
  2. Make a uniform interface for constructing objects of all types.
  3. Have a well designed coercion system.
  4. Add many new types easily
  5. Use better generic tests for more economic use of writing test code.
  6. Avoid global variables like the precision in arb or cap in series.

And of course the big one is that the generic rings code allows constructing new rings like a ring of matrices of multivariate polynomials etc.

I haven't added tests etc because I expect the design here to change a lot but I think it is worth just adding this as a private/unstable module so that people can test it out and we can work out the full design over time. I think that it would be worth fleshing this out with matrices, polynomials, special functions etc, figuring all the types, conversions and coercions and so on before making it "public". It can also be fine for people to use this as long as they understand that the interfaces are subject to change in future.

These are the scalar types that can be created through the generic system with this PR:

In [1]: from flint.types import _gr

In [2]: [name for name in dir(_gr) if not name.startswith('_')]
Out[2]: 
['gr',
 'gr_complex_acb_ctx',
 'gr_complex_algebraic_ca_ctx',
 'gr_complex_ca_ctx',
 'gr_complex_extended_ca_ctx',
 'gr_complex_float_acf_ctx',
 'gr_complex_qqbar_ctx',
 'gr_ctx',
 'gr_fexpr_ctx',
 'gr_fmpq_ctx',
 'gr_fmpz_ctx',
 'gr_fmpz_mod_ctx',
 'gr_fmpzi_ctx',
 'gr_fq_ctx',
 'gr_fq_nmod_ctx',
 'gr_fq_zech_ctx',
 'gr_matrix_domain_ctx',
 'gr_matrix_ring_ctx',
 'gr_matrix_space_ctx',
 'gr_mpoly_ctx',
 'gr_nf_ctx',
 'gr_nf_fmpz_poly_ctx',
 'gr_nmod_ctx',
 'gr_poly_ctx',
 'gr_real_algebraic_ca_ctx',
 'gr_real_arb_ctx',
 'gr_real_ca_ctx',
 'gr_real_float_arf_ctx',
 'gr_real_qqbar_ctx',
 'gr_scalar_ctx']

Note that some of these are just abstract classes e.g. there are no polynomials or matrices here. Some of these duplicate things that already exist like fmpz, nmod etc.

Some examples of things this allows that are not currently accessible through python-flint are:

  1. Gaussian integers:
In [3]: R = _gr.gr_fmpzi_ctx

In [4]: R
Out[4]: gr_fmpzi_ctx

In [5]: R.i()
Out[5]: I

In [6]: R.i() + 2
Out[6]: (2+I)

In [7]: _**2
Out[7]: (3+4*I)

In [10]: a = 1 + R.i()

In [11]: a
Out[11]: (1+I)

In [13]: (2*a).gcd(4*a)
Out[13]: (2+2*I)

Having contexts for all objects opens the possibility that it could be R.gcd(2*a, 4*a) or even that it could be gcd(2*a, 4*a) where gcd is a function that can find the context.

  1. Algebraic number fields:
In [20]: x = fmpq_poly([0, 1])

In [21]: R = _gr.gr_nf_ctx.new(x**2 - 2)

In [22]: R
Out[22]: gr_nf_ctx(x^2 + (-2))

In [24]: R.gen()
Out[24]: a

In [25]: a = R.gen()

In [26]: 1 + a + a**2
Out[26]: a+3
  1. QQbar:
In [33]: R = _gr.gr_real_qqbar_ctx.new()

In [36]: R
Out[36]: gr_real_qqbar_ctx(-1, -1)

In [34]: R(2).sqrt()
Out[34]: Root a = 1.41421 of a^2-2

In [35]: R(2).sqrt() ** 3
Out[35]: Root a = 2.82843 of a^2-8
  1. Calcium
In [37]: R = _gr.gr_complex_ca_ctx.new()

In [38]: R
Out[38]: gr_complex_ca_ctx({})

In [39]: R(123).sqrt()
Out[39]: 11.0905 {a where a = 11.0905 [a^2-123=0]}
  1. Symbolic expressions
In [40]: R = _gr.gr_fexpr_ctx.new()

In [41]: R
Out[41]: gr_fexpr_ctx

In [42]: R(123)/2 + 1
Out[42]: Add(Div(123, 2), 1)
  1. We already have arb, arf etc but currently they use global precision making them unsuitable for SymPy/mpmath. Here we can have per-context precision and no global variables:
In [44]: R1 = _gr.gr_real_arb_ctx.new(10)

In [45]: R2 = _gr.gr_real_arb_ctx.new(20)

In [46]: R1(2).sqrt()
Out[46]: [1.41 +/- 6.02e-3]

In [47]: R2(2).sqrt()
Out[47]: [1.41421 +/- 5.09e-6]

@oscarbenjamin
Copy link
Collaborator Author

I've just added the gr poly, mpoly and series types. This gives e.g. multivariate polynomials over the Gaussian integers:

In [4]: from flint.types import _gr

In [5]: R = _gr.gr_gr_mpoly_ctx.new(_gr.gr_fmpzi_ctx, 2)

In [6]: R
Out[6]: gr_gr_mpoly_ctx(gr_fmpzi_ctx, 2, 0)

In [7]: R.gens_recursive()
Out[7]: [I, x1, x2]

In [8]: i, x1, x2 = R.gens_recursive()

In [9]: (x1 + x2 + i)**3
Out[9]: x1^3 + 3*x1^2*x2 + (3*I)*x1^2 + 3*x1*x2^2 + (6*I)*x1*x2 - 3*x1 + x2^3 + (3*I)*x2^2 - 3*x2 - I

@oscarbenjamin oscarbenjamin merged commit bbc459f into flintlib:main Sep 15, 2024
18 checks passed
@oscarbenjamin oscarbenjamin deleted the pr_generic_rings branch September 15, 2024 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant