-
Notifications
You must be signed in to change notification settings - Fork 27
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
Adding Fmpz mpoly #59
Conversation
Split nmod_poly_factor out from nmod_poly Remove arb from setup.py
Added a skeleton pyproject.toml added a simple setuptools setup.py THere is no good replacement to use instead of numpy.distutils to get the default library and include paths. Any suggestions would be welcome.
The arb lib is now part of flint so all paths are prefixed with flint/
added missing fmpz_mpoly_sort_terms to _flint.pxd updated fmpz_mpoly_context to include names and other term orders Fixed up arithmetic to use python 3 protocol and removed automatic conversion.
Made fmpz_mpoly_ctx return a string instead of bytes.
For SymPy the corresponding Python format looks like: In [4]: p = QQ[x,y].convert(x**2*y + 1)
In [5]: p
Out[5]: x**2*y + 1
In [6]: dict(p)
Out[6]: {(0, 0): 1, (2, 1): 1}
In [7]: type(dict(p)[(0,0)])
Out[7]: flint._flint.fmpq Here I would definitely like to be able to just pass that fmpz_mpoly(p, ctx) or perhaps a method on the context object: ctx.from_dict(p) I discussed the idea of a higher-level interface for all of this in gh-53 but we do not need to do that here e.g. this is like level 1 referred to at #55 (comment)
NotImplemented is used for any types that you do not recognise. It allows the other type to potentially overload the method instead and it is good practice to do that for any type that you have not explicitly contemplated. Note that if both operands give NotImplemented then in the end a TypeError will be raised so returning NotImplemented usually results in TypeError. ValueError would be when you recognise the type but the operation is not valid e.g. So generally just check for the types that you want to allow and otherwise return NotImplemented. If you find a type that is expected and allowed but the value is not compatible (different context) raise ValueError.
I think it probably makes sense to have a common base class for the mpoly types. The situation would be like: cdef class mpoly_base:
# any generic code that just calls other methods
# no C functions are called here
cdef class fmpz_mpoly(mpoly_base):
cdef fmpz_mpoly_t val
# methods that actually call C functions. It would be easier to ensure a common interface like this and the idea would be that some of the code can be shared between the three classes. |
pyproject.toml
Outdated
@@ -0,0 +1,2 @@ | |||
[build-system] | |||
requires=["setuptools", "cython", "wheel"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seem to be various unrelated changes bundled in here. This one is possibly what breaks building the wheels in CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to keep different changes like this in separate pull requests so this one can be for adding fmpz_mpoly
but changes for flint 3 would go somewhere else (actually we already have a gh-43 for that).
Any changes to the build system should definitely be kept separate from anything else so that we can see clearly the impact of each change independently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the info on a coefficient/exponent dict.
Sorry about the build system confusion (I forgot that I did that I'll see about backing it out)
I agree about a common base class (one exists, but it is currently empty)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are actually three unrelated changes, include changes for flint 3.0, build system changes, and fmpz_changes. Sorry, I'll get to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I've backed out the flint-3 and setuptools and will see if that makes the CI happy. I am building a copy of flint-2.9 so I can test it. What is the version of arb that corresponds to flint-2.9?
I hope these work for CI. I currently don't have flint 3.0 or whatever the matching arb lib is.
Looks like CI is working again.
Not that it needs to be fixed here but actually it would be better to have more of an exception hierarchy like class FlintError(Exception):
pass
class FlintDomainError(FlintError):
"""Raised when the domains of operands are not compatible."""
pass
class FlintIncompatibleGeneratorsError(FlintDomainError):
"""Raised when polynomial rings have incompatible generators."""
pass or something like that. Generally it is better to have something more specific than |
@GiacomoPope will the changes in #61 impact on this PR? I guess maybe ctx/thectx might need to be imported or used differently but it doesn't look like it's being used here. It is probably worth being aware of the work @deinst is doing here while refactoring things to try to reduce potential conflicts. |
Yeah the change should be minimal. I think the only issues that will arise are when this code is using something which is previously global and will need to be imported to get everything working. |
Some slight adjustments to flint_mpoly_context and fmpz_mpoly needed. It now works on my machine.
Also added tests, and a few __iop__ methods to fmpz_mpoly
Yes, this does not compile. I need to go off on another branch to experiment with solutions.
What is the status of this now. Should we merge it and make further improvements later? There are many more functions defined in Here we have |
Probably the number one missing feature here that I would want for mpoly is factor. |
At some point when some version of this is merged I will try to integrate it with SymPy which and will probably discover a large number of methods that need to be added. For now the most important thing missing to be able to do that is just a |
This is not yet done, I need to touch up the call code, and I will add the factorization code. I got distracted by cython wierdness when attempting fmpq_mpoly, which I think is caused by a combination of the giant single module, and the trick of cythoning pyflint to make a _flint submodule, thereby dragging the _.flint pxd into view. I have a mostly modularized codebase in my fork in the mod_experiment branch (I still need to extract the dirichlet module). It is not ready for a PR, as it includes a couple of poor design decisions, the most glaring of which is that I renamed all the submodules to having a prefixed underscore to avoid conflicts between submodule names and class names, when I should have them in a subdirectory/module like |
Added infrastructure for fmpq_mpoly fixed up new import system
Forgot to add it to last commit
Also respliced fmpz_mpoly and fmpq_mpoly back into doctests.
Added submodule to setup Added rational function class to flint_base Added interface to flintlib Added skeleton fmpz_mpoly_q to types
Added missing fmpz_clear for exponents in creating fmpz_mpoly from dictionary Fixed spacing in converting fmpq_mpoly to string
also basic ops on fmpz_mpoly_q
Also added method to get context from a fmpq_mpoly so the tests could check that it was correct.
def __truediv__(self, other): | ||
cdef fmpq_mpoly qres | ||
cdef fmpq_t q_other | ||
cdef int xtype | ||
|
||
if typecheck(other, fmpz_mpoly): | ||
if not other: | ||
raise ZeroDivisionError("fmpz_mpoly division by zero") | ||
if self.ctx is not (<fmpz_mpoly> other).ctx: | ||
return NotImplemented | ||
return fmpz_mpoly_q(self, other) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a more general decision on how to handle a / b
in non-field rings.
Currently this is a TypeError in all other cases:
In [2]: fmpz(2) / fmpz(2)
...
TypeError: unsupported operand type(s) for /: 'flint.types.fmpz.fmpz' and 'flint.types.fmpz.fmpz'
Likewise all polynomials and matrices. Division by a scalar is allowed where the base ring is a field e.g.:
In [4]: fmpz_poly([1, 0]) / fmpz(1)
...
TypeError: unsupported operand type(s) for /: 'flint.types.fmpz_poly.fmpz_poly' and 'flint.types.fmpz.fmpz'
In [5]: fmpq_poly([1, 0]) / fmpq(1)
Out[5]: 1
There is one exception:
In [8]: fmpz_mat([[1], [0]]) / fmpz(2)
Out[8]:
[1/2]
[ 0]
In other words scalar division of an fmpz_mat
by an fmpz
returns an fmpq_mat
.
There is one other case of implicit conversion to a larger domain:
In [15]: fmpz_mat([[2, 0], [0, 2]]).inv()
Out[15]:
[1/2, 0]
[ 0, 1/2]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think you're probably right. The gains in convenience are probably not worth the loss of sanity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what is best here in general. At least in the places where SymPy would use this the code is very careful not to use a / b
if it is not known that we are working in a field. It would look like:
In [1]: from sympy import *
In [2]: x, y = symbols('x, y')
In [3]: R = QQ[x,y]
In [4]: R
Out[4]: QQ[x,y]
In [5]: p1 = R(x - y)
In [6]: p2 = R(x + y)
In [7]: K = R.get_field()
In [8]: K
Out[8]: QQ(x,y)
In [9]: K.convert_from(p1, R) / K.convert_from(p2, R)
Out[9]: (x - y)/(x + y)
Currently lacking in python-flint to make this work is:
- A systematic way to get the field of fractions from a ring.
- A systematic way to convert from the ring to that field.
- A systematic way to get the numerator and denominator back from the field as elements of the ring of integers.
It is not an immediate problem for SymPy that these things are lacking but I think that for someone using python-flint directly there should be some way to do this.
Probably what is needed is to have ring objects for every domain (including those that don't have a context in Flint like fmpz, fmpq, nmod). Probably this is all something that would be easier with generic rings so I am unsure right now what is the best approach to take in python-flint for conversions.
It might be good to have a meeting at some point to talk about some of these basic design questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO the /
operator should work as div
in the new generics system: preserve the type, allow divisions when the quotient exists in the ring, raise an exception otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preserve the type, allow divisions when the quotient exists in the ring, raise an exception otherwise.
That seems reasonable to me. Also if it is something that is used consistently in generic rings then it would be good to match that here and to use it consistently also across python-flint.
In SymPy this kind of division is called exquo
:
In [10]: R.exquo(p1, p2)
...
ExactQuotientFailed: x + y does not divide x - y in QQ[x,y]
In [11]: R.exquo(p1, p1)
Out[11]: 1
This is a useful operation that is used in e.g. fraction-free algorithms. Exposing it as a / b
would give SymPy a direct way to access the operation from python-flint rather than needing to use divmod
.
It is unfortunate that SymPy's domain types cannot be trusted to use a / b
for exquo but it all starts from using int
or gmpy2.mpz
for ZZ
. Having this for fmpz
leaves it still a little incompatible with int
and gmpy2.mpz
but that is unavoidable unless we want to match what they do:
In [12]: 2/3
Out[12]: 0.6666666666666666
I think it is fine for fmpz
to be incompatible with this because gmpy2.mpz
already exists for someone who wants a dropin faster replacement for int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A meeting would be great. This week is not great as I have people fixing my roof, but after that anytime after 1200 GMT after that would be great.
I also have type related questions in reference to the call method of the various mpoly types: should I insist that all the parameters have identical types, or that at least one of the parameters is of a type that all the others can be promoted to (for example if one was an arb_poly then the other parameters could be arb, fmpz or fmpz_poly), or that there was some type, not necessarily occuring in the list, that all the parameters could be converted to.
I do think that we should probably find a way of inserting the flint generics under python-flint. Issue #86 addresses this as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GiacomoPope or @fredrik-johansson would you like to join a meeting with me and @deinst (possibly next week some time)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that sounds good. Happy to make time next week. I'm in the UK re: timezones and planning but happy to be flexible :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, I'll follow up by email.
I will just note here some of the questions that I would like to resolve or at least make progress on:
- How exactly python-flint should use Flint's generics and also how to handle things that are not really generic.
- What a higher level python-flint interface might look like.
- How to handle explicit conversions between different types/domains.
- What to do with operations that do not necessarily make sense in a given domain like division and inverse.
@deinst do you have any update on this? I could take over soon if you are not going to work on this right now. |
Sorry, I got distracted by life. I'll get it done this weekend. |
Adding Fmpz mpoly #59, cont.
Thanks @deinst. The PR gh-132 by @Jake-Moss finished this off. I've pushed python-flint 0.7.0a3 to PyPI so it can be installed like Then: In [2]: from flint import fmpz_mpoly_ctx, fmpz_mpoly, Ordering
In [3]: R = fmpz_mpoly_ctx(2, Ordering.lex, ['x', 'y'])
In [4]: p = fmpz_mpoly('x^2 + y*x - 3*y', R)
In [5]: p
Out[5]: x^2 + x*y - 3*y
In [6]: p**2
Out[6]: x^4 + 2*x^3*y + x^2*y^2 - 6*x^2*y - 6*x*y^2 + 9*y^2
In [7]: (p**2).factor()
Out[7]: (1, [(x^2 + x*y - 3*y, 2)]) |
This is a more work in progress than a request to change the code. I'd like to get some comments before building this out completely. A few questions:
Do we want some way of specifying a polynomial with a list of tuples of the form (fmpz, (exp1, ..., expn)) ? What would be the best format for this?
I'm not certain when I should use NotImplemented vs ValueError when for example adding polynomials from different context and similar situations.
If this looks ok, I'll forge on and get the rest of fmpz_mpoly working and do the same for fmpq_mpoly and fmpz_mpoly_q