;;; -*- Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Copyright (C) 1999, 2002, 2009, 2015 Marek Rychlik ;;; ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2 of the License, or ;;; (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Global switches ;; ;; Can be used in Maxima just fine, as they observe the ;; Maxima naming convention, i.e. all names visible at the ;; Maxima toplevel begin with a '$'. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar $poly_monomial_order '$lex "This switch controls which monomial order is used in polynomial and Grobner basis calculations. If not set, LEX will be used") (defvar $poly_coefficient_ring '$expression_ring "This switch indicates the coefficient ring of the polynomials that will be used in grobner calculations. If not set, Maxima's general expression ring will be used. This variable may be set to RING_OF_INTEGERS if desired.") (defvar $poly_primary_elimination_order nil "Name of the default order for eliminated variables in elimination-based functions. If not set, LEX will be used.") (defvar $poly_secondary_elimination_order nil "Name of the default order for kept variables in elimination-based functions. If not set, LEX will be used.") (defvar $poly_elimination_order nil "Name of the default elimination order used in elimination calculations. If set, it overrides the settings in variables POLY_PRIMARY_ELIMINATION_ORDER and SECONDARY_ELIMINATION_ORDER. The user must ensure that this is a true elimination order valid for the number of eliminated variables.") (defvar $poly_return_term_list nil "If set to T, all functions in this package will return each polynomial as a list of terms in the current monomial order rather than a Maxima general expression.") (defvar $poly_grobner_debug nil "If set to TRUE, produce debugging and tracing output.") (defvar $poly_grobner_algorithm '$buchberger "The name of the algorithm used to find grobner bases.") (defvar $poly_top_reduction_only nil "If not FALSE, use top reduction only whenever possible. Top reduction means that division algorithm stops after the first reduction.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Coefficient ring operations ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; These are ALL operations that are performed on the coefficients by ;; the package, and thus the coefficient ring can be changed by merely ;; redefining these operations. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defstruct (ring) (parse #'identity :type function) (unit #'identity :type function) (zerop #'identity :type function) (add #'identity :type function) (sub #'identity :type function) (uminus #'identity :type function) (mul #'identity :type function) (div #'identity :type function) (lcm #'identity :type function) (ezgcd #'identity :type function) (gcd #'identity :type function)) (defparameter *ring-of-integers* (make-ring :parse #'identity :unit #'(lambda () 1) :zerop #'zerop :add #'+ :sub #'- :uminus #'- :mul #'* :div #'/ :lcm #'lcm :ezgcd #'(lambda (x y &aux (c (gcd x y))) (values c (/ x c) (/ y c))) :gcd #'gcd) "The ring of integers.") (defvar *coefficient-ring* *ring-of-integers* "The ring of coefficients, over which all polynomials are assumed to be defined.") (defvar *ratdisrep-fun* #'identity "A function applied to polynomials after conversion to Maxima representation.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This is how we perform operations on coefficients ;; using Maxima functions. ;; ;; Functions and macros dealing with internal representation structure ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Debugging/tracing ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmacro debug-cgb (&rest args) `(when $poly_grobner_debug (format *terminal-io* ,@args))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; These are provided mostly for debugging purposes To enable ;; verification of grobner bases with BUCHBERGER-CRITERION, do ;; (pushnew :grobner-check *features*) and compile/load this file. ;; With this feature, the calculations will slow down CONSIDERABLY. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun grobner-test (ring g f) "Test whether G is a Grobner basis and F is contained in G. Return T upon success and NIL otherwise." (debug-cgb "~&GROBNER CHECK: ") (let (($poly_grobner_debug nil) (stat1 (buchberger-criterion ring g)) (stat2 (every #'poly-zerop (makelist (normal-form ring (copy-tree (elt f i)) g nil) (i 0 (1- (length f))))))) (unless stat1 (error "~&Buchberger criterion failed.")) (unless stat2 (error "~&Original polys not in ideal spanned by Grobner."))) (debug-cgb "~&GROBNER CHECK END") t) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Selection of algorithm and pair heuristic ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun find-grobner-function (algorithm) "Return a function which calculates Grobner basis, based on its names. Names currently used are either Lisp symbols, Maxima symbols or keywords." (ecase algorithm ((buchberger :buchberger $buchberger) #'buchberger) ((parallel-buchberger :parallel-buchberger $parallel_buchberger) #'parallel-buchberger) ((gebauer-moeller :gebauer_moeller $gebauer_moeller) #'gebauer-moeller))) (defun grobner (ring f &optional (start 0) (top-reduction-only nil)) ;;(setf F (sort F #'< :key #'sugar)) (funcall (find-grobner-function $poly_grobner_algorithm) ring f start top-reduction-only)) (defun reduced-grobner (ring f &optional (start 0) (top-reduction-only $poly_top_reduction_only)) (reduction ring (grobner ring f start top-reduction-only))) (defun set-pair-heuristic (method) "Sets up variables *PAIR-KEY-FUNCTION* and *PAIR-ORDER* used to determine the priority of critical pairs in the priority queue." (ecase method ((sugar :sugar $sugar) (setf *pair-key-function* #'sugar-pair-key *pair-order* #'sugar-order)) ; ((minimal-mock-spoly :minimal-mock-spoly $minimal_mock_spoly) ; (setf *pair-key-function* #'mock-spoly ; *pair-order* #'mock-spoly-order)) ((minimal-lcm :minimal-lcm $minimal_lcm) (setf *pair-key-function* #'(lambda (p q) (monom-lcm (poly-lm p) (poly-lm q))) *pair-order* #'reverse-monomial-order)) ((minimal-total-degree :minimal-total-degree $minimal_total_degree) (setf *pair-key-function* #'(lambda (p q) (monom-total-degree (monom-lcm (poly-lm p) (poly-lm q)))) *pair-order* #'<)) ((minimal-length :minimal-length $minimal_length) (setf *pair-key-function* #'(lambda (p q) (+ (poly-length p) (poly-length q))) *pair-order* #'<)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Conversion from internal to infix form ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun coerce-to-infix (poly-type object vars) (case poly-type (:termlist `(+ ,@(mapcar #'(lambda (term) (coerce-to-infix :term term vars)) object))) (:polynomial (coerce-to-infix :termlist (poly-termlist object) vars)) (:poly-list `([ ,@(mapcar #'(lambda (p) (coerce-to-infix :polynomial p vars)) object))) (:term `(* ,(term-coeff object) ,@(mapcar #'(lambda (var power) `(expt ,var ,power)) vars (monom-exponents (term-monom object))))) (otherwise object))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Order utilities ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun find-order (order) "This function returns the order function bases on its name." (cond ((null order) nil) ((symbolp order) (case order ((lex :lex $lex) #'lex>) ((grlex :grlex $grlex) #'grlex>) ((grevlex :grevlex $grevlex) #'grevlex>) ((invlex :invlex $invlex) #'invlex>) ((elimination-order-1 :elimination-order-1 elimination_order_1) #'elimination-order-1) (otherwise (warn "~%Warning: Order ~A not found. Using default.~%" order)))) (t (warn "~%Order specification ~A is not recognized. Using default.~%" order) nil))) (defun find-ring (ring) "This function returns the ring structure bases on input symbol." (cond ((null ring) nil) ((symbolp ring) (case ring ((expression-ring :expression-ring $expression_ring) *expression-ring*) ((ring-of-integers :ring-of-integers $ring_of_integers) *ring-of-integers*) (otherwise (warn "~%Warning: Ring ~A not found. Using default.~%" ring)))) (t (warn "~%Ring specification ~A is not recognized. Using default.~%" ring) nil))) (defmacro with-monomial-order ((order) &body body) "Evaluate BODY with monomial order set to ORDER." `(let ((*monomial-order* (or (find-order ,order) *monomial-order*))) . ,body)) (defmacro with-coefficient-ring ((ring) &body body) "Evaluate BODY with coefficient ring set to RING." `(let ((*coefficient-ring* (or (find-ring ,ring) *coefficient-ring*))) . ,body)) (defmacro with-elimination-orders ((primary secondary elimination-order) &body body) "Evaluate BODY with primary and secondary elimination orders set to PRIMARY and SECONDARY." `(let ((*primary-elimination-order* (or (find-order ,primary) *primary-elimination-order*)) (*secondary-elimination-order* (or (find-order ,secondary) *secondary-elimination-order*)) (*elimination-order* (or (find-order ,elimination-order) *elimination-order*))) . ,body)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Conversion from internal form to Maxima general form ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun maxima-head () (if $poly_return_term_list '(mlist) '(mplus))) (defun coerce-to-maxima (poly-type object vars) (case poly-type (:polynomial `(,(maxima-head) ,@(mapcar #'(lambda (term) (coerce-to-maxima :term term vars)) (poly-termlist object)))) (:poly-list `((mlist) ,@(mapcar #'(lambda (p) ($ratdisrep (coerce-to-maxima :polynomial p vars))) object))) (:term `((mtimes) ,($ratdisrep (term-coeff object)) ,@(mapcar #'(lambda (var power) `((mexpt) ,var ,power)) vars (monom-exponents (term-monom object))))) ;; Assumes that Lisp and Maxima logicals coincide (:logical object) (otherwise object))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Macro facility for writing Maxima-level wrappers for ;; functions operating on internal representation ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmacro with-parsed-polynomials (((maxima-vars &optional (maxima-new-vars nil new-vars-supplied-p)) &key (polynomials nil) (poly-lists nil) (poly-list-lists nil) (value-type nil)) &body body &aux (vars (gensym)) (new-vars (gensym))) `(let ((,vars (coerce-maxima-list ,maxima-vars)) ,@(when new-vars-supplied-p (list `(,new-vars (coerce-maxima-list ,maxima-new-vars))))) (coerce-to-maxima ,value-type (with-coefficient-ring ($poly_coefficient_ring) (with-monomial-order ($poly_monomial_order) (with-elimination-orders ($poly_primary_elimination_order $poly_secondary_elimination_order $poly_elimination_order) (let ,(let ((args nil)) (dolist (p polynomials args) (setf args (cons `(,p (parse-poly ,p ,vars)) args))) (dolist (p poly-lists args) (setf args (cons `(,p (parse-poly-list ,p ,vars)) args))) (dolist (p poly-list-lists args) (setf args (cons `(,p (parse-poly-list-list ,p ,vars)) args)))) . ,body)))) ,(if new-vars-supplied-p `(append ,vars ,new-vars) vars))))