(uiop:define-package :st-buchberger/src/groebner
(:mix :cl)
(:mix-reexport :st-buchberger/src/arithmetic)
(:export #:s-poly #:make-index-set #:pair-member
 #:criterion #:normal-form #:groebner #:reduce-gb
 #:reduced-groebner))

(in-package :st-buchberger/src/groebner)

(defun s-poly (f g)
 "Returns the S-polynomial of f and g"
 ;; S(f, g) = x^gamma/LT(f) * f - x^gamma/LT(g) * g where x^gamma is
 ;; LCM(LM(f), LM(g))
 (flet ((make-x-gamma (f g)
          (make-instance 'term
                         :coefficient 1
                         :monomial (map 'vector #'max
                                        (monomial (lt f)) (monomial (lt g))))))
   (let ((x-gamma (make-x-gamma f g)))
     (sub (mul f (div x-gamma (lt f)))
          (mul g (div x-gamma (lt g)))))))

(defun make-index-set (s)
 (loop :for i :below s
       :nconc (loop :for j :below s :when (< i j) :collect (cons i j))))

(defun pair-member (l m B)
 (or (member (cons l m) B :test #'equal)
     (member (cons m l) B :test #'equal)))

(defun criterion (G B i j fi fj)
 "Returns T if S_{ij} ought to be considered, NIL otherise."
 (loop :with status := nil
       :for fk :across G
       :for k :from 0
       :when (and (/= k i) (/= k j)
                  (divides-p (lt fk) (ring-lcm (lt fi) (lt fj)))
                  (not (pair-member i k B)) (not (pair-member j k B)))
         :do (setf status t)
             (loop-finish)
       :finally (return status)))

(defun normal-form (f G)
 (nth-value 1 (divmod f (remove-if #'ring-zero-p G))))

(defun groebner (polynomials)
 "Returns a Groebner basis for the ideal generated by the specified
array of polynomials."
 (let* ((s (array-dimension polynomials 0))
        (r s)
        (G (make-array s :initial-contents polynomials
                         :adjustable t :fill-pointer s)))
   (do ((B (make-index-set s) (rest B)))
       ((null B) G)
     (destructuring-bind (i . j) (first B)
       (let ((fi (elt G i))
             (fj (elt G j)))
         (when (and (not (ring-equal-p (ring-lcm (lt fi) (lt fj))
                                       (mul (lt fi) (lt fj))))
                    (not (criterion G B i j fi fj)))
           (let ((remainder (normal-form (s-poly fi fj) G)))
             (unless (ring-zero-p remainder)
               (vector-push-extend remainder G)
               (setf B (append B (loop :for i :below r :collect (cons i r))))
               (incf r)))))))))

(defun reduce-gb (G)
 "Returns a reduced Groebner basis."
 (flet ((filter (f G)
          (remove-if (lambda (x) (or (ring-zero-p x) (equal f x))) G)))
   (loop :with F := (map 'vector (lambda (x) (mul x (/ (lc x)))) G)
         :for i :below (length F) :for fi :across F :do
           (setf (elt F i)
                 (if (some (lambda (x) (divides-p (lt x) (lt fi)))
                           (filter fi F))
                     (make-instance 'polynomial :ring (base-ring fi)) ; zero polynomial
                     (normal-form fi (remove fi F))))
         :finally (return (remove-if #'ring-zero-p F)))))

(defun reduced-groebner (F)
 "Computes and reduces a Groebner basis of the ideal generated by F."
 (reduce-gb (groebner F)))