Metadata-Version: 2.4
Name: qqideal
Version: 0.1.0
Summary: Exact ideals over QQ: python-flint for arithmetic, msolve for Groebner bases.
Author: DC Posch
License: MIT
Project-URL: Homepage, https://github.com/dcposch/qqideal
Project-URL: Source, https://github.com/dcposch/qqideal
Keywords: msolve,groebner,ideal,polynomial,computer-algebra
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: msolveio>=0.1.0
Requires-Dist: python-flint<1,>=0.8
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# qqideal

Exact ideals over QQ: python-flint for arithmetic, msolve for Gröbner bases, verdicts that
refuse to guess. Emptiness, dimension, and 0-dimensional degree from grevlex leading ideals.
No solver-mode I/O.

## Install

```
pip install qqideal
```

That pulls [msolveio](https://pypi.org/project/msolveio/) and python-flint from PyPI. You also
need a system `msolve` 0.10.x binary on `PATH`.

## Usage

```python
from qqideal import Ideal, Kind, Ring, ideal_verdict

R = Ring("x", "y")
I = Ideal(["x^2-1", "y-x"], ring=R)

verdict = ideal_verdict(I)
print(verdict.kind)       # Kind.NONEMPTY
print(verdict.dim)        # 0
print(verdict.degree)     # 2
print(verdict.certainty)  # Certainty.PROVEN

if verdict.kind is Kind.NONEMPTY:   # never `if verdict:` -- that raises
    print(I.groebner())             # (Poly('x - y', ...), Poly('y^2 - 1', ...))
```

`Verdict.__bool__` raises `TypeError`. `TIMEOUT` and `ERROR` are not answers, and a
truthiness test would silently fold them into one of the two that are.

`opens=` saturates before the test, so you can ask about the complement of a hypersurface:

```python
ideal_verdict(["x*y", "x"], ring=R, opens=["x"]).kind   # Kind.EMPTY
Ideal(["x^2"], ring=R).radical_member("x").kind         # Kind.EMPTY: x is in the radical
```

## Certainty

A unit ideal over Q from msolve `-g` is `Certainty.MODULAR`, not `PROVEN`: msolve 0.10.1
returns after its first modular prime and still prints characteristic 0. A nonempty result
over Q uses a lifted `-g 2` basis and is `Certainty.PROVEN`, as is any result over a prime
field.

## Saturation

`I.saturate(f)` is Rabinowitsch: it returns `I + (u*f - 1)` in the ring extended by one slack
variable. Its variety is `V(I) \ V(f)`, so emptiness, dimension, and degree are those of
`I : f^∞` -- but its generators are not that ideal written back in `R`, which would need an
elimination order msolve's Gröbner mode does not offer. `colon` is an alias, and in v0.1 the
colon is the saturation.

## Not in v0.1

Primary decomposition, positive-dimensional radicals, radical computation of any kind
(membership only), solver mode / `-P`, Macaulay2. These raise `NotImplementedError` rather
than returning an approximation.

## License

MIT © 2026 DC Posch — <https://github.com/dcposch/qqideal>
