(save-buffer) (let ((org-confirm-babel-evaluate (lambda (lang body) (declare (ignorable lang body)) nil))) (org-latex-export-to-pdf))
(save-buffer) (let ((python-indent-offset 4)) (org-babel-tangle))
CLOSED: [2016-05-01 Sun 14:33]
As a part of my lisp-based Computer Algebra System, an algebraic manipulation toolkit is required. This will be used to simplify equations, or for that matter solve them. This creates this toolkit, but does not create a complete simplifier or solver. It does this by providing manipulators and automatic rewriters. These together will provide simplification and solving utilities.
[7/7]
To accomplish the goal of providing a complete system to manipulate algebraic expressions, a way to determine the type of expression is important. This will allow for a form of "generic programming" to be used in the development of the manipulator functions, as a way to ensure that the correct manipulator is chosen.
This includes a form of storage, the classification definition macro, a way to check a classification, an expression classifier, and all possible classifications.
<<classification-storage>> <<define-classification>> <<check-classification>> <<classify-expression>> <<classification-case>> <<when-classified>> <<possible-classifications>>
CLOSED: [2016-05-04 Wed 19:30]
This is the classification definition macro, define-classification
. It takes one symbol argument, name
(the name of the classification), and a body, which is encapsulated within a defun, and binds the following variables:
expression
the expression which is to be classified
length
the length of the expression if the expression is a list, or 0 if it is not.
Aside from defining the classification, it also pushes the classification name and the classifier onto the stack, which can be used for direct classification checking or to completely classify an expression.
(defmacro define-classification (name &body body) (check-type name symbol) (let ((classifier-name (symbolicate name '-classifier))) `(progn (defun ,classifier-name (expression &aux (length (if (listp expression) (length expression) 0))) (declare (ignorable length)) ,@body) (pushnew '(,name . ,classifier-name) *classifications*) ',name)))
CLOSED: [2016-05-04 Wed 19:37]
To check a classification, the classifier is obtained, unless the specified classifier is *
, in which case, t
is always returned. If the classification is not, the classifier function is called on the expression, the result of which is returned.
(defun classified-as-p (expression classification) (if (eq '* classification) t (funcall (cdr (assoc classification *classifications*)) expression)))
CLOSED: [2016-05-04 Wed 19:44]
To completely classify an expression, the *classifications*
alist is mapped over, checking to see if each classification is applicable to the expression, if so, the name being returned, otherwise nil
. All nils are removed, leaving the complete classification, which is returned for use.
(defun classify (expression) (let ((classifications '())) (dolist (possible ,*classifications* (reverse classifications)) (let ((name (car possible)) (checker (cdr possible))) (when (funcall checker expression) (push name classifications))))))
CLOSED: [2016-05-30 Mon 18:17]
Following the case pattern, and to allow for cleaner code, I've defined the classification case macro. It does this by taking a variable name and a list of cases. These are then mapped over, producing clauses suitable for a cond
expression, to which this macro finally expands, binding the complete classification of the given expression to the-classification
.
(defmacro classification-case (var &rest cases) (declare (slime-indent (as case))) (let ((conditions (map 'list #'(lambda (case) (destructuring-bind (type &body body) case (if (eq type 't) `((classified-as-p ,var '*) ,@body) `((classified-as-p ,var ',type) ,@body)))) cases))) `(let ((the-classification (classify ,var))) (declare (ignorable the-classification)) (cond ,@conditions))))
CLOSED: [2016-05-30 Mon 19:18]
The when-classified-as
macro takes a classification, variable and a body. It expands to a when
form, with the classification and variable put into a classified-as-p
call becoming the predicate, determining whether or not the body is run.
(defmacro when-classified-as (classification variable &body body) `(when (classified-as-p ,variable ',classification) ,@body))
[13/13]
I must define several different classifications, ranging from simple numeric expressions (numbers) to trigonometric expressions ($\n$,cos$ d the lot). They are as follows:
Numbers
Variables
Non-Atomics
Additives
Subtractives
Powers
Exponentials
Multiplicatives
Logarithmics
Rationals
Polynomial Terms
Polynomials
Trigonometrics
<<classify-numbers>> <<classify-variables>> <<classify-non-atomics>> <<classify-additives>> <<classify-subtractives>> <<classify-powers>> <<classify-exponentials>> <<classify-multiplicatives>> <<classify-logarithmics>> <<classify-rationals>> <<classify-polynomial-term>> <<classify-polynomials>> <<classify-trigonometrics>>
CLOSED: [2016-05-04 Wed 19:56]
Check to see if a given expression is a number using numberp
.
(define-classification numeric (numberp expression))
CLOSED: [2016-05-04 Wed 19:57]
Check to see if a given expression is a variable, that is to say a symbol, using symbolp
.
(define-classification variable (symbolp expression))
CLOSED: [2016-05-04 Wed 19:59]
Check to see if a given expression is a non-atomic (any expression other than a number or a variable) using listp
.
(define-classification non-atomic (listp expression))
CLOSED: [2016-05-04 Wed 20:01]
Check to see whether or not an expression is an additive by ensuring that it is non-atomic and the first element is the symbol +
.
(define-classification additive (when-classified-as non-atomic expression (eq '+ (first expression))))
CLOSED: [2016-05-04 Wed 20:02]
Check to see whether a given expression is a subtractive by ensuring it is non-atomic and the first element is the symbol -
.
(define-classification subtractive (when-classified-as non-atomic expression (eq '- (first expression))))
CLOSED: [2016-05-04 Wed 20:07]
This is used to classify "powers", that is to say, equations of the form $x$,here $nis any numeric. It does so by first ensuring that the expression is non-atomic, following that, it checks to see if the first element in the expression is the symbol expt
, the second is a variable and the third a numeric.
(define-classification power (when-classified-as non-atomic expression (and (eq 'expt (first expression)) (classified-as-p (second expression) 'variable) (classified-as-p (third expression) 'numeric))))
CLOSED: [2016-05-30 Mon 18:24]
This classifies both natural and non-natural exponentials. It does so by ensuring that natural exponentials ($e$)re of the form (exp x)
, and non-natural exponentials ($a$)re of the form (expt base power)
.
(define-classification natural-exponential (when-classified-as non-atomic expression (and (= 2 length) (eq 'exp (first expression))))) (define-classification exponential (when-classified-as non-atomic expression (and (= 3 length) (eq 'expt (first expression)))))
CLOSED: [2016-05-30 Mon 18:55]
To classify multiplicative expressions, it is first ensured that they are non-atomic, and then, the first element is tested to see if it is equal to the symbol *
.
(define-classification multiplicative (when-classified-as non-atomic expression (eq '* (first expression))))
CLOSED: [2016-05-30 Mon 18:30]
This defines the classifications for logarithmic expressions, for both natural and non-natural bases. For natural bases ($\ x$)it ensures that expressions are of the form (log x)
, and for non-natural bases ($\gbx$)re of the form (log expression base-expression)
.
(define-classification natural-logarithmic (when-classified-as non-atomic expression (and (= 2 length) (eq 'log (first expression))))) (define-classification logarithmic (when-classified-as non-atomic expression (and (= 3 length) (eq 'log (first expression)))))
CLOSED: [2016-05-30 Mon 18:58]
Rationals are classified similarly to multiplicatives, checking to see whether or not they are non-atomic and checking whether or not the first element is /
, but rationals are also defined as only having three elements, the operation and two following operands, and thus, the length is also checked.
(define-classification rational (when-classified-as non-atomic expression (and (= 3 length) (eq '/ (first expression)))))
CLOSED: [2016-05-30 Mon 19:13]
To classify a polynomial term, The expression is checked to see if it satisfies one of the following:
Numeric
Variable
Power
Multiplicative that composed of a numeric and a power or variable.
(define-classification polynomial-term (or (classified-as-p expression 'numeric) (classified-as-p expression 'variable) (classified-as-p expression 'power) (and (classified-as-p expression 'multiplicative) (= (length (rest expression)) 2) (or (and (classified-as-p (second expression) 'numeric) (or (classified-as-p (third expression) 'power) (classified-as-p (third expression) 'variable))) (and (classified-as-p (third expression) 'numeric) (or (classified-as-p (second expression) 'power) (classified-as-p (second expression) 'variable)))))))
CLOSED: [2016-05-08 Sun 16:46]
This determines whether or not a given expression is a polynomial, that is to say it is either additive
or subtractive
, and each and every term is classified as polynomial-term
, that is to say, a numeric
, power
, or a multiplicative
consisting of a numeric
followed by a power
.
(define-classification polynomial (when-classified-as non-atomic expression (and (or (eq '- (first expression)) (eq '+ (first expression))) (reduce #'(lambda (a b) (and a b)) (map 'list #'(lambda (the-expression) (classified-as-p the-expression 'polynomial-term)) (rest expression))))))
CLOSED: [2016-05-30 Mon 19:15]
Trigonometrics are classified as many others are, they are first checked to see if they are non-atomic, and then the first element is checked, with the following being valid symbols:
sin
cos
tan
csc
sec
cot
(define-classification trigonometric (when-classified-as non-atomic expression (member (first expression) '(sin cos tan csc sec cot)))) (define-classification sin (when-classified-as non-atomic expression (eq 'sin (first expression)))) (define-classification cos (when-classified-as non-atomic expression (eq 'cos (first expression)))) (define-classification tan (when-classified-as non-atomic expression (eq 'tan (first expression)))) (define-classification csc (when-classified-as non-atomic expression (eq 'csc (first expression)))) (define-classification sec (when-classified-as non-atomic expression (eq 'sec (first expression)))) (define-classification cot (when (classified-as-p expression 'non-atomic) (eq 'cot (first expression))))
CLOSED: [2016-05-04 Wed 19:49]
The storage of classifications is simple, they are stored as an alist in the form of (name . classifier)
, in the list *classifications*
.
(defvar *classifications* '())
CLOSED: [2016-05-31 Tue 18:54]
Variable collection is somewhat important, and to accomplish this, I use a recursive algorithm. An expression is passed to the function, and if the expression is a variable, then the variable is collected and spit out; otherwise, if the expression is non-atomic, it is passed to the function recursively, and the returned variables are then merged into the variables list. Upon termination (no further sub-expressions), all variables are returned. (See Figure fig:variable-collection.)
digraph { start [label = "Start"]; stop [label = "Stop"]; collect [label = "Collect"]; if_var [label = "If Variable", shape = rectangle]; recurse_collect [label = "Iterate, Recurse and Collect Results"]; start -> if_var; if_var -> collect [label = "True"]; collect -> stop; if_var -> recurse_collect [label = "Non-atomic"]; recurse_collect -> start; }
(defun collect-variables (expression) (let ((variables '())) (flet ((merge-variables (variable) (pushnew variable variables))) (classification-case expression (variable (merge-variables expression)) (non-atomic (map 'list #'(lambda (expr) (dolist (variable (collect-variables expr)) (merge-variables variable))) (rest expression))))) (reverse variables)))
[0/6]
<<get-coefficient>> <<get-term-variable>> <<get-power>> <<same-order>> <<same-variable>> <<is-combinable>>
(defun coefficient (term) (when (classified-as-p term 'polynomial-term) (classification-case term (variable 1) (power 1) (multiplicative (second term)) (numeric term))))
(defun term-variable (term) (when (classified-as-p term 'polynomial-term) (classification-case term (power (second term)) (multiplicative (if (listp (third term)) (second (third term)) (third term))) (numeric nil))))
(defun get-power (term) (classification-case term (numeric 0) (variable 1) (power (third term)) (multiplicative (if (listp (third term)) (third (third term)) 1)) (* 0)))
(defun same-order-p (term-a term-b) (= (get-power term-a) (get-power term-b)))
(defun same-variable-p (term-a term-b) (eq (term-variable term-a) (term-variable term-b)))
(defun single-term-combinable-p (term-a term-b) (and (same-order-p term-a term-b) (same-variable-p term-a term-b)))
[2/8]
Foo
<<misc-manipulator-functions>> <<define-expression-manipulator>> <<external-manipulator>> <<addition-manipulator>> <<subtraction-manipulator>> <<multiplication-manipulators>> <<division-manipulators>> <<trigonometric-manipulators>>
CLOSED: [2016-05-08 Sun 10:34]
This defines the *manipulator-map*
, where the manipulators for various functions are stored, and defines a function to generate an arguments list given a count of arguments.
(defvar *manipulator-map* '()) (defun gen-args-list (count) (let ((letters '(a b c d e f g h i j k l m n o p q r s t u v w x y z))) (let ((variables-list '())) (dotimes (i count) (pushnew (symbolicate 'expression- (nth i letters)) variables-list)) (reverse variables-list))))
(defmacro define-operation (name arity short) (declare (slime-indent (as defun))) (check-type name symbol) (check-type arity (integer 1 26)) (check-type short symbol) (let* ((args (gen-args-list arity)) (expression-types (map 'list #'(lambda (x) (symbolicate x '-type)) args)) (rules-name (symbolicate '*manipulators- name '*)) (base-manipulator-name (symbolicate name '-manipulator-)) (manipulator-define-name (symbolicate 'define- name '-manipulator)) (is-applicable-name (symbolicate name '-is-applicable-p)) (get-operations-name (symbolicate 'get- name '-manipulators)) (type-check-list (let ((i 0)) (loop for arg in args collect (prog1 `(classified-as-p ,arg (nth ,i types)) (incf i)))))) `(progn (push '(,short . ,name) *manipulator-map*) (defvar ,rules-name '()) (defun ,is-applicable-name (types ,@args) (and ,@type-check-list)) (defun ,get-operations-name (,@args) (remove-if #'null (map 'list #'(lambda (option) (let ((types (car option)) (name (cdr option))) (if (,is-applicable-name types ,@args) name))) ,rules-name))) (defun ,name (,@args) (funcall (first (,get-operations-name ,@args)) ,@args)) (defmacro ,manipulator-define-name ((,@expression-types) &body body) (declare (slime-indent (as defun))) (let ((manipulator-name (symbolicate ',base-manipulator-name ,@expression-types))) `(progn (setf ,',rules-name (append ,',rules-name '(((,,@expression-types) . ,manipulator-name)))) (defun ,manipulator-name ,',args ,@body)))))))
(load "manipulation") (in-package #:manipulator) (format t "#+Caption: Expression Manipulator Expansion~%#+Name: ex-manip-expansion~%#+BEGIN_SRC lisp :exports code~%~a~%#+END_SRC" (macroexpand-1 '(define-operation frobnicate 2 frob)))
(PROGN (PUSH '(FROB . FROBNICATE) *MANIPULATOR-MAP*) (DEFVAR *MANIPULATORS-FROBNICATE* 'NIL) (DEFUN FROBNICATE-IS-APPLICABLE-P (TYPES EXPRESSION-A EXPRESSION-B) (AND (CLASSIFIED-AS-P EXPRESSION-A (NTH 0 TYPES)) (CLASSIFIED-AS-P EXPRESSION-B (NTH 1 TYPES)))) (DEFUN GET-FROBNICATE-MANIPULATORS (EXPRESSION-A EXPRESSION-B) (REMOVE-IF #'NULL (MAP 'LIST #'(LAMBDA (OPTION) (LET ((TYPES (CAR OPTION)) (NAME (CDR OPTION))) (IF (FROBNICATE-IS-APPLICABLE-P TYPES EXPRESSION-A EXPRESSION-B) NAME))) *MANIPULATORS-FROBNICATE*))) (DEFUN FROBNICATE (EXPRESSION-A EXPRESSION-B) (FUNCALL (FIRST (GET-FROBNICATE-MANIPULATORS EXPRESSION-A EXPRESSION-B)) EXPRESSION-A EXPRESSION-B)) (DEFMACRO DEFINE-FROBNICATE-MANIPULATOR ((EXPRESSION-A-TYPE EXPRESSION-B-TYPE) &BODY BODY) (DECLARE (SLIME-INDENT (AS DEFUN))) (LET ((MANIPULATOR-NAME (SYMBOLICATE 'FROBNICATE-MANIPULATOR- EXPRESSION-A-TYPE EXPRESSION-B-TYPE))) `(PROGN (SETF ,'*MANIPULATORS-FROBNICATE* (APPEND ,'*MANIPULATORS-FROBNICATE* '(((,EXPRESSION-A-TYPE ,EXPRESSION-B-TYPE) ,@MANIPULATOR-NAME)))) (DEFUN ,MANIPULATOR-NAME ,'(EXPRESSION-A EXPRESSION-B) ,@BODY)))))
CLOSED: [2016-05-31 Tue 19:48]
The Expression Manipulators should not be touched outside of this package, as they are not designed to be used outside of it. Instead, they should be used through this simple function. It takes an action and a list of expressions. The function used to perform the action correctly is determined, and used to reduce the expressions.
(defun manipulate (action &rest expressions) (let ((the-manipulator (cdr (assoc action *manipulator-map*)))) (reduce the-manipulator expressions)))
Foo
(define-operation add 2 +) (define-add-manipulator (numeric numeric) (+ expression-a expression-b)) (define-add-manipulator (numeric additive) (let ((total expression-a) (remainder (rest expression-b)) (non-numeric '())) (dolist (element remainder) (if (classified-as-p element 'numeric) (incf total element) (push element non-numeric))) (cond ((null non-numeric) total) ((= 0 total) `(+ ,@non-numeric)) (t `(+ ,total ,@non-numeric))))) (define-add-manipulator (additive additive) (let ((total 0) (elements (append (rest expression-a) (rest expression-b))) (non-numeric '())) (dolist (element elements) (if (classified-as-p element 'numeric) (incf total element) (push element non-numeric))) (cond ((null non-numeric) total) ((= 0 total) `(+ ,@non-numeric)) (t `(+ ,total ,@non-numeric))))) (define-add-manipulator (numeric subtractive) (let ((total expression-a) (the-other (rest expression-b)) (non-numeric '())) (dolist (element the-other) (if (classified-as-p element 'numeric) (decf total element) (push element non-numeric))) (cond ((null non-numeric) total) ((= 0 total) `(+ ,@non-numeric)) (t `(+ ,total (-,@non-numeric)))))) (define-add-manipulator (numeric polynomial-term) `(+ ,expression-a ,expression-b)) (define-add-manipulator (polynomial-term polynomial-term) (if (single-term-combinable-p expression-a expression-b) (let ((new-coefficient (+ (coefficient expression-a) (coefficient expression-b))) (variable (term-variable expression-a)) (power (get-power expression-a))) `(* ,new-coefficient (expt ,variable ,power))) `(+ ,expression-a ,expression-b))) (define-add-manipulator (* numeric) (add expression-b expression-a))
Foo
(define-operation subtract 2 -) (define-subtract-manipulator (numeric numeric) (- expression-a expression-b)) (define-subtract-manipulator (numeric subtractive) (let ((total expression-a) (elements (rest expression-b)) (non-numeric '())) (dolist (element elements) (if (classified-as-p element 'numeric) (decf total element) (push element non-numeric))) (cond ((null non-numeric) total) ((= 0 total) `(- ,@(reverse non-numeric))) (t `(- ,total ,@(reverse non-numeric)))))) (define-subtract-manipulator (* numeric) (subtract expression-b expression-a))
Foo
(define-operation multiply 2 *) (define-multiply-manipulator (numeric numeric) (* expression-a expression-b)) (define-multiply-manipulator (numeric polynomial-term) (let ((new-coefficient (* expression-a (coefficient expression-b))) (variable (term-variable expression-b)) (power (get-power expression-b))) (if (= 1 power) `(* ,new-coefficient ,variable) `(* ,new-coefficient (expt ,variable ,power))))) (define-multiply-manipulator (polynomial-term polynomial-term) (let ((new-coefficient (* (coefficient expression-a) (coefficient expression-b))) (variable (term-variable expression-b)) (power (+ (get-power expression-a) (get-power expression-b)))) `(* ,new-coefficient (expt ,variable ,power))))
Foo
(define-operation division 2 /) (define-division-manipulator (numeric numeric) (/ expression-a expression-b))
[0/6]
Foo
<<sine-manipulators>> <<cosine-manipulators>> <<tangent-manipulators>> <<cosecant-manipulators>> <<secant-manipulators>> <<cotangent-manipulators>>
(define-operation sine 1 sin) (define-sine-manipulator (numeric) (sin expression-a))
(define-operation cosine 1 cos) (define-cosine-manipulator (numeric) (cosine expression-a))
(define-operation tangent 1 tan) (define-tangent-manipulator (numeric) (tan expression-a))
(define-operation cosecant 1 csc)
(define-operation secant 1 sec)
(define-operation cotangent 1 cot)
CLOSED: [2016-05-05 Thu 21:21]
This assembles and packages the algebraic manipulation system into a single file and library. To do so, it must first define a package, import specific symbols from other packages, and export symbols from itself. It then includes the remainder of the functionality, placing it in the file manipulation.lisp
.
(defpackage #:manipulator (:use #:cl) (:import-from #:alexandria #:symbolicate) (:export #:manipulate #:classify #:classified-as-p #:classification-case #:collect-variables #:collect-terms)) (in-package #:manipulator) (declaim (declaration slime-indent)) <<determine-expression-type>> <<collect-variables>> <<collect-terms>> <<polynomial-related-functions>> <<expression-manipulation>>