Skip to content

Commit 034f1f6

Browse files
author
Release Manager
committed
gh-36299: Generic implementation of fitting ideal and specific _fitting_ideal for MPolynomialRing This implements a method `fitting_ideal` for generic matrices, that only does a simple optimizations, and a more optimized version (named `_fitting_ideal` ) for matrices over polynomial rings with multiple variables. Fixes #36298 ### 📝 Checklist - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #36299 Reported by: miguelmarco Reviewer(s): Enrique Manuel Artal Bartolo, miguelmarco
2 parents c325311 + e4e3f36 commit 034f1f6

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

src/sage/matrix/matrix2.pyx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ from sage.structure.element cimport have_same_parent
8989
from sage.misc.verbose import verbose
9090
from sage.categories.fields import Fields
9191
from sage.categories.integral_domains import IntegralDomains
92+
from sage.categories.principal_ideal_domains import PrincipalIdealDomains
9293
from sage.rings.ring import is_Ring
9394
from sage.rings.number_field.number_field_base import NumberField
9495
from sage.rings.integer_ring import ZZ, is_IntegerRing
@@ -102,6 +103,8 @@ from . import berlekamp_massey
102103
from sage.modules.free_module_element import is_FreeModuleElement
103104
from sage.matrix.matrix_misc import permanental_minor_polynomial
104105

106+
from sage.misc.misc_c import prod
107+
105108
# used to deprecate only adjoint method
106109
from sage.misc.superseded import deprecated_function_alias
107110

@@ -15898,6 +15901,148 @@ cdef class Matrix(Matrix1):
1589815901
else:
1589915902
return dp
1590015903

15904+
def fitting_ideal(self, i):
15905+
r"""
15906+
Return the `i`-th Fitting ideal of the matrix. This is the ideal generated
15907+
by the `n - i` minors, where `n` is the number of columns.
15908+
15909+
INPUT:
15910+
15911+
``i`` -- an integer
15912+
15913+
OUTPUT:
15914+
15915+
An ideal on the base ring.
15916+
15917+
EXAMPLES::
15918+
15919+
sage: R.<x,y,z> = QQ[]
15920+
sage: M = matrix(R, [[2*x-z, 0, y-z^2, 1], [0, z - y, z - x, 0],[z - y, x^2 - y, 0, 0]])
15921+
sage: M
15922+
[ 2*x - z 0 -z^2 + y 1]
15923+
[ 0 -y + z -x + z 0]
15924+
[ -y + z x^2 - y 0 0]
15925+
sage: [R.ideal(M.minors(i)) == M.fitting_ideal(4-i) for i in range(5)]
15926+
[True, True, True, True, True]
15927+
sage: M.fitting_ideal(0)
15928+
Ideal (0) of Multivariate Polynomial Ring in x, y, z over Rational Field
15929+
sage: M.fitting_ideal(1)
15930+
Ideal (2*x^4 - 3*x^3*z + x^2*z^2 + y^2*z^2 - 2*y*z^3 + z^4 - 2*x^2*y - y^3 + 3*x*y*z + 2*y^2*z - 2*y*z^2, -x^3 + x^2*z + x*y - y*z, y^2 - 2*y*z + z^2, x*y - x*z - y*z + z^2) of Multivariate Polynomial Ring in x, y, z over Rational Field
15931+
sage: M.fitting_ideal(3)
15932+
Ideal (2*x - z, -z^2 + y, 1, -y + z, -x + z, -y + z, x^2 - y) of Multivariate Polynomial Ring in x, y, z over Rational Field
15933+
sage: M.fitting_ideal(4)
15934+
Ideal (1) of Multivariate Polynomial Ring in x, y, z over Rational Field
15935+
15936+
15937+
If the base ring is a field, the Fitting ideals are zero under the corank::
15938+
15939+
sage: M = matrix(QQ, [[2,1,3,5],[4,2,6,6],[0,3,2,0]])
15940+
sage: M
15941+
[2 1 3 5]
15942+
[4 2 6 6]
15943+
[0 3 2 0]
15944+
sage: M.fitting_ideal(0)
15945+
Principal ideal (0) of Rational Field
15946+
sage: M.fitting_ideal(1)
15947+
Principal ideal (1) of Rational Field
15948+
sage: M.fitting_ideal(2)
15949+
Principal ideal (1) of Rational Field
15950+
sage: M.fitting_ideal(3)
15951+
Principal ideal (1) of Rational Field
15952+
sage: M.fitting_ideal(4)
15953+
Principal ideal (1) of Rational Field
15954+
15955+
15956+
In the case of principal ideal domains, it is given by the elementary
15957+
divisors::
15958+
15959+
sage: M = matrix([[2,1,3,5],[4,2,6,6],[0,3,2,0]])
15960+
sage: M
15961+
[2 1 3 5]
15962+
[4 2 6 6]
15963+
[0 3 2 0]
15964+
sage: M.fitting_ideal(0)
15965+
Principal ideal (0) of Integer Ring
15966+
sage: M.fitting_ideal(1)
15967+
Principal ideal (4) of Integer Ring
15968+
sage: M.fitting_ideal(2)
15969+
Principal ideal (1) of Integer Ring
15970+
sage: M.fitting_ideal(3)
15971+
Principal ideal (1) of Integer Ring
15972+
sage: M.fitting_ideal(4)
15973+
Principal ideal (1) of Integer Ring
15974+
sage: M.elementary_divisors()
15975+
[1, 1, 4]
15976+
15977+
This is also true for univariate polynomials over a field::
15978+
15979+
sage: R.<x> = QQ[]
15980+
sage: M = matrix(R,[[x^2-2*x+1, x-1,x^2-1],[0,x+1,1]])
15981+
sage: M.fitting_ideal(0)
15982+
Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field
15983+
sage: M.fitting_ideal(1)
15984+
Principal ideal (x - 1) of Univariate Polynomial Ring in x over Rational Field
15985+
sage: M.fitting_ideal(2)
15986+
Principal ideal (1) of Univariate Polynomial Ring in x over Rational Field
15987+
sage: M.smith_form()[0]
15988+
[ 1 0 0]
15989+
[ 0 x - 1 0]
15990+
15991+
"""
15992+
R = self.base_ring()
15993+
if not R.is_exact():
15994+
raise NotImplementedError("Fitting ideals over non-exact rings not implemented at present")
15995+
n = self.ncols()
15996+
rank_minors = n - i
15997+
if rank_minors > self.nrows():
15998+
return R.ideal([R.zero()])
15999+
elif rank_minors <= 0:
16000+
return R.ideal([R.one()])
16001+
elif rank_minors == 1:
16002+
return R.ideal(self.coefficients())
16003+
if R in _Fields:
16004+
if self.rank() >= rank_minors:
16005+
return R.ideal([1])
16006+
else:
16007+
return R.ideal([0])
16008+
try:
16009+
elemdiv = self.elementary_divisors()
16010+
if rank_minors > len(elemdiv):
16011+
return R.ideal([0])
16012+
return R.ideal(prod(elemdiv[:rank_minors]))
16013+
except (TypeError, NotImplementedError, ArithmeticError):
16014+
pass
16015+
for (nr,r) in enumerate(self.rows()):
16016+
nz = [e for e in enumerate(r) if e[1]]
16017+
if len(nz) == 0:
16018+
N = self.delete_rows([nr])
16019+
return N.fitting_ideal(i)
16020+
elif len(nz) == 1:
16021+
N = self.delete_rows([nr])
16022+
F1 = N.fitting_ideal(i)
16023+
N = N.delete_columns([nz[0][0]])
16024+
F2 = N.fitting_ideal(i)
16025+
return F1 + nz[0][1]*F2
16026+
for (nc,c) in enumerate(self.columns()):
16027+
nz = [e for e in enumerate(c) if e[1]]
16028+
if len(nz) == 0:
16029+
N = self.delete_columns([nc])
16030+
return N.fitting_ideal(i - 1)
16031+
elif len(nz) == 1:
16032+
N = self.delete_columns([nc])
16033+
F1 = N.fitting_ideal(i-1)
16034+
N = N.delete_rows([nz[0][0]])
16035+
F2 = N.fitting_ideal(i)
16036+
return F1 + nz[0][1]*F2
16037+
if hasattr(self, '_fitting_ideal'):
16038+
try:
16039+
return self._fitting_ideal(i)
16040+
except NotImplementedError:
16041+
pass
16042+
return R.ideal(self.minors(rank_minors))
16043+
16044+
16045+
1590116046
def _hermite_form_euclidean(self, transformation=False, normalization=None):
1590216047
"""
1590316048
Transform the matrix in place to hermite normal form and optionally

src/sage/matrix/matrix_mpolynomial_dense.pyx

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ from sage.matrix.matrix2 cimport Matrix
2323
from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomialRing_libsingular
2424
from sage.rings.polynomial.polynomial_singular_interface import can_convert_to_singular
2525

26-
from sage.libs.singular.function import singular_function
26+
from sage.libs.singular.function import singular_function, lib
27+
28+
from cysignals.signals cimport sig_on, sig_off
2729

2830

2931
cdef class Matrix_mpolynomial_dense(Matrix_generic_dense):
@@ -451,6 +453,68 @@ cdef class Matrix_mpolynomial_dense(Matrix_generic_dense):
451453
"""
452454
return self.fetch('swapped_columns')
453455

456+
def _fitting_ideal(self, i):
457+
r"""
458+
Return the `i`-th Fitting ideal of the matrix. This is the ideal generated
459+
by the `n - i` minors, where `n` is the number of columns.
460+
461+
INPUT:
462+
463+
``i`` -- an integer
464+
465+
OUTPUT:
466+
467+
An ideal on the base ring.
468+
469+
EXAMPLES::
470+
471+
sage: R.<x,y,z> = QQ[]
472+
sage: M = matrix(R, [[2*x-z, 0, y-z^2, 0], [0, z - y, z - x, 0],[z - y, x^2 - y, 0, z]])
473+
sage: M
474+
[ 2*x - z 0 -z^2 + y 0]
475+
[ 0 -y + z -x + z 0]
476+
[ -y + z x^2 - y 0 z]
477+
sage: M.fitting_ideal(0)
478+
Ideal (0) of Multivariate Polynomial Ring in x, y, z over Rational Field
479+
sage: M.fitting_ideal(1)
480+
Ideal (2*x^4 - 3*x^3*z + x^2*z^2 + y^2*z^2 - 2*y*z^3 + z^4 - 2*x^2*y - y^3 + 3*x*y*z + 2*y^2*z - 2*y*z^2, y*z^3 - z^4 - y^2*z + y*z^2, -2*x*y*z + 2*x*z^2 + y*z^2 - z^3, -2*x^2*z + 3*x*z^2 - z^3) of Multivariate Polynomial Ring in x, y, z over Rational Field
481+
sage: M.fitting_ideal(2)
482+
Ideal (-x^3 + x^2*z + x*y - y*z, -y^2 + 2*y*z - z^2, -x^2*z^2 + x^2*y + y*z^2 - y^2, 2*x^3 - x^2*z - 2*x*y + y*z, -x*y + x*z + y*z - z^2, -y*z^2 + z^3 + y^2 - y*z, -2*x*y + 2*x*z + y*z - z^2, y*z^2 - z^3 - y^2 + y*z, 2*x^2 - 3*x*z + z^2, 2*x*z - z^2, -z^3 + y*z, -y*z + z^2, -x*z + z^2) of Multivariate Polynomial Ring in x, y, z over Rational Field
483+
sage: M.fitting_ideal(3)
484+
Ideal (2*x - z, -z^2 + y, -y + z, -x + z, -y + z, x^2 - y, z) of Multivariate Polynomial Ring in x, y, z over Rational Field
485+
sage: M.fitting_ideal(4)
486+
Ideal (1) of Multivariate Polynomial Ring in x, y, z over Rational Field
487+
sage: [R.ideal(M.minors(i)) == M._fitting_ideal(4 - i) for i in range(5)]
488+
[True, True, True, True, True]
489+
490+
"""
491+
minor = singular_function("minor")
492+
R = self.base_ring()
493+
for (nrow, row) in enumerate(self.rows()):
494+
if not row:
495+
N = self.delete_rows([nrow])
496+
return N._fitting_ideal(i)
497+
for (ncoef, coef) in enumerate(row):
498+
if all(coef.divides(f) for f in row):
499+
N = self.__copy__()
500+
for j in range(self.ncols()):
501+
if j != ncoef:
502+
N.add_multiple_of_column(j, ncoef, -R(self[nrow,j] / coef))
503+
return N.fitting_ideal(i)
504+
for (ncolumn, column) in enumerate(self.columns()):
505+
if not column:
506+
N = self.delete_columns([ncolumn])
507+
return N._fitting_ideal(i-1)
508+
for (ncoef, coef) in enumerate(column):
509+
if all(coef.divides(f) for f in column):
510+
N = self.__copy__()
511+
for j in range(self.nrows()):
512+
if j != ncoef:
513+
N.add_multiple_of_row(j, ncoef, -R(self[j, ncolumn] / coef))
514+
return N.fitting_ideal(i)
515+
rank = self.ncols() - i
516+
return R.ideal(minor(self, rank))
517+
454518
def determinant(self, algorithm=None):
455519
"""
456520
Return the determinant of this matrix

0 commit comments

Comments
 (0)