Mercurial > dylan
view sicm/bk/utils.org @ 8:4dfeaf1a70c0
typos, gender reassignment
author | Dylan Holmes <ocsenave@gmail.com> |
---|---|
date | Thu, 27 Oct 2011 23:02:59 -0500 |
parents | b4de894a1e2e |
children |
line wrap: on
line source
1 #+TITLE:Building a Classical Mechanics Library in Clojure2 #+author: Robert McIntyre & Dylan Holmes3 #+EMAIL: rlm@mit.edu4 #+MATHJAX: align:"left" mathml:t path:"../MathJax/MathJax.js"5 #+STYLE: <link rel="stylesheet" type="text/css" href="../css/aurellem.css" />6 #+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t7 #+SETUPFILE: ../templates/level-0.org8 #+INCLUDE: ../templates/level-0.org9 #+BABEL: :noweb yes :results silent11 * Generic Arithmetic13 #+srcname: generic-arithmetic14 #+begin_src clojure15 (ns sicm.utils)16 (in-ns 'sicm.utils)20 (defn all-equal? [coll]21 (if (empty? (rest coll)) true22 (and (= (first coll) (second coll))23 (recur (rest coll)))))26 (defprotocol Arithmetic27 (zero [this])28 (one [this])29 (negate [this])30 (invert [this])31 (conjugate [this]))33 (defn zero? [x]34 (= x (zero x)))35 (defn one? [x]36 (= x (one x)))40 (extend-protocol Arithmetic41 java.lang.Number42 (zero [x] 0)43 (one [x] 1)44 (negate [x] (- x))45 (invert [x] (/ x))46 )48 (extend-protocol Arithmetic49 clojure.lang.IFn50 (one [f] identity)51 (negate [f] (comp negate f))52 (invert [f] (comp invert f)))55 (extend-protocol Arithmetic56 clojure.lang.Seqable57 (zero [this] (map zero this))58 (one [this] (map one this))59 (invert [this] (map invert this))60 (negate [this] (map negate this)))63 (defn ordered-like64 "Create a comparator using the sorted collection as an65 example. Elements not in the sorted collection are sorted to the66 end."67 [sorted-coll]68 (let [69 sorted-coll? (set sorted-coll)70 ascending-pair?71 (set(reduce concat72 (map-indexed73 (fn [n x]74 (map #(vector x %) (nthnext sorted-coll n)))75 sorted-coll)))]76 (fn [x y]77 (cond78 (= x y) 079 (ascending-pair? [x y]) -180 (ascending-pair? [y x]) 181 (sorted-coll? x) -182 (sorted-coll? y) 1))))86 (def type-precedence87 (ordered-like [incanter.Matrix]))89 (defmulti add90 (fn [x y]91 (sort type-precedence [(type x)(type y)])))93 (defmulti multiply94 (fn [x y]95 (sort type-precedence [(type x) (type y)])))97 (defmethod add [java.lang.Number java.lang.Number] [x y] (+ x y))98 (defmethod multiply [java.lang.Number java.lang.Number] [x y] (* x y))100 (defmethod multiply [incanter.Matrix java.lang.Integer] [x y]101 (let [args (sort #(type-precedence (type %1)(type %2)) [x y])102 matrix (first args)103 scalar (second args)]104 (incanter.core/matrix (map (partial map (partial multiply scalar)) matrix))))106 #+end_src109 * Useful Data Types111 ** Complex Numbers112 #+srcname: complex-numbers113 #+begin_src clojure114 (in-ns 'sicm.utils)116 (defprotocol Complex117 (real-part [z])118 (imaginary-part [z])119 (magnitude-squared [z])120 (angle [z])121 (conjugate [z])122 (norm [z]))124 (defn complex-rectangular125 "Define a complex number with the given real and imaginary126 components."127 [re im]128 (reify Complex129 (real-part [z] re)130 (imaginary-part [z] im)131 (magnitude-squared [z] (+ (* re re) (* im im)))132 (angle [z] (java.lang.Math/atan2 im re))133 (conjugate [z] (complex-rectangular re (- im)))135 Arithmetic136 (zero [z] (complex-rectangular 0 0))137 (one [z] (complex-rectangular 1 0))138 (negate [z] (complex-rectangular (- re) (- im)))139 (invert [z] (complex-rectangular140 (/ re (magnitude-squared z))141 (/ (- im) (magnitude-squared z))))143 Object144 (toString [_]145 (if (and (zero? re) (zero? im)) (str 0)146 (str147 (if (not(zero? re))148 re)149 (if ((comp not zero?) im)150 (str151 (if (neg? im) "-" "+")152 (if ((comp not one?) (java.lang.Math/abs im))153 (java.lang.Math/abs im))154 "i")))))))156 (defn complex-polar157 "Define a complex number with the given magnitude and angle."158 [mag ang]159 (reify Complex160 (magnitude-squared [z] (* mag mag))161 (angle [z] angle)162 (real-part [z] (* mag (java.lang.Math/cos ang)))163 (imaginary-part [z] (* mag (java.lang.Math/sin ang)))164 (conjugate [z] (complex-polar mag (- ang)))166 Arithmetic167 (zero [z] (complex-polar 0 0))168 (one [z] (complex-polar 1 0))169 (negate [z] (complex-polar (- mag) ang))170 (invert [z] (complex-polar (/ mag) (- ang)))172 Object173 (toString [_] (str mag " * e^(i" ang")"))174 ))177 ;; Numbers are complex quantities179 (extend-protocol Complex180 java.lang.Number181 (real-part [x] x)182 (imaginary-part [x] 0)183 (magnitude [x] x)184 (angle [x] 0)185 (conjugate [x] x))188 #+end_src192 ** Tuples and Tensors194 A tuple is a vector which is spinable\mdash{}it can be either /spin195 up/ or /spin down/. (Covariant, contravariant; dual vectors)196 #+srcname: tuples197 #+begin_src clojure198 (in-ns 'sicm.utils)200 (defprotocol Spinning201 (up? [this])202 (down? [this]))204 (defn spin205 "Returns the spin of the Spinning s, either :up or :down"206 [#^Spinning s]207 (cond (up? s) :up (down? s) :down))210 (deftype Tuple211 [spin coll]212 clojure.lang.Seqable213 (seq [this] (seq (.coll this)))214 clojure.lang.Counted215 (count [this] (count (.coll this))))217 (extend-type Tuple218 Spinning219 (up? [this] (= ::up (.spin this)))220 (down? [this] (= ::down (.spin this))))222 (defmethod print-method Tuple223 [o w]224 (print-simple (str (if (up? o) 'u 'd) (.coll o)) w))228 (defn up229 "Create a new up-tuple containing the contents of coll."230 [coll]231 (Tuple. ::up coll))233 (defn down234 "Create a new down-tuple containing the contents of coll."235 [coll]236 (Tuple. ::down coll))239 #+end_src241 *** Contraction242 Contraction is a binary operation that you can apply to compatible243 tuples. Tuples are compatible for contraction if they have the same244 length and opposite spins, and if the corresponding items in each245 tuple are both numbers or both compatible tuples.247 #+srcname: tuples-2248 #+begin_src clojure249 (in-ns 'sicm.utils)251 (defn numbers?252 "Returns true if all arguments are numbers, else false."253 [& xs]254 (every? number? xs))256 (defn contractible?257 "Returns true if the tuples a and b are compatible for contraction,258 else false. Tuples are compatible if they have the same number of259 components, they have opposite spins, and their elements are260 pairwise-compatible."261 [a b]262 (and263 (isa? (type a) Tuple)264 (isa? (type b) Tuple)265 (= (count a) (count b))266 (not= (spin a) (spin b))268 (not-any? false?269 (map #(or270 (numbers? %1 %2)271 (contractible? %1 %2))272 a b))))276 (defn contract277 "Contracts two tuples, returning the sum of the278 products of the corresponding items. Contraction is recursive on279 nested tuples."280 [a b]281 (if (not (contractible? a b))282 (throw283 (Exception. "Not compatible for contraction."))284 (reduce +285 (map286 (fn [x y]287 (if (numbers? x y)288 (* x y)289 (contract x y)))290 a b))))292 #+end_src294 *** Matrices295 #+srcname: matrices296 #+begin_src clojure297 (in-ns 'sicm.utils)298 (require 'incanter.core) ;; use incanter's fast matrices300 (defprotocol Matrix301 (rows [matrix])302 (cols [matrix])303 (diagonal [matrix])304 (trace [matrix])305 (determinant [matrix])306 (transpose [matrix])307 (conjugate [matrix])308 )310 (extend-protocol Matrix311 incanter.Matrix312 (rows [rs] (map down (apply map vector (apply map vector rs))))313 (cols [rs] (map up (apply map vector rs)))314 (diagonal [matrix] (incanter.core/diag matrix) )315 (determinant [matrix] (incanter.core/det matrix))316 (trace [matrix] (incanter.core/trace matrix))317 (transpose [matrix] (incanter.core/trans matrix))318 )320 (defn count-rows [matrix]321 ((comp count rows) matrix))323 (defn count-cols [matrix]324 ((comp count cols) matrix))326 (defn square? [matrix]327 (= (count-rows matrix) (count-cols matrix)))329 (defn identity-matrix330 "Define a square matrix of size n-by-n with 1s along the diagonal and331 0s everywhere else."332 [n]333 (incanter.core/identity-matrix n))339 (defn matrix-by-rows340 "Define a matrix by giving its rows."341 [& rows]342 (if343 (not (all-equal? (map count rows)))344 (throw (Exception. "All rows in a matrix must have the same number of elements."))345 (incanter.core/matrix (vec rows))))347 (defn matrix-by-cols348 "Define a matrix by giving its columns"349 [& cols]350 (if (not (all-equal? (map count cols)))351 (throw (Exception. "All columns in a matrix must have the same number of elements."))352 (incanter.core/matrix (vec (apply map vector cols)))))354 (defn identity-matrix355 "Define a square matrix of size n-by-n with 1s along the diagonal and356 0s everywhere else."357 [n]358 (incanter.core/identity-matrix n))362 (extend-protocol Arithmetic363 incanter.Matrix364 (one [matrix]365 (if (square? matrix)366 (identity-matrix (count-rows matrix))367 (throw (Exception. "Non-square matrices have no multiplicative unit."))))368 (zero [matrix]369 (apply matrix-by-rows (map zero (rows matrix))))370 (negate [matrix]371 (apply matrix-by-rows (map negate (rows matrix))))372 (invert [matrix]373 (incanter.core/solve matrix)))377 (defmulti coerce-to-matrix378 "Converts x into a matrix, if possible."379 type)381 (defmethod coerce-to-matrix incanter.Matrix [x] x)382 (defmethod coerce-to-matrix Tuple [x]383 (if (apply numbers? (seq x))384 (if (up? x)385 (matrix-by-cols (seq x))386 (matrix-by-rows (seq x)))387 (throw (Exception. "Non-numerical tuple cannot be converted into a matrix."))))393 ;; (defn matrix-by-cols394 ;; "Define a matrix by giving its columns."395 ;; [& cols]396 ;; (cond397 ;; (not (all-equal? (map count cols)))398 ;; (throw (Exception. "All columns in a matrix must have the same number of elements."))399 ;; :else400 ;; (reify Matrix401 ;; (cols [this] (map up cols))402 ;; (rows [this] (map down (apply map vector cols)))403 ;; (diagonal [this] (map-indexed (fn [i col] (nth col i) cols)))404 ;; (trace [this]405 ;; (if (not= (count-cols this) (count-rows this))406 ;; (throw (Exception.407 ;; "Cannot take the trace of a non-square matrix."))408 ;; (reduce + (diagonal this))))410 ;; (determinant [this]411 ;; (if (not= (count-cols this) (count-rows this))412 ;; (throw (Exception.413 ;; "Cannot take the determinant of a non-square matrix."))414 ;; (reduce * (map-indexed (fn [i col] (nth col i)) cols))))415 ;; )))417 (extend-protocol Matrix Tuple418 (rows [this] (if (down? this)419 (list this)420 (map (comp up vector) this)))422 (cols [this] (if (up? this)423 (list this)424 (map (comp down vector) this))425 ))427 (defn matrix-multiply428 "Returns the matrix resulting from the matrix multiplication of the given arguments."429 ([A] (coerce-to-matrix A))430 ([A B] (incanter.core/mmult (coerce-to-matrix A) (coerce-to-matrix B)))431 ([M1 M2 & Ms] (reduce matrix-multiply (matrix-multiply M1 M2) Ms)))433 #+end_src436 ** Power Series437 #+srcname power-series438 #+begin_src clojure439 (in-ns 'sicm.utils)440 (use 'clojure.contrib.def)444 (defn series-fn445 "The function corresponding to the given power series."446 [series]447 (fn [x]448 (reduce +449 (map-indexed (fn[n x] (* (float (nth series n)) (float(java.lang.Math/pow (float x) n)) ))450 (range 20)))))452 (deftype PowerSeries453 [coll]454 clojure.lang.Seqable455 (seq [this] (seq (.coll this)))457 clojure.lang.Indexed458 (nth [this n] (nth (.coll this) n 0))459 (nth [this n not-found] (nth (.coll this) n not-found))461 ;; clojure.lang.IFn462 ;; (call [this] (throw(Exception.)))463 ;; (invoke [this & args] args464 ;; (let [f465 ;; )466 ;; (run [this] (throw(Exception.)))467 )469 (defn power-series470 "Returns a power series with the items of the coll as its471 coefficients. Trailing zeros are added to the end of coll."472 [coeffs]473 (PowerSeries. coeffs))475 (defn power-series-indexed476 "Returns a power series consisting of the result of mapping f to the non-negative integers."477 [f]478 (PowerSeries. (map f (range))))481 (defn-memo nth-partial-sum482 ([series n]483 (if (zero? n) (first series)484 (+ (nth series n)485 (nth-partial-sum series (dec n))))))487 (defn partial-sums [series]488 (lazy-seq (map nth-partial-sum (range))))493 (def cos-series494 (power-series-indexed495 (fn[n]496 (if (odd? n) 0497 (/498 (reduce *499 (reduce * (repeat (/ n 2) -1))500 (range 1 (inc n)))501 )))))503 (def sin-series504 (power-series-indexed505 (fn[n]506 (if (even? n) 0507 (/508 (reduce *509 (reduce * (repeat (/ (dec n) 2) -1))510 (range 1 (inc n)))511 )))))513 #+end_src516 * Basic Utilities518 ** Sequence manipulation520 #+srcname: seq-manipulation521 #+begin_src clojure522 (ns sicm.utils)524 (defn do-up525 "Apply f to each number from low to high, presumably for526 side-effects."527 [f low high]528 (doseq [i (range low high)] (f i)))530 (defn do-down531 "Apply f to each number from high to low, presumably for532 side-effects."533 [f high low]534 (doseq [i (range high low -1)] (f i)))537 (defn all-equal? [coll]538 (if (empty? (rest coll)) true539 (and (= (first coll) (second coll))540 (recur (rest coll))))))542 (defn multiplier543 "Returns a function that 'multiplies' the members of a collection,544 returning unit if called on an empty collection."545 [multiply unit]546 (fn [coll] ((partial reduce multiply unit) coll)))548 (defn divider549 "Returns a function that 'divides' the first element of a collection550 by the 'product' of the rest of the collection."551 [divide multiply invert unit]552 (fn [coll]553 (apply554 (fn555 ([] unit)556 ([x] (invert x))557 ([x y] (divide x y))558 ([x y & zs] (divide x (reduce multiply y zs))))559 coll)))561 (defn left-circular-shift562 "Remove the first element of coll, adding it to the end of coll."563 [coll]564 (concat (rest coll) (take 1 coll)))566 (defn right-circular-shift567 "Remove the last element of coll, adding it to the front of coll."568 [coll]569 (cons (last coll) (butlast coll)))570 #+end_src575 ** Ranges, Airity and Function Composition576 #+srcname: arity577 #+begin_src clojure578 (in-ns 'sicm.utils)579 (def infinity Double/POSITIVE_INFINITY)580 (defn infinite? [x] (Double/isInfinite x))581 (def finite? (comp not infinite?))583 (defn arity-min584 "Returns the smallest number of arguments f can take."585 [f]586 (apply587 min588 (map (comp alength #(.getParameterTypes %))589 (filter (comp (partial = "invoke") #(.getName %))590 (.getDeclaredMethods (class f))))))592 (defn arity-max593 "Returns the largest number of arguments f can take, possibly594 Infinity."595 [f]596 (let [methods (.getDeclaredMethods (class f))]597 (if (not-any? (partial = "doInvoke") (map #(.getName %) methods))598 (apply max599 (map (comp alength #(.getParameterTypes %))600 (filter (comp (partial = "invoke") #(.getName %)) methods)))601 infinity)))604 (def ^{:arglists '([f])605 :doc "Returns a two-element list containing the minimum and606 maximum number of args that f can take."}607 arity-interval608 (juxt arity-min arity-max))612 ;; --- intervals614 (defn intersect615 "Returns the interval of overlap between interval-1 and interval-2"616 [interval-1 interval-2]617 (if (or (empty? interval-1) (empty? interval-2)) []618 (let [left (max (first interval-1) (first interval-2))619 right (min (second interval-1) (second interval-2))]620 (if (> left right) []621 [left right]))))623 (defn same-endpoints?624 "Returns true if the left endpoint is the same as the right625 endpoint."626 [interval]627 (= (first interval) (second interval)))629 (defn naturals?630 "Returns true if the left endpoint is 0 and the right endpoint is631 infinite."632 [interval]633 (and (zero? (first interval))634 (infinite? (second interval))))637 (defn fan-in638 "Returns a function that pipes its input to each of the gs, then639 applies f to the list of results. Consequently, f must be able to640 take a number of arguments equal to the number of gs."641 [f & gs]642 (fn [& args]643 (apply f (apply (apply juxt gs) args))))645 (defn fan-in646 "Returns a function that pipes its input to each of the gs, then applies f to the list of results. The resulting function takes any number of arguments, but will fail if given arguments that are incompatible with any of the gs."647 [f & gs]648 (comp (partial apply f) (apply juxt gs)))652 (defmacro airty-blah-sad [f n more?]653 (let [syms (vec (map (comp gensym (partial str "x")) (range n)))654 optional (gensym "xs")]655 (if more?656 `(fn ~(conj syms '& optional)657 (apply ~f ~@syms ~optional))658 `(fn ~syms (~f ~@syms)))))660 (defmacro airt-whaa* [f n more?]661 `(airty-blah-sad ~f ~n ~more?))666 (defn fan-in*667 "Returns a function that pipes its input to each of the gs, then668 applies f to the list of results. Unlike fan-in, fan-in* strictly669 enforces arity: it will fail if the gs do not have compatible670 arities."671 [f & gs]672 (let [arity-in (reduce intersect (map arity-interval gs))673 left (first arity-in)674 right (second arity-in)675 composite (fan-in f gs)676 ]677 (cond678 (empty? arity-in)679 (throw (Exception. "Cannot compose functions with incompatible arities."))681 (not682 (or (= left right)683 (and (finite? left)684 (= right infinity))))686 (throw (Exception.687 "Compose can only handle arities of the form [n n] or [n infinity]"))688 :else689 (airty-blah-sad composite left (= right infinity)))))693 (defn compose-n "Compose any number of functions together."694 ([] identity)695 ([f] f)696 ([f & fs]697 (let [fns (cons f fs)]698 (compose-bin (reduce fan-in (butlast fs)) (last fs))))699 )706 (defn iterated707 ([f n id] (reduce comp id (repeat n f)))708 ([f n] (reduce comp identity (repeat n f))))710 (defn iterate-until-stable711 "Repeatedly applies f to x, returning the first result that is close712 enough to its predecessor."713 [f close-enough? x]714 (second (swank.util/find-first715 (partial apply close-enough?)716 (partition 2 1 (iterate f x)))))718 (defn lexical< [x y]719 (neg? (compare (str x) (str y))))722 ;; do-up723 ;; do-down724 (def make-pairwise-test comparator)725 ;;all-equal?726 (def accumulation multiplier)727 (def inverse-accumulation divider)728 ;;left-circular-shift729 ;;right-circular-shift730 (def exactly-n? same-endpoints?)731 (def any-number? naturals?)732 ;; TODO compose733 ;; TODO compose-n734 ;; identity735 (def compose-2 fan-in)736 (def compose-bin fan-in*)737 (def any? (constantly true))738 (def none? (constantly false))739 (def constant constantly)740 (def joint-arity intersect)741 (def a-reduce reduce)742 ;; filter743 (def make-map (partial partial map) )744 (def bracket juxt)745 ;; TODO apply-to-all746 ;; TODO nary-combine747 ;; TODO binary-combine748 ;; TODO unary-combine749 ;; iterated750 ;; iterate-until-stable751 (def make-function-of-vector (partial partial map))752 (def make-function-of-arguments (fn [f] (fn [& args] (f args))))753 (def alphaless lexical<)755 #+end_src763 :765 * Numerical Methods766 #+srcname: numerical-methods767 #+begin_src clojure768 (in-ns 'sicm.utils)769 (import java.lang.Math)770 (use 'clojure.contrib.def)772 ;; ---- USEFUL CONSTANTS774 (defn machine-epsilon775 "Smallest value representable on your machine, as determined by776 successively dividing a number in half until consecutive results are777 indistinguishable."778 []779 (ffirst780 (drop-while781 (comp not zero? second)782 (partition 2 1783 (iterate (partial * 0.5) 1)))))786 (def pi (Math/PI))787 (def two-pi (* 2 pi))789 (def eulers-gamma 0.5772156649015328606065)791 (def phi (/ (inc (Math/sqrt 5)) 2))793 (def ln2 (Math/log 2))794 (def ln10 (Math/log 10))795 (def exp10 #(Math/pow 10 %))796 (def exp2 #(Math/pow 2 %))799 ;;801 ;; ---- ANGLES AND TRIGONOMETRY803 (defn angle-restrictor804 "Returns a function that ensures that angles lie in the specified interval of length two-pi."805 [max-angle]806 (let [min-angle (- max-angle two-pi)]807 (fn [x]808 (if (and809 (<= min-angle x)810 (< x max-angle))811 x812 (let [corrected-x (- x (* two-pi (Math/floor (/ x two-pi))))]813 (if (< corrected-x max-angle)814 corrected-x815 (- corrected-x two-pi)))))))817 (defn angle-restrict-pi818 "Coerces angles to lie in the interval from -pi to pi."819 [angle]820 ((angle-restrictor pi) angle))822 (defn angle-restrict-two-pi823 "Coerces angles to lie in the interval from zero to two-pi"824 [angle]825 ((angle-restrictor two-pi) angle))830 (defn invert [x] (/ x))831 (defn negate [x] (- x))833 (defn exp [x] (Math/exp x))835 (defn sin [x] (Math/sin x))836 (defn cos [x] (Math/cos x))837 (defn tan [x] (Math/tan x))839 (def sec (comp invert cos))840 (def csc (comp invert sin))842 (defn sinh [x] (Math/sinh x))843 (defn cosh [x] (Math/cosh x))844 (defn tanh [x] (Math/tanh x))846 (def sech (comp invert cosh))847 (def csch (comp invert sinh))850 ;; ------------852 (defn factorial853 "Computes the factorial of the nonnegative integer n."854 [n]855 (if (neg? n)856 (throw (Exception. "Cannot compute the factorial of a negative number."))857 (reduce * 1 (range 1 (inc n)))))859 (defn exact-quotient [n d] (/ n d))861 (defn binomial-coefficient862 "Computes the number of different ways to choose m elements from n."863 [n m]864 (assert (<= 0 m n))865 (let [difference (- n m)]866 (exact-quotient867 (reduce * (range n (max difference m) -1 ))868 (factorial (min difference m)))))870 (defn-memo stirling-1871 "Stirling numbers of the first kind: the number of permutations of n872 elements with exactly m permutation cycles. "873 [n k]874 ;(assert (<= 1 k n))875 (if (zero? n)876 (if (zero? k) 1 0)877 (+ (stirling-1 (dec n) (dec k))878 (* (dec n) (stirling-1 (dec n) k)))))880 (defn-memo stirling-2 ;;count-partitions881 "Stirling numbers of the second kind: the number of ways to partition a set of n elements into k subsets."882 [n k]883 (cond884 (= k 1) 1885 (= k n) 1886 :else (+ (stirling-2 (dec n) (dec k))887 (* k (stirling-2 (dec n) k)))))889 (defn harmonic-number [n]890 (/ (stirling-1 (inc n) 2)891 (factorial n)))894 (defn sum895 [f low high]896 (reduce + (map f (range low (inc high)))))898 #+end_src911 * Differentiation913 We compute derivatives by passing special *differential objects* $[x,914 dx]$ through functions. Roughly speaking, applying a function $f$ to a915 differential object \([x, dx]\) should produce a new differential916 object $[f(x),\,Df(x)\cdot dx]$.918 \([x,\,dx]\xrightarrow{\quad f \quad}[f(x),\,Df(x)\cdot dx]\)919 Notice that you can obtain the derivative of $f$ from this920 differential object, as it is the coefficient of the $dx$ term. Also,921 as you apply successive functions using this rule, you get the922 chain-rule answer you expect:924 \([f(x),\,Df(x)\cdot dx]\xrightarrow{\quad g\quad} [gf(x),\,925 Dgf(x)\cdot Df(x) \cdot dx ]\)927 In order to generalize to multiple variables and multiple derivatives,928 we use a *power series of differentials*, a sortred infinite sequence which929 contains all terms like $dx\cdot dy$, $dx^2\cdot dy$, etc.932 #+srcname:differential933 #+begin_src clojure934 (in-ns 'sicm.utils)935 (use 'clojure.contrib.combinatorics)936 (use 'clojure.contrib.generic.arithmetic)938 (defprotocol DifferentialTerm939 "Protocol for an infinitesimal quantity."940 (coefficient [this])941 (partials [this]))943 (extend-protocol DifferentialTerm944 java.lang.Number945 (coefficient [this] this)946 (partials [this] []))948 (deftype DifferentialSeq949 [terms]950 clojure.lang.IPersistentCollection951 (cons [this x]952 (throw (Exception. x))953 (DifferentialSeq. (cons x terms)))954 ;; (seq [this] (seq terms))955 (count [this] (count terms))956 (empty [this] (empty? terms)))968 (defn coerce-to-differential-seq [x]969 (cond970 (= (type x) DifferentialSeq) x971 (satisfies? DifferentialTerm x) (DifferentialSeq. x)))974 (defn differential-term975 "Returns a differential term with the given coefficient and976 partials. Coefficient may be any arithmetic object; partials must977 be a list of non-negative integers."978 [coefficient partials]979 (reify DifferentialTerm980 (partials [_] (set partials))981 (coefficient [_] coefficient)))985 (defn differential-seq*986 ([coefficient partials]987 (DifferentialSeq. [(differential-term coefficient partials)]))988 ([coefficient partials & cps]989 (if cps994 (defn differential-seq995 "Constructs a sequence of differential terms from a numerical996 coefficient and a list of keys for variables. If no coefficient is supplied, uses 1."997 ([variables] (differential-seq 1 variables))998 ([coefficient variables & cvs]999 (if (number? coefficient)1000 (conj (assoc {} (apply sorted-set variables) coefficient)1001 (if (empty? cvs)1002 nil1003 (apply differential-seq cvs)))1004 (apply differential-seq 1 coefficient 1 variables cvs)1005 )))1008 (defn differential-add1009 "Add two differential sequences by combining like terms."1010 [dseq1 dseq2]1011 (merge-with + dseq1 dseq2))1013 (defn differential-multiply1014 "Multiply two differential sequences. The square of any differential variable is zero since differential variables are infinitesimally small."1015 [dseq1 dseq2]1016 (reduce1017 (fn [m [[vars1 coeff1] [vars2 coeff2]]]1018 (if (empty? (clojure.set/intersection vars1 vars2))1019 (assoc m (clojure.set/union vars1 vars2) (* coeff1 coeff2))1020 m))1021 {}1022 (clojure.contrib.combinatorics/cartesian-product1023 dseq11024 dseq2)))1027 (defn big-part1028 "Returns the part of the differential sequence that is finite,1029 i.e. not infinitely small."1030 [dseq]1031 (let1032 [keys (sort-by count (keys dseq))1033 smallest-var (last(last keys))]1034 (apply hash-map1035 (reduce concat1036 (remove (comp smallest-var first) dseq)))))1038 (defn small-part1039 "Returns the part of the differential sequence that is1040 infinitesimal."1041 [dseq]1042 (let1043 [keys (sort-by count (keys dseq))1044 smallest-var (last(last keys))]1045 (apply hash-map1046 (reduce concat1047 (filter (comp smallest-var first) dseq)))))1049 (defn big-part1050 "Returns the 'finite' part of the differential sequence."1051 [dseq]1052 (let1053 [keys (sort-by count (keys dseq))1054 smallest-var (last (last keys))]1055 (apply hash-map1056 (reduce concat1057 (filter (remove smallest-var first) dseq)1058 ))))1061 (defn small-part1062 "Returns the 'infinitesimal' part of the differential sequence."1063 [dseq]1064 (let1065 [keys (sort-by count (keys dseq))1066 smallest-var (last (last keys))]1068 (apply hash-map1069 (reduce concat1070 (filter (comp smallest-var first) dseq)1071 ))))1073 (defn linear-approximator1074 "Returns the function that corresponds to unary-fn but which accepts and returns differential objects."1075 ([unary-f dfdx]1076 (fn F [dseq]1077 (differential-add1078 (unary-f (big-part dseq))1079 (differential-multiply1080 (dfdx (big-part dseq))1081 (small-part dseq))))))1088 ;; ;; A differential term consists of a numerical coefficient and a1089 ;; ;; sorted1090 ;; (defrecord DifferentialTerm [coefficient variables])1091 ;; (defmethod print-method DifferentialTerm1092 ;; [o w]1093 ;; (print-simple1094 ;; (apply str (.coefficient o)(map (comp (partial str "d") name) (.variables o)))1095 ;; w))1098 ;; (defn differential-seq1099 ;; "Constructs a sequence of differential terms from a numerical1100 ;; coefficient and a list of keywords for variables. If no coefficient is1101 ;; supplied, uses 1."1102 ;; ([variables] (differential-seq 1 variables))1103 ;; ([coefficient variables]1104 ;; (list1105 ;; (DifferentialTerm. coefficient (apply sorted-set variables))))1106 ;; ([coefficient variables & cvs]1107 ;; (sort-by1108 ;; #(vec(.variables %))1109 ;; (concat (differential-seq coefficient variables) (apply differential-seq cvs)))))1111 #+end_src1125 * Symbolic Manipulation1127 #+srcname:symbolic1128 #+begin_src clojure1129 (in-ns 'sicm.utils)1131 (deftype Symbolic [type expression]1132 Object1133 (equals [this that]1134 (cond1135 (= (.expression this) (.expression that)) true1136 :else1137 (Symbolic.1138 java.lang.Boolean1139 (list '= (.expression this) (.expression that)))1140 )))1145 (deftype AbstractSet [glyph membership-test])1146 (defn member? [abstract-set x]1147 ((.membership-test abstract-set) x))1149 ;; ------------ Some important AbstractSets1152 (def Real1153 (AbstractSet.1154 'R1155 (fn[x](number? x))))1158 ;; ------------ Create new AbstractSets from existing ones1160 (defn abstract-product1161 "Gives the cartesian product of abstract sets."1162 ([sets]1163 (if (= (count sets) 1) (first sets)1164 (AbstractSet.1165 (symbol1166 (apply str1167 (interpose 'x (map #(.glyph %) sets))))1168 (fn [x]1169 (and1170 (coll? x)1171 (= (count sets) (count x))1172 (reduce #(and %1 %2)1173 true1174 (map #(member? %1 %2) sets x)))))))1175 ([abstract-set n]1176 (abstract-product (repeat n abstract-set))))1180 (defn abstract-up1181 "Returns the abstract set of all up-tuples whose items belong to the1182 corresponding abstract sets in coll."1183 ([coll]1184 (AbstractSet.1185 (symbol (str "u["1186 (apply str1187 (interpose " "1188 (map #(.glyph %) coll)))1189 "]"))1190 (fn [x]1191 (and1192 (satisfies? Spinning x)1193 (up? x)1194 (= (count coll) (count x))1195 (reduce1196 #(and %1 %2)1197 true1198 (map #(member? %1 %2) coll x))))))1199 ([abstract-set n]1200 (abstract-up (repeat n abstract-set))))1203 (defn abstract-down1204 "Returns the abstract set of all down-tuples whose items belong to the1205 corresponding abstract sets in coll."1206 ([coll]1207 (AbstractSet.1208 (symbol (str "d["1209 (apply str1210 (interpose " "1211 (map #(.glyph %) coll)))1212 "]"))1213 (fn [x]1214 (and1215 (satisfies? Spinning x)1216 (down? x)1217 (= (count coll) (count x))1218 (reduce1219 #(and %1 %2)1220 true1221 (map #(member? %1 %2) coll x))))))1222 ([abstract-set n]1223 (abstract-down (repeat n abstract-set))))1229 ;-------ABSTRACT FUNCTIONS1230 (defrecord AbstractFn1231 [#^AbstractSet domain #^AbstractSet codomain])1234 (defmethod print-method AbstractFn1235 [o w]1236 (print-simple1237 (str1238 "f:"1239 (.glyph (:domain o))1240 "-->"1241 (.glyph (:codomain o))) w))1242 #+end_src1245 * COMMENT1246 #+begin_src clojure :tangle utils.clj1247 (ns sicm.utils)1249 ;***** GENERIC ARITHMETIC1250 <<generic-arithmetic>>1253 ;***** TUPLES AND MATRICES1254 <<tuples>>1255 <<tuples-2>>1256 ;***** MATRICES1257 <<matrices>>1259 #+end_src