larcs.org 67 KB

LARCS

Introduction

CLOSED: [2016-12-14 Wed 14:59]

LARCS is a Computer Algebra System, written from scratch, using the technique of literate programming to accomplish the goal. The purpose of this is threefold: first, as a learning exercise; second, to develop a CAS that is built around a clean, readable and modular design, rather than efficiency; and third, to demonstrate the abilities of Literate Programming in general, and Org-Mode in particular. There is another motive, many CASs are non-free, and those that aren't don't always provide a useful programming interface, and with the goal of a clean, readable and modular design, this can be accomplished.

What's in a name?

CLOSED: [2016-12-14 Wed 15:13]

While the Introduction describes the purpose of LARCS, it does not describe the name. LARCS stands for Lisp Automated Rewrite and Calculation System. This name describes the system quite accurately, as LARCS was written in Lisp, provides an automated system for rewriting equations into various forms, and calculating given an equation and values of the various variables, and acts as a coherent grouping of libraries forming a system.

TOC   ignore

WORKING Common Functionality [3/6]

There are several bits of common functions or variables that are required for use. This primarily includes functions that some of the macros rely on, or things that are required for use in other parts of the system, but don't present as specific functionality.

Generate an Args List

CLOSED: [2016-07-30 Sat 16:08]

For some macros, an arguments list must be generated. This is done by generating a list of variables starting with the word expression- followed by a letter from the alphabet, in turn.

  (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))))

Constants and Greeks

CLOSED: [2016-08-05 Fri 21:32]

This is a mapping between the names of constants and the way that they are correctly displayed in TeX. Besides defining the mapping, which is in the form of an alist, it also collects the names of all of the constants, and exports the names themselves.

  (defvar *special-symbols-to-sequences*
    '((alpha . "\\alpha")
      (beta . "\\beta")
      (gamma . "\\gamma")
      (delta . "\\delta")
      (epsilon . "\\epsilon")
      (varepsilon . "\\varepsilon")
      (zeta . "\\zeta")
      (eta . "\\eta")
      (theta . "\\theta")
      (vartheta . "\\vartheta")
      (gamma . "\\gamma") (kappa . "\\kappa")
      (lambda . "\\lambda")
      (mu . "\\mu")
      (nu . "\\nu")
      (xi . "\\xi")
      (omicron . "\\o")
      (pi . "\\pi")
      (varpi . "\\varpi")
      (rho . "\\rho")
      (varrho . "\\varrho")
      (sigma . "\\sigma")
      (varsigm . "\\varsigm")
      (tau . "\\tau")
      (upsilon . "\\upsilon")
      (phi . "\\phi")
      (varphi . "\\varphi")
      (chi . "\\chi")
      (psi . "\\psi")
      (omega . "\\omega")
      (big-gamma . "\\Gamma")
      (big-delta . "\\Delta")
      (big-theta . "\\Theta")
      (big-lambda . "\\Lambda")
      (big-xi . "\\Xi")
      (big-pi . "\\Pi")
      (big-sigma . "\\Sigma")
      (big-upsilon . "\\Upsilon")
      (big-phi . "\\Phi")
      (big-psi . "\\Psi")
      (big-omega . "\\Omega")))

  (defvar *constant-names*
    (mapcar #'car *special-symbols-to-sequences*))

  (mapcar #'export *constant-names*)

Aget

  (defun aget (place indicator &optional default)
    "
  RETURN:   The value of the entry INDICATOR of the a-list PLACE, or DEFAULT.
  "
    (let ((a (assoc indicator place)))
      (if a (cdr a) default)))

  (define-setf-expander aget (place indicator &optional default &environment env)
    (declare (ignore default))
    (multiple-value-bind (vars vals store-vars writer-form reader-form)
        (get-setf-expansion place env)
      (let* ((vindicator (gensym "INDICATOR"))
             (vvalue     (gensym "VALUE"))
             (vstore     (first store-vars))
             (acs        (gensym "PAIR")))
        (values (list* vindicator vars)
                (list* indicator  vals)
                (list  vvalue)
                `(let* ((,acs (assoc ,vindicator ,reader-form)))
                   (if ,acs
                       (setf (cdr ,acs) ,vvalue)
                       (let ((,vstore (acons ,vindicator ,vvalue ,reader-form)))
                         ,writer-form))
                   ,vvalue)
                `(assoc ,vindicator ,reader-form)))))

Ensure List

  (defun ensure-list (object)
    "
  RETURN:         If OBJECT is a list then OBJECT, otherwise a fresh
                  list containing OBJECT.
  "
    (if (listp object) object (list object)))

Evaluating Bind

  (defun evaluating-bind (expression &rest pairs)
    (let ((vars (mapcar #'first pairs))
          (values (mapcar #'second pairs)))
      (apply (eval `(lambda ,vars ,expression)) values)))

Assembly

CLOSED: [2016-07-30 Sat 15:43]

This is where the common functions and constants are assembled into their own package. Almost all of the functions and variables are exported and available for everything else.

  (in-package #:larcs.common)

  <<common-generate-an-args-list>>

  <<constants-and-greeks>>

  <<aget>>

  <<ensure-list>>

  <<evaluating-bind>>

WORKING Expression Types [1/4]

All expressions are built from various <expression> objects. These objects are specialized to contain only certain, specific information, relevant to a particular expression. The most basic expressions (besides expression itself) are <atomic> (holding things like numbers or variables) and <compound> (holding things like multiplicatinos, divisions, exponents, trigonometric expressions, arithmetic expressions, etc.). All subtypes of <expression> must know if they are atomic, so we define a generic for this, they must also tell if they are eqal (a form of equality), and be able to perform substitution, evaluation, and simplification, however, the latter three are implemented elsewhere. The organization of the various types may be found in Figure~fig:expression-types.

Note, by default, eqal will handle type-error by trying to switch the order of the arguments. If this fails, it will warn that an applicable method for the given types, and return nil.

  (defclass <expression> () ())

  (defgeneric atomicp (expression))

  (defgeneric eqal (expression-a expression-b))

  (defmethod eqal ((expression-a <expression>) (expression-b <expression>))
    (if (> (length (compute-applicable-methods #'eqal (list expression-b expression-a))) 2)
        (eqal expression-b expression-a)
        (warn "No definition of `eqal' for types `~a' and `~a'."
              (type-of expression-a)
              (type-of expression-b))))

  (defmethod eqal :around ((expression-a <expression>) (expression-b <expression>))
    (handler-case (call-next-method)
      (type-error (err)
        (declare (ignore err))
        (eqal expression-b expression-a))))

  (defgeneric substitute-expression (expression with in))

  (defgeneric evaluate (expression &key substitutions-list resolve-constants-light-p resolve-constants-hard-p))

  (defgeneric simplify (expression &key resolve-constans-light-p resolve-constants-hard-p))
  digraph {
          expression [label = "<expression>"];

          atomic [label = "<atomic>"];
          number [label = "<number>"];
          variable [label = "<variable>"];
          constant [label = "<constant>"];

          compound [label = "<compound>"];
          mult [label = "<multiplication>"];
          div [label = "<division>"];
          expt [label = "<expt>"];
          exp [label = "<exp>"];
          trig [label = "<trig>"];
          log [label = "<log>"];
          ln [label = "<ln>"];
          add [label = "<addition>"];
          sub [label = "<subtraction>"];

          polyTerm [label = "<polynomial-term>"];

          expression -> atomic;
          atomic -> number -> constant;
          atomic -> variable -> constant;

          expression -> compound;
          compound -> mult -> polyTerm;
          compound -> div;
          compound -> expt -> exp;
          expt -> polyTerm;
          compound -> trig;
          compound -> add;
          compound -> sub;
          compound -> log -> ln;

          variable -> polyTerm;

  }

img/types.png

WORKING Atomic Types [2/3]

Of the various types, there are three atomic types, <number>, <variable> and <constant> They are very similar, only differing on the :type their value slot has. As all <atomic> subtypes are, in fact, atomic (save for some later blended types), we can simply return t for atomicp. Further, all <atomic> have a definite value, so we should be able to retrieve this.

  (defclass <atomic> (<expression>) ())

  (defgeneric value (expression &key resolve-constants))

  (defmethod atomicp ((expression <atomic>))
    (declare (ignore expression))
    t)

Numbers

CLOSED: [2019-01-05 Sat 09:56]

As an <atomic>, <number> is rather simple, having only one slot, value, readable using the value method, and initialized with :value. This value must be of type number. Two <number> objects are equal, if their value is =.

  (defclass <number> (<atomic>)
    ((value :initarg :value
            :type number)))

  (defmethod value ((expression <number>) &key resolve-constants)
    (declare (ignore resolve-constants))
    (slot-value expression 'value))

  (defmethod eqal ((expression-a <number>) (expression-b <number>))
    (= (value expression-a) (value expression-b)))

Variables

CLOSED: [2019-01-05 Sat 09:56]

<variable> is similar to <number>, with the only difference being name must be a symbol or keyword. Two <variable> objects are eqal if the name of each is equalp.

  (defclass <variable> (<atomic>)
    ((name :initarg :name
           :type (or symbol keyword))))

  (defmethod value ((expression <variable>) &key resolve-constants)
    (declare (ignore resolve-constants))
    (slot-value expression 'name))

  (defmethod eqal ((expression-a <variable>) (expression-b <variable>))
    (equalp (value expression-a)
            (value expression-b)))

WORKING Constants

<constant> is a bit different from the rest – it has a name, as well as a number, and the value depends on whether or not you want to "resolve" the constant. If the constant is to be resolved, the numeric value is returned, otherwise, the name is returned.

Equality of constants is a bit different, and is checked as follows:

<constant>, <constant>

Check if name is equalp.

<constant>, <variable>

Check if name is equalp.

<constant>, <number>

Check if value is =.

  (defclass <constant> (<number> <variable>)
    ((name :initarg :name
           :type (or symbol keyword))
     (value :initarg :value
            :type number)))

  (defmethod value ((expression <constant>) &key resolve-constants)
    (if resolve-constants
        (slot-value expression 'value)
        (slot-value expression 'name)))

  (defmethod eqal ((expression-a <constant>) (expression-b <constant>))
    (equalp (slot-value expression-a 'name)
            (slot-value expression-b 'name)))

  (defmethod eqal ((expression-a <constant>) (expression-b <variable>))
    (equalp (slot-value expression-a 'name)
            (slot-value expression-b 'name)))

  (defmethod eqal ((expression-a <constant>) (expression-b <number>))
    (= (slot-value expression-a 'value)
       (slot-value expression-b 'value)))

WORKING Compound Types [3/7]

Compound types are the majority of expressions. An atom is well and good, but often says relatively little. We use compound types to form equations from other expressions. These are not, then, atomicp.

  (defclass <compound> (<expression>) ())

  (defmethod atomicp ((expression <compound>))
    (declare (ignore expression))
    nil)

Multiplications

CLOSED: [2019-01-05 Sat 09:01]

Multiplication is one of the more frequently used expression types, and is, surprisingly, simple. A <multiplication> has only one slot, terms, the various expressions to be multiplied.

  (defclass <multiplication> (<compound>)
    ((terms :reader terms
            :initarg terms
            :type (list <expression>))))

Divisions

CLOSED: [2019-01-05 Sat 09:09]

Division is similar to <multiplication>, although instead of having terms it has numerator and denominator.

  (defclass <division> (<compound>)
    ((numerator :reader div-numerator
                :initarg :numerator
                :type <expression>)
     (denominator ::reader div-denominator
                  :initarg :denominator
                  :type <expression>)))

Exponentials

CLOSED: [2019-01-05 Sat 09:20]

There are two primary forms of exponential – the natural (exp) and the general (expt). Given this, there are two classes, with <exp> being a subclass of <expt>, mirroring the generality of exponentials in general.

  (defclass <expt> (<compound>)
    ((base :reader base
           :initarg :base
           :type <expression>)
     (exponent :reader exponent
               :initarg :exponent
               :type <expression>)))

  (defclass <exp> (<expt>)
    ((exponent :reader exponent
               :initarg :exponent
               :type <expression>)))

  (defmethod base ((exp <exp>))
    (make-instance '<constant>
                   :name :e
                   :value 2.71828))

Logarithmics

Trigonometric

Additions

Subtractions

Blended Types – Polynomial Terms

CLOSED: [2019-01-05 Sat 09:46]

The <polynomial-term> is one of the more interesting types. It's a variable, a multiplication and an exponent, all rolled into one. Therefore, it needs to implement all of the functionality of those. So, while some of the generics are obvious, i.e., exponent, others are less so. Things like value, base and terms must be written directly, and these can later be used, for instance, <polynomial-term> can easily be converted to <multiplication>, and an example of this is provided.

  (defclass <polynomial-term> (<multiplication> <expt> <variable>)
    ((coefficient :reader coefficient
                  :initarg :coefficient
                  :type <number>)
     (variable :reader term-variable
               :initarg :variable
               :type <variable>)
     (exponent :reader exponent
               :initarg :exponent
               :type <number>)))

  (defmethod value ((expression <polynomial-term>) &key resolve-constants)
    (declare (ignore resolve-constants))
    (value (slot-value expression 'variable)))

  (defmethod base ((expression <polynomial-term>))
    (slot-value expression 'variable))

  (defmethod terms ((expression <polynomial-term>))
    (list
     (slot-value expression 'coefficient)
     (make-instance '<expt>
                    :base (base expression)
                    :exponent (exponent expression))))

  (defmethod update-instance-for-different-class ((prev <polynomial-term>) (current <multiplication>) &key)
    (setf (slot-value current 'terms)
          (terms prev)))

Object Protocol


WORKING Expression Typing [0/8]

To be able to provide various forms of matching and manipulation, the type of an expression must be determined. This is done by analyzing the contents of the expression. To accomplish this, there must be a way to define a classifier, store all possible classifiers, check a classifier and produce a classification. To provide more flexibility in programming, there is also a special version of case, called classification-case and a when-pattern macro called when-classified-as.

Define Classification

Classifications are defined as define-classification. This macro takes a name, which is the name of the classification, and a body, which is classified within a function. Inside the function, the following are bound: expression, the expression to be classified; and, length, which is the length of the expression if it's a list, otherwise, 0 if it's atomic. A cons cell containing the name of the classification and the name of the classifier is pushed onto classification storage, and the classifier name is exported.

  (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*)
         (export ',name)
         ',name)))

Check Classification

To classify an expression, the expression and name of the possible classification is passed in. If the given name of the classification is *, then t is returned, as this is a catch all; otherwise the classification is retrieved by name, and the expression is passed to the classifier, which will return either t or nil.

  (defun classified-as-p (expression classification)
    (if (eq '* classification)
        t
        (funcall (cdr (assoc classification *classifications*))
                 expression)))

Classify Expression

While being able to check if an expression is given a specific classification is vital, for some things, being able to see what all possible classifications for an expression are can be quite useful. To do this, an expression is passed in, and for each possible classification in the classification storage, it is checked to see whether or not the classification is possible. If it is, the classification is pushed on to a list of valid classifications. When the possible classifications are exhausted, the list of valid classifications is reversed and returned.

  (defun classify (expression)
    (let ((classifications '()))
      (dolist (possible
                ,*classifications*
               (reverse classifications))
        (let ((name (car possible))
              (checker (cdr possible)))
          (when (funcall checker expression)
            (push name classifications))))))

Classification Case

Because case is such a useful tool, and because it provides a way to ensure that an expression doesn't fall through when acting on it, I've written the classification-case macro. It takes an expression, named var and a list of cases, in the form of (classification body-form-1 body-form-2 body-form-n). It transforms the cases, converting them to the form ((classified-as-p expression 'type) body-form-1 body-form-2 body-form-n). It finally expands to a cond in which the-classification is bound to the full and complete classification of the passed expression.

  (defmacro classification-case (var &rest cases)
    (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))))

When Classified

Another utility macro is when-classified-as, which takes a classification, an expressiond named variable and a body. It expands fairly simply to a when form, with the predicate taking the following form (classified-as-p variable 'classification), wrapping around the passed in body.

  (defmacro when-classified-as (classification variable &body body)
    `(when (classified-as-p ,variable ',classification)
       ,@body))

Classifications [13/13]

I define the following classifications:

Numerics

All numbers

Variables

Any symbols

Non-atomics

Anything that isn't simply a number or a variable

Additives

Expressions that are adding multiple terms

Subtractives

Expressions subtracting multiple terms

Powers

Expressions of the form $x$,here $xis a variable, and $nis a numeric.

Exponentials

Expressions of the form $x$ $e$,here $xand $yare generic expressions, and $eis Euler's constant.

Logarithmics

Expressions of the form of $\ x$ $\g_b x$,here $xand $bare generic expressions.

Rationals

Expressions of the form $\ac{f(x)}{g(x)}$.

Polynomial Terms

Any integers, multiplicatives of the form $nm$ powers of the form $x$,here $xis a variable and $nand $mare numerics.

Polynomials

Additives or Subtractives consisting solely of Polynomial Terms.

Trigonometrics

The trig functions: $\n$,cos$,tan$,csc$,sec$ d $\t$.

  <<et-classify-numbers>>
  <<et-classify-variables>>
  <<et-classify-non-atomics>>
  <<et-classify-additives>>
  <<et-classify-subtractives>>
  <<et-classify-powers>>
  <<et-classify-exponentials>>
  <<et-classify-multiplicatives>>
  <<et-classify-logarithmics>>
  <<et-classify-rationals>>
  <<et-classify-polynomial-term>>
  <<et-classify-polynomials>>
  <<et-classify-trigonometrics>>

Numbers

CLOSED: [2016-06-14 Tue 23:58]

A number is defined as anything that satisfies the built-in numberp. This includes integers, rationals, floats and complex numbers.

  (define-classification numeric
    (numberp expression))

Variables

CLOSED: [2016-06-15 Wed 00:00]

Variables are defined as anything that satisfies the Common Lisp predicate, symbolp.

  (define-classification variable
    (symbolp expression))

Non Atomics

CLOSED: [2016-06-15 Wed 00:02]

Non-atomic is a classification for anything other than numerics and variables. It is defined as anything that satisfies the predicate listp.

  (define-classification non-atomic
    (listp expression))

Additives

CLOSED: [2016-06-15 Wed 00:03]

When an expression is non-atomic, and the first element is the symbol +, it is classified as an additive expression.

  (define-classification additive
    (when-classified-as non-atomic expression
      (eq '+ (first expression))))

Subtractive

CLOSED: [2016-06-15 Wed 00:06]

A non-atomic expression for which the first element is the symbol - is a subtractive expression.

  (define-classification subtractive
    (when-classified-as non-atomic expression
      (eq '- (first expression))))

Powers

CLOSED: [2016-06-15 Wed 00:07]

A power is any expression that is non-atomic, the first element is the symbol expt, the second is a variable and the third is 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))))

Exponentials

CLOSED: [2016-06-15 Wed 00:11]

There are two types of exponentials, natural and non-natural. Natural exponentials are defined as being non-atomic, two elements long, and the first element being exp. Non-natural exponentials are defined similarly, but are three elements long, and the first of which is the symbol expt.

  (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)))))

Multiplicatives

CLOSED: [2016-06-15 Wed 00:12]

A multiplicative expression is non-atomic, of any length, and the first element is the symbol *.

  (define-classification multiplicative
    (when-classified-as non-atomic expression
      (eq '* (first expression))))

Logarithmics

CLOSED: [2016-06-15 Wed 00:14]

There are two types of logarithmic classifications, natural and non-natural. Natural logarithmics are non-atomic, two elements long, and the first element is the symbol log. Natural logarithmics are also non-atomic, but they are three elements long, starting with the symbol log.

  (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)))))

Rationals

CLOSED: [2016-06-15 Wed 00:15]

Rationals are non-atomic, three elements long, and the first element is the symbol /.

  (define-classification rational
    (when-classified-as non-atomic expression
      (and (= 3 length)
         (eq '/ (first expression)))))

Polynomial Terms

CLOSED: [2016-06-15 Wed 00:17]

Polynomials are a compound classification:

  • Numerics

  • Variables

  • Powers

  • Multiplicatives that are a numeric and a variable

  • Multiplicatives that are a numeric and a power

  (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)))))))

Polynomials

CLOSED: [2016-06-15 Wed 00:19]

Polynomials are compound classifications that are defined as expressions which are either additive or subtrative, for which each term is a polynomial term.

  (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))))))

Trigonometrics

CLOSED: [2016-06-15 Wed 00:22]

Trigonometrics are defined as non atomic expressions that are two elements long, for which the first element of the expression is either sin, cos, tan, csc, sec, or cot. For each of these there is a classification seperate from the generic trigonometric classification.

  (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))))

Classification Storage

Classifications are stored in an alist, with the key being the name of the classification, and the value being the classifier itself. These cons cells are stored in the *classifications* variable.

  (defvar *classifications* '())

Assembly

This assembles the classification library, which in the #:larcs.classify package. It correctly resolves the order of the code, taking it from simple blocks to a complete file.

  (in-package #:larcs.classify)
  <<et-classification-storage>>
  <<et-define-classification>>
  <<et-check-classification>>
  <<et-classify-expression>>
  <<et-classification-case>>
  <<et-when-classified>>
  <<et-possible-classifications>>

WORKING Symbolic Differentiation [0/4]

While this isn't exactly algebra, differentiation is important mathematically. This is done rather simply using rules to rewrite an initial expression forming the derivative.

WORKING Rule Definition [0/3]

CLOSED: [2016-08-16 Tue 22:10]

Rule definition is how this system works. This is done rather simply, and is comprised of definition, retrieval and storage portions.

Definition

Rules are defined using the define-derivative macro, which takes an expression type, an arguments list, and a body. If the expression type is not already in the expansions map, it pushes the expression type and expansion name onto the the mapping. Following that, it defines a function for the expansion, using the given arguments list as the lambda-list and the given body for the body for the function.

  (defmacro define-derivative (expression-type (&rest arguments-list) &body body)
    (let ((expansion-name (symbolicate expression-type '-expansion)))
      `(progn
         (when (not (member ',expression-type (mapcar #'car *rules*)))
           (setq *rules* (append *rules* '((,expression-type . ,expansion-name)))))
         (defun ,expansion-name (,@arguments-list)
           ,@body))))

Retrieval

Rule retrieval works by matching a rewrite rule to an expression by classification. This is done by iterating through the possible classification-rewrite rule pairs, and if the expression matches the classification, returing the rewrite rule to be used.

  (defun get-rule (expression)
    (cdr (first (remove-if #'(lambda (pair)
                               (let ((type (first pair)))
                                 (not (classified-as-p expression type))))
                           ,*rules*))))

Storage

Rules are stored rather simply, in a list of cons cells, with the CAR being the classification, and the CDR being the actualy rewrite function. They are found in the variable *rules*.

  (defvar *rules* '())

WORKING Rules [0/9]

The process of symbolic derivation is carried out through rules. These are defined in the following sections, taking apart the equations and putting them back together again as their derivatives.

  <<sd-numbers>>
  <<sd-variables>>
  <<sd-polynomial-terms>>
  <<sd-multiplicatives>>
  <<sd-rationals>>
  <<sd-additives>>
  <<sd-subtractives>>
  <<sd-exponentials-and-logarithmics>>
  <<sd-trigonometric-functions>>

Numbers

Numbers are perhaps one of the simplest rules to define, ignore everything and simply return 0, which is, by definition, the derivative of any bare number.

  (define-derivative numeric (&rest junk)
    (declare (ignorable junk))
    0)

Variables

As with Numbers, Variables are just as simple, if something is simply a bare variable, the derivative of such is 1.

  (define-derivative variable (&rest junk)
    (declare (ignorable junk))
    1)

Polynomial Terms

A Polynomial Term is a bit more complex than the previous two, the rewrite rule has to be able to get the variable, coefficient and current power of the polynomial term. Given this information, there are three possible cases:

The power is equal to 1.

The coefficient is returned.

The power is equal to 2.

The coefficient times the variable (in symbolic form) is returned.

The power is greater than 2.

This comes in the form of $()x^{(n-1)}$.

  (define-derivative polynomial-term (&rest term)
    (let* ((coefficient (coefficient term))
           (variable (term-variable term))
           (power (get-power term)))
      (cond
        ((= 1 power)
         coefficient)
        ((= 2 power)
         `(* ,(* coefficient power) ,variable))
        (t
         `(* ,(* coefficient power) (expt ,variable ,(1- power)))))))

Multiplicatives

Differentiation of Multiplicative equations are performed by the product rule. This is defined as $\ac{\mathrm{d}}{\mathrm{d}x} f(x) ⋅ g(x) = f(x) ⋅ g(x) + f(x) ⋅ g(x)$.There are some minor exceptions, if $f)$ d $g)$ e numeric, then the result is the product of the two; if either $f)$ $g)$ numeric and the other is not, then the numeric is placed in front of the other derivative of the remainder.

  (define-derivative multiplicative (function first &rest rest)
    (declare (ignore function))
    (if (= 1 (length rest))
        (let ((second (first rest)))
          (cond
            ((and (classified-as-p first 'numeric)
                (classified-as-p second 'numeric))
             (* first second))
            ((classified-as-p first 'numeric)
             `(* ,first ,(differentiate second)))
            ((classified-as-p second 'numeric)
             `(* ,second ,(differentiate first)))
            (t
             `(+ (* ,first ,(differentiate second))
                 (* ,second ,(differentiate first))))))
        (differentiate `(* ,first (* ,@rest)))))

Rationals

This follows the quotient rule, which is defined as $\ac{\mathrm{d}}{\mathrm{d}x} \frac{f(x)}{g(x)} = \frac{f(x)g(x) - f(x)g(x)}{g(x)^2}$.

  (define-derivative rational (function numerator denominator)
    (declare (ignore function))
    `(/ (- (* ,numerator ,(differentiate denominator))
           (* ,denominator ,(differentiate numerator)))
        (expt ,denominator 2)))

Additives

This is quite simple, differentiate each term and add them together. This is accomplished using map.

  (define-derivative additive (function &rest terms)
    (declare (ignore function))
    `(+ ,@(map 'list #'(lambda (term) (differentiate term)) terms)))

Subtractives

Following the same pattern as for additives, subtractives map over, deriving each term and prepending the - symbol to render the derivative of the originally passed subtractive equation.

  (define-derivative subtractive (function &rest terms)
    (declare (ignore function))
    `(- ,@(map 'list #'(lambda (term) (differentiate term)) terms)))

Exponentials and Logarithmics

There are four types of functions that are supported:

Natural Exponentials

Effectively this returns itself.

Exponentials

The original function multiplied by the log of the base.

Natural Logarithmics

The derivative of the function being passed to $\$ er the same function.

Logarithmics

The derivative of the natural log of the expression over the log of the base times the expression.

  (define-derivative natural-exponential (function expression)
    (declare (ignore function))
    `(exp ,expression))

  (define-derivative exponential (function base power)
    (declare (ignore function))
    (if (numberp power)
        (if (listp base)
            `(* ,power (expt ,base ,(1- power)) ,(differentiate base))
            `(* ,power (expt ,base ,(1- power))))
        `(* (expt ,base ,power) (log ,base))))

  (define-derivative natural-logarithmic (function expression)
    (declare (ignore function))
    `(/ ,(differentiate expression) ,expression))

  (define-derivative logarithmic (function number base)
    (declare (ignore function))
    `(/ ,(differentiate (cons 'log number)) (* (log ,base) ,number)))

Trigonometric Functions

The following trig functions are supported:

Sine

$f′}(x) ⋅ cos(f(x))$

Cosine

$^f(x) ⋅ -sin(f(x))$

Tangent

$f′}(x) ⋅ {sec(f(x))}^2$

Cosecant

$f′}(x) ⋅ ≤ft( csc(f(x)) - cot(f(x)) \right)$

Cotangent

$f′}(x) ⋅ -{csc(f(x))}^2$

  (define-derivative sin (function expression)
    (declare (ignore function))
    `(* ,(differentiate expression) (cos ,expression)))

  (define-derivative cos (function expression)
    (declare (ignore function))
    `(* ,(differentiate expression) (- (sin ,expression))))

  (define-derivative tan (function expression)
    (declare (ignore function))
    `(* ,(differentiate expression) (expt (sec ,expression) 2)))

  (define-derivative csc (function expression)
    (declare (ignore function))
    `(* ,(differentiate expression) (- (csc ,expression)) (cot ,expression)))

  (define-derivative cot (function expression)
    (declare (ignore function))
    `(* ,(differentiate expression) (- (expt (csc ,expression) 2))))

Driver

This is the derivative driver, differentiate, which in the end is called recursively. It takes an expression (called function), finds a rule using the get-rule function, and applies the rule to the function, ensuring that the function is passed as a list.

  (defun differentiate (function)
    (let ((rule (get-rule function)))
      (when rule
        (apply rule (ensure-list function)))))

Assembly

This assembles the package, placing the contents in the correct order and puts them in the file larcs-differentiate.lisp.

  (in-package #:larcs.differentiate)
  <<sd-rule-storage>>
  <<sd-rule-definition>>
  <<sd-rule-retrieval>>
  <<sd-rules>>
  <<sd-derivative-driver>>

WORKING Symbolic To Typeset Form [0/5]

One of the less important parts of this system is the format converter, which converts between the internal symbolic form and a format that is capable of being typeset using TeX. This is done using a variant of the common rewrite system, but instead of going between variants of the symbolic format, it converts from a symbolic format to string-based format.

WORKING Rule Management [0/2]

To accomplish the task of conversion from symbolic form to typeset form, rules are necessary. It is done using three main things, rule definition, rule retrieval and rule storage.

Define Rules

Rule definitions are built using the define-converter macro, which takes an expression type, a lambda list and a body. It creates a function using the body and the given arguments list, and if it hasn't been pushed onto the storage system, the converter function is pushed into storage.

  (defvar *rules* '())

  (defmacro define-converter (expression-type (&rest arguments-list) &body body)
    (let ((expansion-name (symbolicate expression-type '-conversion)))
      `(progn
         (when (not (member ',expression-type (mapcar #'car *rules*)))
           (setq *rules* (append *rules* '((,expression-type . ,expansion-name)))))
         (defun ,expansion-name (,@arguments-list)
           ,@body))))

Rule Retrieval

Rule retrieval is done by taking an expression, comparing it against given classifications, and from the first classification, returning the second element of the (classification . converter) pair.

  (defun get-rule (expression)
    (cdr (first (remove-if #'(lambda (pair)
                               (let ((type (first pair)))
                                 (not (classified-as-p expression type))))
                           ,*rules*))))

WORKING Rules [0/9]

The following contains all of the defined rules, which are as follows:

  • Numerics

  • Variables

  • Polynomial Terms

  • Multiplicatives

  • Rationals

  • Additives

  • Subtractives

  • Trigonometrics

  • Exponentials & Logarithmics

  <<stf-numerics>>
  <<stf-variables>>
  <<stf-polynomial-terms>>
  <<stf-multiplicatives>>
  <<stf-rationals>>
  <<stf-additives>>
  <<stf-subtractives>>
  <<stf-trigonometrics>>
  <<stf-exponentials-logarithmics>>

Numbers

Numbers are formatted fairly simply, as they are simply surrounded by curly braces, and formatted as to be normal read syntax, which is generally correct.

  (define-converter numeric (number)
    (with-tex-output
      (format nil "{~A}" number)))

Variables

As with numbers, variables are a relatively simple thing to format. If the variable passed is in the *constant-names* list, then it must be a formattable constant for which there is a known TeX command. If there is, it is looked up in the *special-symbols-to-sequences* alist, otherwise, the given variable is downcased and output as a string. Either way, they are surrounded by, as usual, curly braces.

  (define-converter variable (var)
    (if (member var *constant-names*)
        (with-tex-output
          (format nil "{~A}" (cdr (assoc var *special-symbols-to-sequences*))))
        (with-tex-output
          (format nil "{~A}" (string-downcase var)))))

Polynomial Terms

Polynomial Terms are a specific classification, defined as follows:

  • A variable, raised to a numeric power.

  • A number, followed by a single variable.

  • A number, followed by a variable raised to a numeric power.

These are typeset as a single unit, ensuring readability.

  (define-converter polynomial-term (&rest term)
    (let ((variable (term-variable term))
          (coefficient (coefficient term))
          (power (get-power term)))
      (cond
        ((= 1 power)
         (with-tex-output
           (format nil "{~A}{~A}"
                   (convert-for-display coefficient)
                   (convert-for-display power))))
        ((= 0 coefficient)
         (with-tex-output
           (format nil "{~A}^{~A}"
                   (convert-for-display variable)
                   (convert-for-display power))))
        (t
         (with-tex-output
           (format nil "{~A}{~A}^{~A}"
                   (convert-for-display coefficient)
                   (convert-for-display variable)
                   (convert-for-display power)))))))

Multiplicatives

In the case of multiplicatives, which are variadic, a $\ot$ \cdot is placed in between each term, individually converted itself.

  (define-converter multiplicative (op &rest elements)
    (declare (ignore op))
    (with-tex-output
      (format nil "{~{~A~^ \\cdot ~}}"
              (mapcar #'convert-for-display
                      elements))))

Rationals

  (define-converter rational (op numerator denominator)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\frac{~A}{~A}}"
              (convert-for-display numerator)
              (convert-for-display denominator))))

Additives

  (define-converter additive (op &rest terms)
    (declare (ignore op))
    (with-tex-output
      (format nil "{~{~A~^ + ~}}"
              (mapcar #'convert-for-display terms))))

Subtractives

  (define-converter subtractive (op &rest terms)
    (declare (ignore op))
    (with-tex-output
      (format nil "{~{~A~^ - ~}}"
              (mapcar #'convert-for-display terms))))

Trigonometrics

  (define-converter sin (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\sin {~A}}" (convert-for-display term))))

  (define-converter cos (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\cos {~A}}" (convert-for-display term))))

  (define-converter tan (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\tan {~A}}" (convert-for-display term))))

  (define-converter csc (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\csc {~A}}" (convert-for-display term))))

  (define-converter sec (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\sec {~A}}" (convert-for-display term))))

  (define-converter cot (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\cot {~A}}" (convert-for-display term))))

Exponentials and Logarithmics

  (define-converter natural-exponential (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{e^~A}" (convert-for-display term))))

  (define-converter exponential (op base power)
    (declare (ignore op))
    (with-tex-output
      (format nil "{~A^~A}"
              (convert-for-display base)
              (convert-for-display power))))

  (define-converter natural-logarithmic (op term)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\ln ~A}"
              (convert-for-display term))))

  (define-converter logarithmic (op term base)
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\log_~a ~a}"
              (convert-for-display base)
              (convert-for-display term))))

WORKING Converter [2/7]

The convert-for-display function is the driver for this portion of the application, and, in general, uses the previously defined rules, save for the logical functions and, or, not, and the equality operation, summation with big-Sigma, integration and parenthesis.

  (defun convert-for-display (function)
    (if (and (listp function)
           (member (first function) '(and or not = sum integrate parens)))
        (let ((operator (first function)))
          (cond
            ((eq operator 'and)
             <<stf-and-operator>>
             )
            ((eq operator 'or)
             <<stf-or-operator>>
             )
            ((eq operator 'not)
             <<stf-not-operator>>
             )
            ((eq operator '=)
             <<stf-equality-operator>>
             )
            ((eq operator 'sum)
             <<stf-summation>>
             )
            ((eq operator 'integrate)
             <<stf-integration>>
             )
            ((eq operator 'parens)
             <<stf-parenthesis>>
             )))
        (let ((rule (get-rule function)))
          (when rule
            (apply rule (ensure-list function))))))

And

CLOSED: [2016-12-09 Fri 15:20]

Like other rules, this formats a list of other sub-equations, with the symbol $\nd$ ∧~) between each term.

  (destructuring-bind (op &rest terms) function
    (declare (ignore op))
    (with-tex-output
      (format nil "{~{~A~^ \\wedge ~}}"
              (mapcar #'convert-for-display terms))))

Or

CLOSED: [2016-12-09 Fri 15:22]

This does the same thing as "And", replacing the symbol with $\r$ ∨~).

  (destructuring-bind (op &rest terms) function
    (declare (ignore op))
    (with-tex-output
      (format nil "{~{~A~^ \\vee ~}}"
              (mapcar #'convert-for-display terms))))

Not

Foo

  (destructuring-bind (op term) function
    (with-tex-output
      (format nil "{\\not ~A}"
              (convert-for-display term))))

Equality

Foo

  (destructuring-bind (op lhs rhs) function
    (declare (ignore op))
    (format nil "{~A = ~A}"
            (convert-for-display lhs)
            (convert-for-display rhs)))

Summation

  (destructuring-bind (op start stop expression) function
    (declare (ignore op))
    (format nil "{\sum_~A^~A ~A}"
            (convert-for-display start)
            (convert-for-display stop)
            (convert-for-display expression)))

Integration

  (destructuring-bind (op from to expression wrt) function
    (declare (ignore op))
    (with-tex-output
      (format nil "{\\int_~A^~A ~A\\,\\mathrm{d}~A}"
              (convert-for-display from)
              (convert-for-display to)
              (convert-for-display expression)
              (convert-for-display wrt))))

Parenthesis

  (destructuring-bind (op type expression) function
    (declare (ignore op))
    (let* ((types '((square . ("[" . "]"))
                    (curly . ("{" . "}"))
                    (smooth . ("(" . ")"))))
           (left (cadr (assoc type types)))
           (right (cddr (assoc type types))))
      (with-tex-output
        (format nil "{\\left~a {~a} \\right~a}"
                left
                (convert-for-display expression)
                right))))

Special Macros

There is one specialty macro, with-tex-output, which is used to ensure that an expression is wrapped to be a part of correct (La)TeX output. It works by checking to see whether or not the variable *tex-outputp* is true, if so, it simply pass through the given body, and if not, it binds the variable to t, and makes sure that the given body is wrapped in $, allowing the expression to be typeset correctly.

  (defvar *tex-outputp* nil)
  (declaim (special *tex-outputp*))

  (defmacro with-tex-output (&body body)
    `(if *tex-outputp*
         (progn
           ,@body)
         (let ((*tex-outputp* t))
           (format nil "$~a$"
                   (progn
                     ,@body)))))

Assembly

The final assembly of this portion of the system is as simple as the rest, resolving dependencies and placing everything in a single file. As normal, this is done using NoWeb syntax, with everything tangled to the file larcs-typeset.lisp.

  (in-package #:larcs.typeset)
  <<stf-special-macros>>
  <<stf-rule-retrieval>>
  <<stf-define-rule>>
  <<stf-conversion-driver>>
  <<stf-rules>>

WORKING Library Assembly [0/2]

LARCS is primarily a library, and as such, the formal interface for the library, and a way to load it must be provided. This is done here by defining packages centrally and defining an ASDF system to load the library quickly and easily.

WORKING Package Definitions [0/6]

As all of the packages are defined centrally, this makes resolving inter-package dependencies much easier and convenient.


Common

This defines the common package, which keeps a few macros and variables that are used by all other packages. It exports a function to generate arguments lists, a list of special symbols to TeX sequences and a list of constant names.


Classification

Classification is another system that is used by just as many packages as the common packages. In this case, it exports two functions (one to get classification and one to check whether something is classified as a given classification) and a macro (the case pattern for classification).


Polynomial Functions

Polynomial Functions are used in several of the other packages, and provide a way of comparing and retrieving information about specific terms, or providing a way to collect information about variables.


Manipulation

This package provides for a method to manipulate and perform algebra symbolically. In a way, this is the core of the library, upon which nearly everything else is built.


Differentiation

This is a rather simple package, providing a way to differentiate equations for use in a variety of functions.


Typesetting

Again, this is a relatively simple package definition, providing a way to convert symbolic equations to a form that can be more easily read by most humans.


System Definition

This defines the LARCS Library system, allowing the functionality to be loaded by other systems or to provide a way to make development quickly and easily.


Push to bottom   ignore

Version Information

This document is version src_sh{git describe –always –long –dirty –abbrev=10 –tags}.