Repository: sagemath/sage

Issue:
Allow minimal model of cdga to return partial results
### Is there an existing issue for this?

- [X] I have searched the existing issues for a bug report that matches the one I want to file, without success.

### Problem Description

Minimal model of cdga's might be infinitely generated, so we haver a limit on the iteration steps tried. If this limit is reached, we return an error.

However, in some situations (for instance, studying Orlik-Solomon algebras of hyperplane arrangements), it can be interested to get the result computed up to a given point (even if it is not really an i-minimal model).

### Proposed Solution

To implement a `partial_result` flag in the  mimimal model method. This flag should be set to `False` by default, but when enabled, it would return the partially computed result instead of an error.

### Alternatives Considered

I see no other sensible alternative.

### Additional Information

_No response_


Discussion:

Resolving patch:
diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py
--- a/src/sage/algebras/commutative_dga.py
+++ b/src/sage/algebras/commutative_dga.py
@@ -2424,7 +2424,7 @@ def vector_to_element(v, deg):
                                for g in Q.basis()]
         return res
 
-    def minimal_model(self, i=3, max_iterations=3):
+    def minimal_model(self, i=3, max_iterations=3, partial_result=False):
         r"""
         Try to compute a map from a ``i``-minimal gcda that is a
         ``i``-quasi-isomorphism to self.
@@ -2437,7 +2437,12 @@ def minimal_model(self, i=3, max_iterations=3):
 
         - ``max_iterations`` -- integer (default: `3`); the number of
           iterations of the method at each degree. If the algorithm does not
-          finish in this many iterations at each degree, an error is raised.
+          finish in this many iterations at each degree, an error is raised,
+          or the partial result computed up to that point is returned, deppending
+          on the ``partial_result`` flag.
+
+        - ``partial_result``  -- boolean (default: ``False``); wether to return
+          the partial result if the ``max_iterations`` limit is reached.
 
         OUTPUT:
 
@@ -2576,6 +2581,34 @@ def minimal_model(self, i=3, max_iterations=3):
                t --> 0
               Defn: (x3_0, x3_1) --> (z, t)
 
+        ::
+
+            sage: A.<a,b,c> = GradedCommutativeAlgebra(QQ)
+            sage: I = A.ideal([a*b-a*c+b*c])
+            sage: B = A.quotient(I)
+            sage: S = B.cdg_algebra({})
+            sage: S.minimal_model()
+            Traceback (most recent call last):
+            ...
+            ValueError: could not cover all relations in max iterations in degree 2
+            sage: S.minimal_model(partial_result=True)
+            Commutative Differential Graded Algebra morphism:
+              From: Commutative Differential Graded Algebra with generators
+               ('x1_0', 'x1_1', 'x1_2', 'y1_0', 'y1_1', 'y1_2') in degrees (1, 1, 1, 1, 1, 1)
+                over Rational Field with differential:
+               x1_0 --> 0
+               x1_1 --> 0
+               x1_2 --> 0
+               y1_0 --> x1_0*x1_1 - x1_0*x1_2 + x1_1*x1_2
+               y1_1 --> x1_0*y1_0 - x1_2*y1_0
+               y1_2 --> x1_1*y1_0 - x1_2*y1_0
+              To:   Commutative Differential Graded Algebra with generators ('a', 'b', 'c')
+               in degrees (1, 1, 1) with relations [a*b - a*c + b*c] over Rational Field with differential:
+               a --> 0
+               b --> 0
+               c --> 0
+              Defn: (x1_0, x1_1, x1_2, y1_0, y1_1, y1_2) --> (a, b, c, 0, 0, 0)
+
         REFERENCES:
 
         - [Fel2001]_
@@ -2661,7 +2694,7 @@ def extendy(phi, degree):
                 if K.dimension() == 0:
                     return phi
                 if iteration == max_iterations - 1:
-                    raise ValueError("could not cover all relations in max iterations in degree {}".format(degree))
+                    return (phi,)
                 ndifs = [CB.lift(g) for g in K.basis()]
                 basisdegree = B.basis(degree)
                 ndifs = [sum(basisdegree[j] * g[j] for j in
@@ -2699,7 +2732,12 @@ def extendy(phi, degree):
             B = A.cdg_algebra(A.differential({}))
             # Solve case that fails with one generator return B,gens
             phi = B.hom(gens)
-            phi = extendy(phi, degnzero + 1)
+            phiext = extendy(phi, degnzero + 1)
+            if isinstance(phiext, tuple):
+                if not partial_result:
+                    raise ValueError("could not cover all relations in max iterations in degree {}".format(degnzero + 1))
+                return phiext[0]
+            phi = phiext
             self._minimalmodels[degnzero] = phi
         else:
             degnzero = max(self._minimalmodels)
@@ -2707,9 +2745,14 @@ def extendy(phi, degree):
 
         for degree in range(degnzero + 1, max_degree + 1):
             phi = extendx(phi, degree)
-            phi = extendy(phi, degree + 1)
+            phiext = extendy(phi, degree + 1)
+            if isinstance(phiext, tuple):
+                if partial_result:
+                    return phiext[0]
+                else:
+                    raise ValueError("could not cover all relations in max iterations in degree {}".format(degree + 1))
+            phi = phiext
             self._minimalmodels[degree] = phi
-
         return phi
 
     def cohomology_algebra(self, max_degree=3):
diff --git a/src/sage/algebras/orlik_solomon.py b/src/sage/algebras/orlik_solomon.py
--- a/src/sage/algebras/orlik_solomon.py
+++ b/src/sage/algebras/orlik_solomon.py
@@ -446,6 +446,88 @@ def degree_on_basis(self, m):
         """
         return len(m)
 
+    def as_gca(self):
+        r"""
+        Return the graded commutative algebra corresponding to ``self``.
+
+        EXAMPLES::
+
+            sage: H = hyperplane_arrangements.braid(3)
+            sage: O = H.orlik_solomon_algebra(QQ)
+            sage: O.as_gca()
+            Graded Commutative Algebra with generators ('e0', 'e1', 'e2') in degrees (1, 1, 1)
+             with relations [e0*e1 - e0*e2 + e1*e2] over Rational Field
+
+        ::
+
+            sage: N = matroids.named_matroids.Fano()
+            sage: O = N.orlik_solomon_algebra(QQ)
+            sage: O.as_gca()
+            Graded Commutative Algebra with generators ('e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6')
+             in degrees (1, 1, 1, 1, 1, 1, 1) with relations
+             [e1*e2 - e1*e3 + e2*e3, e0*e1*e3 - e0*e1*e4 + e0*e3*e4 - e1*e3*e4,
+              e0*e2 - e0*e4 + e2*e4, e3*e4 - e3*e5 + e4*e5,
+              e1*e2*e4 - e1*e2*e5 + e1*e4*e5 - e2*e4*e5,
+              e0*e2*e3 - e0*e2*e5 + e0*e3*e5 - e2*e3*e5, e0*e1 - e0*e5 + e1*e5,
+              e2*e5 - e2*e6 + e5*e6, e1*e3*e5 - e1*e3*e6 + e1*e5*e6 - e3*e5*e6,
+              e0*e4*e5 - e0*e4*e6 + e0*e5*e6 - e4*e5*e6, e1*e4 - e1*e6 + e4*e6,
+              e2*e3*e4 - e2*e3*e6 + e2*e4*e6 - e3*e4*e6, e0*e3 - e0*e6 + e3*e6,
+              e0*e1*e2 - e0*e1*e6 + e0*e2*e6 - e1*e2*e6] over Rational Field
+
+        TESTS::
+
+            sage: H = hyperplane_arrangements.Catalan(3,QQ).cone()
+            sage: O = H.orlik_solomon_algebra(QQ)
+            sage: A = O.as_gca()
+            sage: H.poincare_polynomial()
+            20*x^3 + 29*x^2 + 10*x + 1
+            sage: [len(A.basis(i)) for i in range(5)]
+            [1, 10, 29, 20, 0]
+
+        """
+        from sage.algebras.commutative_dga import GradedCommutativeAlgebra
+        gens = self.algebra_generators()
+        gkeys = gens.keys()
+        names = ['e{}'.format(i) for i in range(len(gens))]
+        A = GradedCommutativeAlgebra(self.base_ring(), names)
+        rels = []
+        for bc in self._broken_circuits.items():
+            bclist = [bc[1]] + list(bc[0])
+            indices = [gkeys.index(el) for el in bclist]
+            indices.sort()
+            rel = A.zero()
+            sign = -(-1)**len(indices)
+            for i in indices:
+                mon = A.one()
+                for j in indices:
+                    if j != i:
+                        mon *= A.gen(j)
+                rel += sign *mon
+                sign = -sign
+            rels.append(rel)
+        I = A.ideal(rels)
+        return A.quotient(I)
+
+    def as_cdga(self):
+        r"""
+        Return the commutative differential graded algebra corresponding to ``self``
+        with the trivial differential.
+
+        EXAMPLES::
+
+            sage: H = hyperplane_arrangements.braid(3)
+            sage: O = H.orlik_solomon_algebra(QQ)
+            sage: O.as_cdga()
+            Commutative Differential Graded Algebra with generators ('e0', 'e1', 'e2')
+             in degrees (1, 1, 1) with relations [e0*e1 - e0*e2 + e1*e2] over Rational Field
+             with differential:
+               e0 --> 0
+               e1 --> 0
+               e2 --> 0
+        """
+        return self.as_gca().cdg_algebra({})
+
+
 class OrlikSolomonInvariantAlgebra(FiniteDimensionalInvariantModule):
     r"""
     The invariant algebra of the Orlik-Solomon algebra from the


Test patch: