(uiop:define-package :st-buchberger/src/arithmetic (:mix :cl) (:mix-reexport :st-buchberger/src/polynomial) (:export #:ring+ #:ring- #:ring* #:ring/ #:add #:sub #:mul #:divides-p #:div #:divmod #:ring-mod #:ring-lcm)) (in-package #:st-buchberger/src/arithmetic) (defmethod ring+ ((poly polynomial) &rest more-polynomials) (reduce #'add more-polynomials :initial-value poly)) (defmethod ring- ((poly polynomial) &rest more-polynomials) (reduce #'sub more-polynomials :initial-value poly)) (defmethod ring* ((poly polynomial) &rest more-polynomials) (reduce #'mul more-polynomials :initial-value poly)) (defmethod ring/ ((poly polynomial) &rest more-polynomials) (if (some #'ring-zero-p more-polynomials) (error 'ring-division-by-zero :operands (list poly more-polynomials))) (divmod poly (make-array (length more-polynomials) :initial-contents more-polynomials))) (defmethod add ((poly polynomial) (tm term)) "Returns the polynomial with the added term" (let ((new-poly (ring-copy poly))) (with-slots (terms) new-poly (with-slots (coefficient monomial) tm (let* ((old-value (gethash monomial terms 0)) (new-value (+ old-value coefficient))) (if (zerop new-value) (remhash monomial terms) (setf (gethash monomial terms) new-value))))) new-poly)) (defmethod add ((p1 polynomial) (p2 polynomial)) "Returns the sum of two polynomials" (let ((new-poly (ring-copy p1))) (doterms (tm p2 new-poly) (setf new-poly (add new-poly tm))))) (defmethod sub ((poly polynomial) (tm term)) "Returns the result of subtracting the term from the polynomial" (with-slots (coefficient monomial) tm (add poly (make-instance 'term :coefficient (* -1 coefficient) :monomial monomial)))) (defmethod sub ((p1 polynomial) (p2 polynomial)) "Returns the result of subtracting two polynomials." (add p1 (mul p2 (make-instance 'term :ring (base-ring p2) :coefficient -1 :monomial (make-array (length (variables (base-ring p2))) :initial-element 0))))) (defmethod mul ((poly polynomial) (num number)) (let ((new-poly (make-instance 'polynomial :ring (base-ring poly)))) (doterms (tt poly new-poly) (setf new-poly (add new-poly (mul tt num)))))) (defmethod mul ((t1 term) (num number)) (make-instance 'term :coefficient (* num (coefficient t1)) :monomial (monomial t1))) (defmethod mul ((t1 term) (t2 term)) "Multiplies two terms storing the result in the first term." (with-slots ((c1 coefficient) (m1 monomial)) t1 (with-slots ((c2 coefficient) (m2 monomial)) t2 (make-instance 'term :coefficient (* c1 c2) :monomial (map 'vector #'+ m1 m2))))) (defmethod mul ((poly polynomial) (tm term)) "Returns the product of a polynomial by a term" (let ((new-poly (make-instance 'polynomial :ring (base-ring poly)))) (doterms (tt poly new-poly) (setf new-poly (add new-poly (mul tt tm)))))) (defmethod mul ((p1 polynomial) (p2 polynomial)) "Returns the product of two polynomials" (assert (and p1 p2)) (let ((new-poly (make-instance 'polynomial :ring (base-ring p1)))) (doterms (tm p2 new-poly) (setf new-poly (add new-poly (mul p1 tm)))))) (defmethod divides-p ((t1 term) (t2 term)) (assert (and t1 t2)) (with-slots ((c1 coefficient) (m1 monomial)) t1 (with-slots ((c2 coefficient) (m2 monomial)) t2 ;; We don't have to check (divides-p c1 c2) because we're working ;; with polynomials over a *field* (and (not (zerop c1)) (every #'>= m2 m1))))) (defmethod divides-p ((t1 term) (p polynomial)) (assert (and t1 p)) (every (lambda (x) (divides-p t1 x)) (terms->list p))) (defmethod div ((t1 term) (t2 term)) "Returns the quotient of two terms" (assert (divides-p t2 t1)) (with-slots ((c1 coefficient) (m1 monomial)) t1 (with-slots ((c2 coefficient) (m2 monomial)) t2 (make-instance 'term :coefficient (/ c1 c2) :monomial (vector- m1 m2))))) (defmethod divmod ((f polynomial) fs) "Divides F by the polynomials in the sequence FS and returns the quotients (as an array) and a remainder." (flet ((init-vector (poly n) (let ((vs (make-array n :fill-pointer 0))) (dotimes (i n vs) (vector-push (make-instance 'polynomial :ring (base-ring poly)) vs))))) (loop :with p := (ring-copy f) :with as := (init-vector f (length fs)) :with r := (make-instance 'polynomial :ring (base-ring f)) :while (not (ring-zero-p p)) :do (loop :with division-occurred-p := nil :with i := 0 :while (and (< i (length fs)) (not division-occurred-p)) :do (let ((fi (elt fs i))) (if (divides-p (lt fi) (lt p)) (setf (elt as i) (add (elt as i) (div (lt p) (lt fi))) p (sub p (mul fi (div (lt p) (lt fi)))) division-occurred-p t) (incf i))) :finally (unless division-occurred-p (setf r (add r (lt p)) p (sub p (lt p))))) :finally (return (values as r))))) (defmethod ring-mod ((f polynomial) &rest fs) (when (apply #'some #'ring-zero-p fs) (error 'ring-division-by-zero :operands (list f fs))) (nth-value 1 (apply #'divmod f fs))) (defmethod ring-lcm ((t1 term) (t2 term)) "Returns LCM(t1, t2)" (with-slots ((c1 coefficient) (m1 monomial)) t1 (with-slots ((c2 coefficient) (m2 monomial)) t2 (make-instance 'term :coefficient (ring-lcm c1 c2) :monomial (map 'vector #'max m1 m2))))) (defmethod ring-lcm ((r1 rational) (r2 rational)) "LCM over the rationals." ;; Translated from SAGE. (let ((d (* (denominator r1) (denominator r2))) (r1-d (* (numerator r1) (denominator r2))) (r2-d (* (numerator r2) (denominator r1)))) (/ (lcm r1-d r2-d) d)))