Skip to content

Interface for making an mpoly context #195

Closed
@oscarbenjamin

Description

@oscarbenjamin

CC @Jake-Moss

The idea of higher-level interfaces is discussed in gh-53. I would imagine that ultimately the usual way to create a multivariate polynomial would look something like:

from flint import ZZ
R, [x, y] = ZZ.mpoly_ring(['x', 'y'])
p = x**2 - y**2
p.factor()

Other options like monomial ordering could be keyword arguments.

In that sense we can think of the current interface as being sort of "low-level" but still I think it can be nicer than it is. This is currently the way to construct and use a multivariate polynomial context:

ctx = fmpz_mpoly_ctx(3, Ordering.lex, ["x", "y", "z"])
x, y, z = ctx.gens()
(x**2 - y**2).factor()

I think it is reasonable that the generators are not returned by default but the constructor syntax is awkward. It is redundant to specify the number of generators if we are already supplying a list of names. Also most of the time using lex as the default ordering is fine. I doubt many people will ever care about the monomial ordering.

It would be better to change it so that you can do

ctx = fmpz_mpoly_ctx(["x", "y", "z"])

and get a lex ordered ring by default. I think it would be reasonable to say that the ordering parameter could be a keyword-only argument like:

ctx = fmpz_mpoly_ctx(["x", "y", "z"], order='degrevlex')

Another problem is that the monomial ordering has to be passed as an enum which is also awkward e.g.:

import flint
ctx = flint.fmpz_mpoly_ctx(["x", "y", "z"], order=flint.Ordering.degrevlex)

It is reasonable to use these enums but it should be possible to just pass a string when calling the constructor. I think that a string should be displayed in the repr as well:

>>> ctx
fmpz_mpoly_ctx(["x", "y", "z"], order="lex")

The reason that the first argument to the current constructor is an integer is because it is envisaged that you would do:

>>> ctx = fmpz_mpoly_ctx(3, Ordering.lex, "x")
>>> ctx.gens()
(x1, x2, x3)

I think that a better way of handling that is to allow a symbol name to be a tuple of name and count e.g.:

>>> ctx = fmpz_mpoly_ctx([("x", 3), "y"])
>>> ctx.gens()
(x1,x2,x3,y)

The docstring for fmpz_mpoly_ctx says not to call it directly. I believe this is because get_context manages a cache of contexts. That is reasonable but in that case I think that calling the constructor directly should give an error and then my comments above apply to get_context as well.

The interface for get_context is:

In [11]: ctx = fmpz_mpoly_ctx.get_context(3, Ordering.lex, names="x")

In [12]: ctx.gens()
Out[12]: (x0, x1, x2)

In [14]: ctx = fmpz_mpoly_ctx.get_context(3, Ordering.lex, nametup=("x", "y", "z"))

In [15]: ctx.gens()
Out[15]: (x, y, z)

Again I think that the argument are somewhat backwards in:

fmpz_mpoly_ctx.get_context(3, Ordering.lex, nametup=("x", "y", "z"))

Ideally it should be

fmpz_mpoly_ctx.get_context(["x", "y", "z"])

The name get_context is a bit confusing for a method that creates a new context. I would perhaps rename that to new like:

fmpz_mpoly_ctx.new(["x", "y", "z"])

Also the parameter names are strange because names plural is what is used when passing a single string and nametup is what I would have naturally called names. The tup emphasises that it needs to be a tuple but really it should be okay to have a list here:

>>> ctx = fmpz_mpoly_ctx.get_context(3, Ordering.lex, nametup=["x", "y", "z"])
...
TypeError: Argument 'nametup' has incorrect type (expected tuple, got list)

I'm not sure we need the names and nametup arguments at all though. This is how I think I would do it:

ctx = fmpz_mpoly_ctx.new(["x", "y", "z"])  # x,y,z
ctx = fmpz_mpoly_ctx.new([("x", 2)])   # x1,x2
ctx = fmpz_mpoly_ctx.new([("x", 2), y])  # x1,x2,y

Another option seen in SymPy is:

In [17]: symbols('x:3, y')
Out[17]: (x0, x1, x2, y)

That looks nice in some situations but it is actually awkward when the number of symbols is a variable because you have to format it into a string:

In [18]: n = 3

In [19]: symbols(f'x:{n}, y')
Out[19]: (x0, x1, x2, y)

Also I think it is better to keep things simple at this level and avoid any sort of string parsing.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions