diff --git a/.cljfmt-vscode.edn b/.cljfmt-vscode.edn new file mode 100644 index 00000000..39a078ed --- /dev/null +++ b/.cljfmt-vscode.edn @@ -0,0 +1,3 @@ +{:sort-ns-references? true + :extra-indents {sci-macro [[:block 1]]} + } diff --git a/CHANGELOG.md b/CHANGELOG.md index fdcc77d9..713c6b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,52 @@ ## [unreleased] +- #149 + + - Retires the Value protocol in favor of MultiFns in the generic scope. + Doing this carefully, by revoking the existing implementation and + then restoring it step by step, revealed some interesting corner cases: + + - `(zero? [0])` but `(not (zero? (lazy-seq [0])))` + - `(not (zero? (series 0)))` + + The only remaining element of the Value protocol was the `kind` function, + used to classify arguments for multi-dispatch. The protocol has therefore + been renamed IKind. + + - Changed behavior: + + - Objects produced by `make-literal` + + In the protocol regime, the implementation of `zero?` was dispatched via the + Literal class; with the MultiFn, it is dispatched by the `kind`, which + is supplied by the creator. The protocol dispatch effectively used + `numeric-zero?`. That behavior is replicated for the kind + `:emmy.expression/numeric`; if you create literals of a different kind, + you can inherit this behavior using `derive` or supply a `defmethod` + yourself. + + - We now prefer to let Clojure internals report an exception whenever + an unimplemented MultiFn in what was formerly the emmy.value/Value + protocol instead of defining a method that throws. The exception thrown + in such cases therefore changes from UnsupportedOperationException to + IllegalArgumentException. + + - For this reason we have avoided defining `:default` handlers for + generic MultiFns, despite the fact that defaults are certainly + convenient in some cases (like `false` for `exact?` and having + the default case for `freeze` pass through). + + - The default exponentiation routine built on generic multiplication + would, in the case of raising to the zero power, return the one-like + of the exponent; now it returns the one-like of the base, which is + correct. + + - The generic `negative?` required Numbers instead of Comparables; + this is fixed. + + - Documentation string for `pochhammer` corrected. + - #145 (thank you to @mhuebert for amazing work here!!): - Adds `emmy.util/sci-macro` for defining macros meant to be exposed via SCI, @@ -1868,7 +1914,7 @@ On to the detailed notes! - new functions: `basis-components->vector-field`, `vector-field->basis-components` - - vector fields now implement `v/zero?` and `v/zero-like` by returning + - vector fields now implement `g/zero?` and `v/zero-like` by returning proper vector fields. - form fields, in `sicmutils.calculus.vector-field`: @@ -1879,7 +1925,7 @@ On to the detailed notes! - `Alt`, `alt-wedge` provide alternate wedge product definitions - - form fields now implement `v/zero?` and `v/zero-like` by returning + - form fields now implement `g/zero?` and `v/zero-like` by returning proper form fields that retain their rank. - form fields now correctly multiply via `*` by using @@ -1939,7 +1985,7 @@ On to the detailed notes! - `rotate-{x,y,z}-tuple` are now aliased into `sicmutils.env`. - `Operator` instances now ignore the right operator in operator-operator - addition if the left operator passes a `v/zero?` test. Contexts are still + addition if the left operator passes a `g/zero?` test. Contexts are still appropriately merged. - in `sicmutils.simplify.rules`, the `sqrt-contract` ruleset now takes a @@ -3079,8 +3125,8 @@ On to the detailed release notes: - `m/identity-like` returns an identity matrix (given a square matrix) with entries of identical type, but set appropriately to zero or one. This is installed as `v/one-like` and `v/identity-like`. - - `v/identity?` now returns true for identity matrices, false otherwise. - `v/one?` returns `false` for identity matrices! If it didn't, `(* 2 (I 10))` + - `g/identity?` now returns true for identity matrices, false otherwise. + `g/one?` returns `false` for identity matrices! If it didn't, `(* 2 (I 10))` would return `2`, since `one?` signals multiplicative identity. - `sicmutils.structure/up` and `sicmutils.structure/down` now have analogous diff --git a/package.json b/package.json index 40096316..7a8188f0 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@nextjournal/lezer-clojure": "1.0.0", "complex.js": "^2.1.1", "d3-require": "1.3.0", - "fraction.js": "^4.2.0", + "fraction.js": "4.2.1", "framer-motion": "6.5.1", "katex": "0.12.0", "markdown-it": "12.3.2", diff --git a/src/deps.cljs b/src/deps.cljs index da572cfa..42a7d98f 100644 --- a/src/deps.cljs +++ b/src/deps.cljs @@ -1,4 +1,4 @@ {:npm-deps {"complex.js" "^2.1.1" - "fraction.js" "^4.2.0" + "fraction.js" "4.2.1" "odex" "3.0.0-rc.4"}} diff --git a/src/emmy/abstract/function.cljc b/src/emmy/abstract/function.cljc index cbb714b8..caa0cb6d 100644 --- a/src/emmy/abstract/function.cljc +++ b/src/emmy/abstract/function.cljc @@ -1,8 +1,8 @@ #_"SPDX-License-Identifier: GPL-3.0" ^#:nextjournal.clerk -{:toc true - :visibility :hide-ns} + {:toc true + :visibility :hide-ns} (ns emmy.abstract.function "Implementation of a [[literal-function]] constructor. Literal functions can be applied to structures and numeric inputs, and differentiated. @@ -65,17 +65,7 @@ (sicm-set->exemplar range)]) (deftype Function [f-name arity domain range] - v/Value - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [_] (fn [& _] (v/zero-like range))) - (one-like [_] (fn [& _] (v/one-like range))) - (identity-like [_] - (let [meta {:arity arity :from :identity-like}] - (with-meta identity meta))) - (exact? [f] (f/compose v/exact? f)) - (freeze [_] (v/freeze f-name)) + v/IKind (kind [_] ::function) f/IArity @@ -229,7 +219,8 @@ (entry->fn entry)]) litfns))) -(u/sci-macro with-literal-functions [litfns & body] +(u/sci-macro with-literal-functions + [litfns & body] (let [pairs (binding-pairs litfns) bindings (into [] cat pairs)] `(let ~bindings ~@body))) @@ -263,7 +254,7 @@ partials (s/map-chain (fn [x path _] (let [dx (d/tangent-part x tag)] - (if (v/zero? dx) + (if (g/zero? dx) 0 (d/d:* (literal-apply (literal-partial f path) ve) @@ -299,7 +290,7 @@ (check-argument-type f xs (domain-types f) [0]) (if (some d/perturbed? xs) (literal-derivative f xs) - (an/literal-number `(~(name f) ~@(map v/freeze xs))))) + (an/literal-number `(~(name f) ~@(map g/freeze xs))))) ;; ## Specific Generics ;; @@ -311,3 +302,11 @@ (f/arity f) (domain-types f) (range-type f))) + +(defmethod g/zero-like [::function] [^Function a] (fn [& _] (g/zero-like (.-range a)))) +(defmethod g/one-like [::function] [^Function a] (fn [& _] (g/one-like (.-range a)))) +(defmethod g/identity-like [::function] [^Function a] + (let [meta {:arity (.-arity a) :from :identity-like}] + (with-meta identity meta))) +(defmethod g/exact? [::function] [a] (f/compose g/exact? a)) +(defmethod g/freeze [::function] [^Function a] (g/freeze (.-f-name a))) diff --git a/src/emmy/abstract/number.cljc b/src/emmy/abstract/number.cljc index 5a345efe..9d45f0b8 100644 --- a/src/emmy/abstract/number.cljc +++ b/src/emmy/abstract/number.cljc @@ -19,15 +19,7 @@ v/Numerical (numerical? [_] true) - v/Value - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (exact? [_] false) - (freeze [o] o) + v/IKind (kind [_] Symbol)) (defn literal-number @@ -211,6 +203,11 @@ ;; whether or not they are negative, we return /something/. Maybe this is ;; ill-founded, but it was required for some polynomial code. (defmethod g/negative? [::x/numeric] [_] false) +(defmethod g/zero? [Symbol] [_] false) +(defmethod g/one? [Symbol] [_] false) +(defmethod g/identity? [Symbol] [_] false) +(defmethod g/freeze [Symbol] [s] s) +(defmethod g/exact? [Symbol] [_] false) (defmethod g/simplify [Symbol] [a] a) (defmethod g/simplify [::x/numeric] [a] diff --git a/src/emmy/calculus/covariant.cljc b/src/emmy/calculus/covariant.cljc index e7837964..6e971fdb 100644 --- a/src/emmy/calculus/covariant.cljc +++ b/src/emmy/calculus/covariant.cljc @@ -22,7 +22,7 @@ ;; This comes from `Lie.scm`. (defn- vector-field-Lie-derivative [X] - (let [freeze-X (v/freeze X) + (let [freeze-X (g/freeze X) op-name `(~'Lie-derivative ~freeze-X)] (-> (fn rec [Y] (cond (f/function? Y) (X Y) @@ -42,7 +42,7 @@ (let [xs (update vectors i (g/Lie-derivative X))] (apply Y xs))) 0 k)))) - name `((~'Lie-derivative ~freeze-X) ~(v/freeze Y))] + name `((~'Lie-derivative ~freeze-X) ~(g/freeze Y))] (ff/procedure->nform-field op k name)) (s/structure? Y) @@ -105,7 +105,7 @@ (-> (fn [F] (g/* (D F) R)) (o/make-operator - (list 'Lie-D (v/freeze R))))) + (list 'Lie-D (g/freeze R))))) ;; ## Interior Product, from interior-product.scm @@ -121,8 +121,8 @@ (assert (= (dec p) (count vectors))) (apply alpha X vectors)) (dec p) - `((~'interior-product ~(v/freeze X)) - ~(v/freeze alpha)))))) + `((~'interior-product ~(g/freeze X)) + ~(g/freeze alpha)))))) ;; ## Covariant Derivative, from covariant-derivative.scm @@ -227,8 +227,8 @@ (vf/procedure->vector-field (fn the-derivative [f] (g/* (vector-basis f) deriv-components)) - `((~'nabla ~(v/freeze V)) - ~(v/freeze U))))))))) + `((~'nabla ~(g/freeze V)) + ~(g/freeze U))))))))) (defn- covariant-derivative-form [Cartan] (fn [V] @@ -244,8 +244,8 @@ (let [xs (update vectors i nabla_V)] (apply tau xs))) 0 k)))) - name `((~'nabla ~(v/freeze V)) - ~(v/freeze tau))] + name `((~'nabla ~(g/freeze V)) + ~(g/freeze tau))] (ff/procedure->nform-field op k name))))) (defn- covariant-derivative-argument-types @@ -367,9 +367,9 @@ :else (u/unsupported (str "Can't do this kind of covariant derivative yet " - (v/freeze X) " @ " (v/freeze V))))) + (g/freeze X) " @ " (g/freeze V))))) name `(~'nabla - ~(v/freeze X))] + ~(g/freeze X))] (o/make-operator op name)))) (defn covariant-derivative diff --git a/src/emmy/calculus/form_field.cljc b/src/emmy/calculus/form_field.cljc index cb79bbe3..a207106a 100644 --- a/src/emmy/calculus/form_field.cljc +++ b/src/emmy/calculus/form_field.cljc @@ -23,8 +23,7 @@ [emmy.structure :as s] [emmy.util :as u] [emmy.util.aggregate :as ua] - [emmy.util.permute :as permute] - [emmy.value :as v])) + [emmy.util.permute :as permute])) ;; ## Form fields ;; @@ -89,7 +88,7 @@ "Given some form field `op`, returns a form field with the same context and its procedure replaced by `ff:zero`. - The returned form field responds `true` to `v/zero?`." + The returned form field responds `true` to `g/zero?`." [op] {:pre [(form-field? op)]} (o/make-operator ff:zero @@ -189,7 +188,7 @@ function on the manifold." ([components coordinate-system] (let [name `(~'oneform-field - ~(v/freeze components))] + ~(g/freeze components))] (components->oneform-field components coordinate-system name))) ([components coordinate-system name] @@ -355,7 +354,7 @@ {:pre [(vf/vector-field? vf)]} (fn [m] ((vf f) m))) vf-structure)) - name `(~'d ~(v/freeze f))] + name `(~'d ~(g/freeze f))] (procedure->oneform-field op name))) (def differential-of-function @@ -401,8 +400,8 @@ (permute/permutation-sequence args) (cycle [1 -1]))))) name `(~'wedge - ~(v/freeze form1) - ~(v/freeze form2))] + ~(g/freeze form1) + ~(g/freeze form2))] (procedure->nform-field w n name)))))) (defn wedge @@ -446,7 +445,7 @@ (permute/permutation-sequence args) (cycle [1 -1])))))] (procedure->nform-field - alternation n `(~'Alt ~(v/freeze form))))))) + alternation n `(~'Alt ~(g/freeze form))))))) (defn- tensor-product2 ([t1] t1) @@ -464,8 +463,8 @@ (apply t2 a2))))] (procedure->nform-field tp n `(~'tensor-product - ~(v/freeze t1) - ~(v/freeze t2)))))))) + ~(g/freeze t1) + ~(g/freeze t2)))))))) (defn- w2 ([form1] form1) @@ -562,7 +561,7 @@ 0 (inc k)) 0))))] (procedure->nform-field - k+1form (inc k) `(~'d ~(v/freeze kform))))))) + k+1form (inc k) `(~'d ~(g/freeze kform))))))) (def exterior-derivative (o/make-operator diff --git a/src/emmy/calculus/manifold.cljc b/src/emmy/calculus/manifold.cljc index c2f0a6bb..4a90ce64 100644 --- a/src/emmy/calculus/manifold.cljc +++ b/src/emmy/calculus/manifold.cljc @@ -596,7 +596,7 @@ (let [[x y] rep rsq (g/+ (g/square x) (g/square y))] - (when (v/zero? rsq) + (when (g/zero? rsq) (u/illegal-state "PolarCylindrical singular")) (-> rep (assoc 0 (g/sqrt rsq)) @@ -625,7 +625,7 @@ (check-coordinates [_ coords] (and (s/up? coords) (= (g/dimension coords) - (:dimension manifold)) + (:dimension manifold))* (or (not (v/number? coords)) (>= (nth coords 0) 0)))) @@ -656,7 +656,8 @@ (g/+ (g/square x) (g/square y) (g/square z)))] - (when (v/zero? r) + (println "r is" r) + (when (g/zero? r) (u/illegal-state "SphericalCylindrical singular")) (-> rep (assoc 0 r) @@ -715,7 +716,7 @@ (g/square y) (g/square z)))] (when (and (v/number? r) - (v/zero? r)) + (g/zero? r)) (throw (ex-info "->SpacetimeSpherical singular: " {:point point @@ -862,7 +863,7 @@ (fn [] (letfn [(safe-atan [y x] (when (and (number? y) (number? x) - (v/zero? y) (v/zero? x)) + (g/zero? y) (g/zero? x)) (log/warn "Sn-coordinates singular!")) (g/atan y x))] (let [pt (rotate-left @@ -1042,7 +1043,7 @@ final-coord (nth pt n)] (when (and (v/number? final-coord) (or (g/negative? final-coord) - (v/zero? final-coord))) + (g/zero? final-coord))) (throw (ex-info "Point not covered by S^n-gnomic coordinate patch." {:point point @@ -1075,7 +1076,7 @@ (= (g/dimension coords) n) (let [c0 (nth coords 0)] (or (not (v/number? c0)) - (not (v/zero? c0)))))) + (not (g/zero? c0)))))) (check-point [_ point] (my-manifold-point? point manifold)) diff --git a/src/emmy/calculus/map.cljc b/src/emmy/calculus/map.cljc index d0f20d11..598e16e7 100644 --- a/src/emmy/calculus/map.cljc +++ b/src/emmy/calculus/map.cljc @@ -11,9 +11,9 @@ [emmy.calculus.manifold :as m] [emmy.calculus.vector-field :as vf] [emmy.function :as f] + [emmy.generic :as g] [emmy.structure :as s] - [emmy.util :as u] - [emmy.value :as v])) + [emmy.util :as u])) ;; ## Maps between Manifolds ;; @@ -49,8 +49,8 @@ (let [v-on-M (fn [g-on-M] (v-on-N (f/compose g-on-M mu:N->M))) - name `((~'d ~(v/freeze mu:N->M)) - ~(v/freeze v-on-N))] + name `((~'d ~(g/freeze mu:N->M)) + ~(g/freeze v-on-N))] (vf/procedure->vector-field v-on-M name)))) (def differential @@ -69,8 +69,8 @@ (let [op (fn [f] (f/compose (v-on-N (f/compose f mu:N->M)) mu-inverse:M->N)) - name `((~'pushforward ~(v/freeze mu:N->M)) - ~(v/freeze v-on-N))] + name `((~'pushforward ~(g/freeze mu:N->M)) + ~(g/freeze v-on-N))] (vf/procedure->vector-field op name)))) (defn literal-manifold-map @@ -95,8 +95,8 @@ (let [op (fn [f-on-M] (f/compose (v-on-M f-on-M) mu:N->M)) - name `((~'vector-field->vector-field-over-map ~(v/freeze mu:N->M)) - ~(v/freeze v-on-M))] + name `((~'vector-field->vector-field-over-map ~(g/freeze mu:N->M)) + ~(g/freeze v-on-M))] (vf/procedure->vector-field op name)))) ;; A form field can also be transported across a map. Given a form @@ -113,7 +113,7 @@ (fn [_] ((V-over-mu f) n))) `(~'make-fake-vector-field - ~(v/freeze V-over-mu)))) + ~(g/freeze V-over-mu)))) (op [& vectors-over-map] (assert (= (count vectors-over-map) (ff/get-rank w-on-M))) @@ -124,8 +124,8 @@ vectors-over-map)) (mu:N->M n))))] (let [rank (ff/get-rank w-on-M) - name `((~'form-field->form-field-over-map ~(v/freeze mu:N->M)) - ~(v/freeze w-on-M))] + name `((~'form-field->form-field-over-map ~(g/freeze mu:N->M)) + ~(g/freeze w-on-M))] (ff/procedure->nform-field op rank name))))) (defn basis->basis-over-map @@ -150,8 +150,8 @@ (apply ((form-field->form-field-over-map mu:N->M) omega-on-M) (map (differential mu:N->M) vectors-on-N))) - name `((~'pullback ~(v/freeze mu:N->M)) - ~(v/freeze omega-on-M))] + name `((~'pullback ~(g/freeze mu:N->M)) + ~(g/freeze omega-on-M))] (ff/procedure->nform-field op k name)))))) (defn pullback-vector-field diff --git a/src/emmy/calculus/metric.cljc b/src/emmy/calculus/metric.cljc index 0410923e..d0c68888 100644 --- a/src/emmy/calculus/metric.cljc +++ b/src/emmy/calculus/metric.cljc @@ -153,7 +153,7 @@ (fn [_] ((V-over-mu f) n))) `(~'make-fake-vector-field - ~(v/freeze V-over-mu)))) + ~(g/freeze V-over-mu)))) (the-metric [v1 v2] (fn [n] ((g-on-M @@ -177,8 +177,8 @@ (ff/procedure->oneform-field omega `(~'lower - ~(v/freeze u) - ~(v/freeze metric)))))) + ~(g/freeze u) + ~(g/freeze metric)))))) (def vector-field->oneform-field "Alias for [[lower]]." @@ -201,8 +201,8 @@ (vf/procedure->vector-field v `(~'raise - ~(v/freeze omega) - ~(v/freeze metric))))))) + ~(g/freeze omega) + ~(g/freeze metric))))))) (def oneform-field->vector-field "Alias for [[raise]]." diff --git a/src/emmy/calculus/vector_field.cljc b/src/emmy/calculus/vector_field.cljc index 2ff241a9..1c5a10d3 100644 --- a/src/emmy/calculus/vector_field.cljc +++ b/src/emmy/calculus/vector_field.cljc @@ -137,7 +137,7 @@ "Given some vector field `vf`, returns a vector field with the same context and its procedure replaced by `vf:zero`. - The returned vector field responds `true` to `v/zero?`." + The returned vector field responds `true` to `g/zero?`." [vf] {:pre [(vector-field? vf)]} (o/make-operator vf:zero @@ -256,8 +256,8 @@ (components point))))) name `(~'+ ~@(map (fn [component basis-element] `(~'* - ~(v/freeze component) - ~(v/freeze basis-element))) + ~(g/freeze component) + ~(g/freeze basis-element))) (flatten components) (flatten vector-basis)))] (procedure->vector-field op name))) diff --git a/src/emmy/collection.cljc b/src/emmy/collection.cljc index e94dd4f6..a59eff74 100644 --- a/src/emmy/collection.cljc +++ b/src/emmy/collection.cljc @@ -1,8 +1,8 @@ #_"SPDX-License-Identifier: GPL-3.0" ^#:nextjournal.clerk -{:toc true - :visibility :hide-ns} + {:toc true + :visibility :hide-ns} (ns emmy.collection "This namespace contains implementations of various Emmy protocols for native Clojure collections." @@ -26,27 +26,23 @@ ;; ## Vector Implementations ;; ;; Vectors are implicitly treated as [[emmy.structure/Structure]] instances -;; with an `up` orientation, and implement [[v/freeze]] identically. They can +;; with an `up` orientation, and implement [[g/freeze]] identically. They can ;; act as `zero?`, but they can't act as `one?` or `identity?`; those are ;; reserved for instances that have no effect on multiplication. (defmethod g/simplify [PersistentVector] [v] (mapv g/simplify v)) +(defmethod g/zero-like [PersistentVector] [v] + (mapv g/zero-like v)) +(defmethod g/exact? [PersistentVector] [v] (every? g/exact? v)) +(defmethod g/freeze [PersistentVector] [v] `(~'up ~@(map g/freeze v))) #?(:clj (defmethod g/simplify [clojure.lang.APersistentVector$SubVector] [v] (mapv g/simplify v))) (extend-type #?(:clj IPersistentVector :cljs PersistentVector) - v/Value - (zero? [v] (every? v/zero? v)) - (one? [_] false) - (identity? [_] false) - (zero-like [v] (mapv v/zero-like v)) - (one-like [_] 1) - (identity-like [_] 1) - (exact? [v] (every? v/exact? v)) - (freeze [v] `(~'up ~@(map v/freeze v))) + v/IKind (kind [v] (type v)) ;; Another difference from [[emmy.structure/Structure]] is that a @@ -68,31 +64,29 @@ ;; ## Sequences ;; ;; Sequences can't act as functions or respond to any of -;; the [[v/zero?]]-and-friends predicates. They pass along the operations that +;; the [[g/zero?]]-and-friends predicates. They pass along the operations that ;; they can implement to their elements via [[map]]. (defmethod g/simplify [v/seqtype] [a] (map g/simplify a)) -#_{:clj-kondo/ignore [:redundant-do]} -(#?@(:clj [do] - :cljs [doseq [klass [Cons IndexedSeq LazySeq List Range IntegerRange]]]) - (extend-type #?(:clj ISeq :cljs klass) - v/Value - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [xs] (map v/zero-like xs)) - (one-like [xs] (u/unsupported (str "one-like: " xs))) - (identity-like [xs] (u/unsupported (str "identity-like: " xs))) - (exact? [_] false) - (freeze [xs] (map v/freeze xs)) - (kind [xs] (type xs)) - - d/IPerturbed - (perturbed? [_] false) - (replace-tag [xs old new] (map #(d/replace-tag % old new) xs)) - (extract-tangent [xs tag] (map #(d/extract-tangent % tag) xs)))) +(doseq [klass #?(:clj [ISeq] + :cljs [Cons IndexedSeq LazySeq List Range IntegerRange])] + (defmethod g/zero? [klass] [_] false) + (defmethod g/one? [klass] [_] false) + (defmethod g/identity? [klass] [_] false) + (defmethod g/zero-like [klass] [xs] (map g/zero-like xs)) + (defmethod g/exact? [klass] [xs] (every? g/exact? xs)) + (defmethod g/freeze [klass] [xs] (map g/freeze xs)) + + (extend-type #?(:clj ISeq :cljs klass) + v/IKind + (kind [xs] (type xs)) + + d/IPerturbed + (perturbed? [_] false) + (replace-tag [xs old new] (map #(d/replace-tag % old new) xs)) + (extract-tangent [xs tag] (map #(d/extract-tangent % tag) xs)))) ;; ## Maps ;; @@ -128,9 +122,9 @@ (defn- combine [f m1 m2 l-default] (letfn [(merge-entry [m e] - (let [k (key e) + (let [k (key e) v (val e)] - (assoc m k (f (get m k l-default) v))))] + (assoc m k (f (get m k l-default) v))))] (reduce merge-entry m1 (seq m2)))) (defmethod g/make-rectangular [::map ::map] [m1 m2] @@ -166,20 +160,19 @@ m)) (doseq [klass [PersistentHashMap PersistentArrayMap PersistentTreeMap]] + (defmethod g/zero? [klass] [m] (every? g/zero? (vals m))) + (defmethod g/one? [klass] [_] false) + (defmethod g/identity? [klass] [_] false) + (defmethod g/zero-like [klass] [m] (u/map-vals g/zero-like m)) + (defmethod g/exact? [klass] [m] (every? g/exact? (vals m))) + (defmethod g/freeze [klass] [m] (u/map-vals g/freeze m)) + #?(:clj (extend klass - v/Value - {:zero? (fn [m] (every? v/zero? (vals m))) - :one? (fn [_] false) - :identity? (fn [_] false) - :zero-like (fn [m] (u/map-vals v/zero-like m)) - :one-like (fn [m] (u/unsupported (str "one-like: " m))) - :identity-like (fn [m] (u/unsupported (str "identity-like: " m))) - :exact? (fn [m] (every? v/exact? (vals m))) - :freeze (fn [m] (u/map-vals v/freeze m)) - :kind (fn [m] (if (sorted? m) - (type m) - (:type m (type m))))} + v/IKind + {:kind (fn [m] (if (sorted? m) + (type m) + (:type m (type m))))} f/IArity {:arity (fn [_] [:between 1 2])} @@ -197,15 +190,7 @@ :cljs (extend-type klass - v/Value - (zero? [m] (every? v/zero? (vals m))) - (one? [_] false) - (identity? [_] false) - (zero-like [m] (u/map-vals v/zero-like m)) - (one-like [m] (u/unsupported (str "one-like: " m))) - (identity-like [m] (u/unsupported (str "identity-like: " m))) - (exact? [m] (every? v/exact? (vals m))) - (freeze [m] (u/map-vals v/freeze m)) + v/IKind (kind [m] (if (sorted? m) (type m) (:type m (type m)))) @@ -235,33 +220,23 @@ (cs/union a b)) (doseq [klass [PersistentHashSet PersistentTreeSet]] + (defmethod g/zero? [klass] [s] (empty? s)) + (defmethod g/one? [klass] [_] false) + (defmethod g/identity? [klass] [_] false) + (defmethod g/zero-like [klass] [_] #{}) + (defmethod g/exact? [klass] [s] (every? g/exact? s)) + #?(:clj (extend klass - v/Value - {:zero? empty? - :one? (fn [_] false) - :identity? (fn [_] false) - :zero-like (fn [_] #{}) - :one-like (fn [s] (u/unsupported (str "one-like: " s))) - :identity-like (fn [s] (u/unsupported (str "identity-like: " s))) - :exact? (fn [_] false) - :freeze (fn [s] (u/unsupported (str "freeze: " s))) - :kind type} + v/IKind + {:kind type} f/IArity {:arity (fn [_] [:between 1 2])}) :cljs (extend-type klass - v/Value - (zero? [s] (empty? s)) - (one? [_] false) - (identity? [_] false) - (zero-like [_] #{}) - (one-like [s] (u/unsupported (str "one-like: " s))) - (identity-like [s] (u/unsupported (str "identity-like: " s))) - (exact? [_] false) - (freeze [s] (u/unsupported (str "freeze: " s))) + v/IKind (kind [s] (type s)) f/IArity diff --git a/src/emmy/complex.cljc b/src/emmy/complex.cljc index 7b34981f..c08df094 100644 --- a/src/emmy/complex.cljc +++ b/src/emmy/complex.cljc @@ -137,26 +137,7 @@ v/Numerical (numerical? [_] true) - v/Value - (zero? [c] - #?(:clj (and (zero? (real c)) - (zero? (imaginary c))) - :cljs (.isZero ^js c))) - - (one? [c] - (and (v/one? (real c)) - (zero? (imaginary c)))) - (identity? [c] (v/one? c)) - (zero-like [_] ZERO) - (one-like [_] ONE) - (identity-like [_] ONE) - (freeze [c] (let [re (real c) - im (imaginary c)] - (if (v/zero? im) - re - (list 'complex re im)))) - (exact? [c] (and (v/exact? (real c)) - (v/exact? (imaginary c)))) + v/IKind (kind [_] ::complex)) ;; ## Gaussian Integers @@ -183,10 +164,10 @@ details." [z] (if (complex? z) - (and (v/almost-integral? (real z)) - (v/almost-integral? (imaginary z))) + (and (g/almost-integral? (real z)) + (g/almost-integral? (imaginary z))) (and (v/real? z) - (v/almost-integral? z)))) + (g/almost-integral? z)))) ;; ## Complex GCD @@ -213,8 +194,8 @@ NOTE that the GCD of two complex numbers is determined up to a factor of ±1 and ±i." [l r] - (cond (v/zero? l) r - (v/zero? r) l + (cond (g/zero? l) r + (g/zero? r) l (v/= l r) (abs-real l) (not (or (gaussian-integer? l) (gaussian-integer? r))) @@ -234,24 +215,40 @@ [l r] [r l])] (loop [a (round l) b (round r)] - (if (v/zero? b) + (if (g/zero? b) (abs-real a) (recur b (g/sub a (g/mul (round (g/div a b)) b)))))))) ;; ## Generic Method Installation - +(defmethod g/zero? [::complex] [c] #?(:clj (and (zero? (real c)) + (zero? (imaginary c))) + :cljs (.isZero ^js c))) +(defmethod g/one? [::complex] [c] (and (g/one? (real c)) + (zero? (imaginary c)))) +(defmethod g/identity? [::complex] [c] (g/one? c)) +(defmethod g/zero-like [::complex] [_] ZERO) +(defmethod g/one-like [::complex] [_] ONE) +(defmethod g/identity-like [::complex] [_] ONE) +(defmethod g/freeze [::complex] [c] + (let [re (real c) + im (imaginary c)] + (if (g/zero? im) + re + (list 'complex re im)))) +(defmethod g/exact? [::complex] [c] + (and (g/exact? (real c)) (g/exact? (imaginary c)))) (defmethod g/gcd [::complex ::complex] [a b] (gcd a b)) (defmethod g/gcd [::complex ::v/real] [a b] (gcd a b)) (defmethod g/gcd [::v/real ::complex] [a b] (gcd a b)) (defmethod g/make-rectangular [::v/real ::v/real] [re im] - (if (v/zero? im) + (if (g/zero? im) re (complex re im))) (defmethod g/make-polar [::v/real ::v/real] [radius angle] - (cond (v/zero? radius) radius - (v/zero? angle) radius + (cond (g/zero? radius) radius + (g/zero? angle) radius :else #?(:cljs (Complex. #js {:abs (js/Number radius) :arg (js/Number angle)}) @@ -378,19 +375,19 @@ (defmethod g/integer-part [::complex] [a] (let [re (g/integer-part (real a)) im (g/integer-part (imaginary a))] - (if (v/zero? im) + (if (g/zero? im) re (complex re im)))) (defmethod g/fractional-part [::complex] [a] (let [re (g/fractional-part (real a)) im (g/fractional-part (imaginary a))] - (if (v/zero? im) + (if (g/zero? im) re (complex re im)))) (defmethod g/negative? [::complex] [a] - (and (v/zero? (imaginary a)) + (and (g/zero? (imaginary a)) (g/negative? (real a)))) (defmethod g/infinite? [::complex] [a] @@ -417,14 +414,14 @@ (defmethod g/floor [::complex] [^Complex a] (let [re (g/floor (.getReal a)) im (g/floor (.getImaginary a))] - (if (v/zero? im) + (if (g/zero? im) re (complex re im)))) (defmethod g/ceiling [::complex] [^Complex a] (let [re (g/ceiling (.getReal a)) im (g/ceiling (.getImaginary a))] - (if (v/zero? im) + (if (g/zero? im) re (complex re im)))) diff --git a/src/emmy/differential.cljc b/src/emmy/differential.cljc index 234ca744..472d906f 100644 --- a/src/emmy/differential.cljc +++ b/src/emmy/differential.cljc @@ -1,8 +1,8 @@ #_"SPDX-License-Identifier: GPL-3.0" ^#:nextjournal.clerk -{:toc true - :visibility :hide-ns} + {:toc true + :visibility :hide-ns} (ns emmy.differential "This namespace contains an implementation of [[Differential]], a generalized dual number type that forms the basis for the forward-mode automatic @@ -13,6 +13,7 @@ (:refer-clojure :exclude [compare]) (:require [clojure.core :as core] [clojure.string :refer [join]] + [emmy.function] ;; for the side effect of making kind: MultiFn -> ::v/function [emmy.generic :as g] [emmy.util :as u] [emmy.util.aggregate :as ua] @@ -358,7 +359,7 @@ #?(:clj Object :cljs default) (perturbed? [_] false) (replace-tag [this _ _] this) - (extract-tangent [this _] (v/zero-like this))) + (extract-tangent [this _] (g/zero-like this))) ;; ## Differential Implementation ;; @@ -471,7 +472,7 @@ Each input must be sequence of `[tag-set, coefficient]` pairs, sorted by `tag-set`." - (ua/merge-fn core/compare g/add v/zero? make-term)) + (ua/merge-fn core/compare g/add g/zero? make-term)) ;; Because we've decided to store terms as a vector, we can multiply two vectors ;; of terms by: @@ -494,19 +495,19 @@ [tags->coefs] (let [terms (for [[tags tags-coefs] (group-by tags tags->coefs) :let [c (transduce (map coefficient) g/+ tags-coefs)] - :when (not (v/zero? c))] + :when (not (g/zero? c))] [tags c])] (into [] (sort-by tags terms)))) (defn- terms:map-coefficients "Given some function `f` and a sequence of `terms`, returns a vector of terms - with all each `c` mapped to `(f c)`. Any term where `(v/zero? (f c))` is true + with all each `c` mapped to `(f c)`. Any term where `(g/zero? (f c))` is true will be filtered out." [f terms] (let [xform (mapcat (fn [term] (let [c' (f (coefficient term))] - (if (v/zero? c') + (if (g/zero? c') [] [(make-term (tags term) c')]))))] (into [] xform terms))) @@ -523,13 +524,13 @@ (let [t (nth terms i nil)] (if (nil? t) acc - (let [[tags1 coeff1] t] - (if (empty? (uv/intersection tags tags1)) - (recur (conj acc (make-term - (uv/union tags tags1) - (g/* coeff coeff1))) - (inc i)) - (recur acc (inc i)))))))) + (let [[tags1 coeff1] t] + (if (empty? (uv/intersection tags tags1)) + (recur (conj acc (make-term + (uv/union tags tags1) + (g/* coeff coeff1))) + (inc i)) + (recur acc (inc i)))))))) (defn terms:* "Returns a vector of non-zero [[Differential]] terms that represent the product @@ -543,7 +544,7 @@ (if (nil? x) [] (terms:+ (t*ts x ylist) - (call (inc i))))))] + (call (inc i))))))] (call 0))) ;; ## Differential Type Implementation @@ -614,21 +615,7 @@ []))) terms))) - v/Value - (zero? [_] - (every? (comp v/zero? coefficient) terms)) - (one? [this] (one? this)) - (identity? [this] (one? this)) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (freeze [_] - (letfn [(freeze-term [term] - (make-term (tags term) - (v/freeze (coefficient term))))] - `[~'Differential - ~@(mapv freeze-term terms)])) - (exact? [_] false) + v/IKind (kind [_] ::differential) Object @@ -695,14 +682,14 @@ (defn- ->terms "Returns a vector of terms that represent the supplied [[Differential]]; any - term with a [[v/zero?]] coefficient will be filtered out before return. + term with a [[g/zero?]] coefficient will be filtered out before return. If you pass a non-[[Differential]], [[->terms]] will return a singleton term list (or `[]` if the argument was zero)." [dx] (cond (differential? dx) (bare-terms dx) (vector? dx) dx - (v/zero? dx) [] + (g/zero? dx) [] :else [(make-term dx)])) (defn- terms->differential @@ -743,7 +730,7 @@ new [[Differential]] generated by transforming all coefficients `c` of `d` to `(f c)`. - Any term in the returned instance with a `v/zero?` coefficient + Any term in the returned instance with a `g/zero?` coefficient will be filtered out." [f d] (let [terms (bare-terms d)] @@ -987,14 +974,14 @@ ;; whenever a non-[[Differential]] `x` would return true. To make this work, ;; these operations look only at the [[finite-part]]. ;; -;; HOWEVER! [[v/one?]] and [[v/zero?]] are examples of Emmy functions that +;; HOWEVER! [[g/one?]] and [[g/zero?]] are examples of Emmy functions that ;; are used to skip operations that we _want_ to happen, like multiplication. ;; -;; `(g/* x y)` will return `y` if `(v/one? x)` is true... but to propagate the +;; `(g/* x y)` will return `y` if `(g/one? x)` is true... but to propagate the ;; derivative through we need this multiplication to occur. The compromise is: ;; -;; - [[v/one?]] and [[v/zero?]] return true only when ALL [[tangent-part]]s are -;; zero and the [[finite-part]] is either [[v/one?]] or [[v/zero?]] +;; - [[g/one?]] and [[g/zero?]] return true only when ALL [[tangent-part]]s are +;; zero and the [[finite-part]] is either [[g/one?]] or [[g/zero?]] ;; respectively ;; - [[eq]] and [[compare-full]] similarly looks at every component in ;; the [[Differential]] supplied to both sides @@ -1003,7 +990,7 @@ ;; ;; - [[equiv]] and [[compare]] only examine the [[finite-part]] of either side. -(defn one? +(defn- one? "Returns true if the supplied instance has a [[finite-part]] that responds true to [[emmy.value/one?]], and zero coefficients on any of its tangent components; false otherwise. @@ -1014,8 +1001,8 @@ the [[finite-part]] and ignore the values of the tangent parts." [dx] (let [[p t] (primal-tangent-pair dx)] - (and (v/one? p) - (v/zero? t)))) + (and (g/one? p) + (g/zero? t)))) (defn eq "For non-differentials, this is identical to [[clojure.core/=]]. @@ -1121,7 +1108,7 @@ (f x) (let [[px tx] (primal-tangent-pair x) fx (call px)] - (if (v/numeric-zero? tx) + (if (g/numeric-zero? tx) fx (d:+* fx (df:dx px) tx))))))) @@ -1155,10 +1142,10 @@ [xe dx] (primal-tangent-pair x tag) [ye dy] (primal-tangent-pair y tag) a (call xe ye) - b (if (v/numeric-zero? dx) + b (if (g/numeric-zero? dx) a (d:+* a (df:dx xe ye) dx))] - (if (v/numeric-zero? dy) + (if (g/numeric-zero? dy) b (d:+* b (df:dy xe ye) dy))))))) @@ -1196,9 +1183,6 @@ ;; Any function built out of these components will work with ;; the [[emmy.calculus.derivative/D]] operator. -(defmethod g/simplify [::differential] [d] - (map-coefficients g/simplify d)) - (defn- defunary "Given: @@ -1265,8 +1249,8 @@ (func x)))) (defn- discont-at-integers [f dfdx] - (let [f (lift-1 f (fn [_] dfdx)) - name (v/freeze f)] + (let [name (g/freeze f) + f (lift-1 f (fn [_] dfdx))] (fn [x] (if (v/integral? (finite-term x)) (u/illegal @@ -1326,3 +1310,19 @@ (defunary g/sinhc (lift-1 g/sinhc)) (defunary g/tanc (lift-1 g/tanc)) (defunary g/tanhc (lift-1 g/tanhc)) + +;; Non-differentiable generic operations + +(defmethod g/simplify [::differential] [d] (map-coefficients g/simplify d)) +(defmethod g/zero? [::differential] [d] (every? (comp g/zero? coefficient) (bare-terms d))) +(defmethod g/one? [::differential] [d] (one? d)) +(defmethod g/identity? [::differential] [d] (one? d)) +(defmethod g/zero-like [::differential] [_] 0) +(defmethod g/one-like [::differential] [_] 1) +(defmethod g/identity-like [::differential] [_] 1) +(defmethod g/freeze [::differential] [d] + (letfn [(freeze-term [term] + (make-term (tags term) + (g/freeze (coefficient term))))] + `[~'Differential + ~@(mapv freeze-term (bare-terms d))])) diff --git a/src/emmy/env.cljc b/src/emmy/env.cljc index ad980c78..6d803e30 100644 --- a/src/emmy/env.cljc +++ b/src/emmy/env.cljc @@ -329,7 +329,10 @@ dot-product inner-product outer-product cross-product partial-derivative Lie-derivative solve-linear solve-linear-left solve-linear-right - simplify] + simplify + zero? one? identity? + zero-like one-like identity-like + exact? freeze] [emmy.structure compatible-shape compatible-zero dual-zero @@ -581,6 +584,5 @@ [emmy.util.stream vector:generate] [emmy.special.elliptic elliptic-f] [emmy.special.factorial factorial] - [emmy.value = compare exact? zero? one? identity? - zero-like one-like identity-like - numerical? freeze kind kind-predicate]) + [emmy.value = compare + numerical? kind kind-predicate]) diff --git a/src/emmy/euclid.cljc b/src/emmy/euclid.cljc index d06fdc10..bb2ab125 100644 --- a/src/emmy/euclid.cljc +++ b/src/emmy/euclid.cljc @@ -15,10 +15,10 @@ For more info, see the Wikipedia article on the [Extended Euclidean algorithm](http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm)." [a b] - (cond (v/zero? a) [(g/abs b) 0 1] - (v/zero? b) [(g/abs a) 1 0] + (cond (g/zero? a) [(g/abs b) 0 1] + (g/zero? b) [(g/abs a) 1 0] :else (loop [s 0 s0 1 t 1 t0 0 r (g/abs b) r0 (g/abs a)] - (if (v/zero? r) + (if (g/zero? r) [r0 s0 t0] (let [q (g/quotient r0 r)] (recur (g/- s0 (g/* q s)) s @@ -30,15 +30,15 @@ divisor](https://en.wikipedia.org/wiki/Greatest_common_divisor) of the two inputs `a` and `b`." [a b] - (cond (v/zero? a) (g/abs b) - (v/zero? b) (g/abs a) + (cond (g/zero? a) (g/abs b) + (g/zero? b) (g/abs a) (or (v/= a b) (v/= a (g/negate b))) (g/abs a) (not (and (v/integral? a) (v/integral? b))) 1 :else (loop [a (g/abs a) b (g/abs b)] - (if (v/zero? b) + (if (g/zero? b) a (recur b (g/remainder a b)))))) diff --git a/src/emmy/expression.cljc b/src/emmy/expression.cljc index f0fbc442..a8de6f16 100644 --- a/src/emmy/expression.cljc +++ b/src/emmy/expression.cljc @@ -35,26 +35,7 @@ v/Numerical (numerical? [_] (= type ::numeric)) - v/Value - (zero? [_] - (and (v/number? expression) - (v/zero? expression))) - - (one? [_] - (and (v/number? expression) - (v/one? expression))) - - (identity? [_] - (and (v/number? expression) - (v/one? expression))) - - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (exact? [_] - (and (v/number? expression) - (v/exact? expression))) - (freeze [_] (v/freeze expression)) + v/IKind (kind [_] type) Object @@ -306,10 +287,27 @@ expression `expr`." [expr] (pr-str - (v/freeze (g/simplify expr)))) + (g/freeze (g/simplify expr)))) (defn print-expression [expr] (pp/pprint - (v/freeze (g/simplify expr)))) + (g/freeze (g/simplify expr)))) (def pe print-expression) + +(defmethod g/zero? [::numeric] [^Literal a] + (let [x (.-expression a)] + (and (v/number? x) (g/zero? x)))) + +(defmethod g/one? [::numeric] [^Literal a] + (let [x (.-expression a)] + (and (v/number? x) (g/one? x)))) + +(defmethod g/identity? [::numeric] [^Literal a] (g/one? a)) +(defmethod g/zero-like [::numeric] [_] 0) +(defmethod g/one-like [::numeric] [_] 1) +(defmethod g/identity-like [::numeric] [_] 1) +(defmethod g/exact? [::numeric] [^Literal a] + (let [x (.-expression a)] + (and (v/number? x) (g/exact? x)))) +(defmethod g/freeze [::numeric] [^Literal a] (g/freeze (.-expression a))) diff --git a/src/emmy/expression/analyze.cljc b/src/emmy/expression/analyze.cljc index b729d6e6..2336ca9a 100644 --- a/src/emmy/expression/analyze.cljc +++ b/src/emmy/expression/analyze.cljc @@ -19,6 +19,7 @@ an expression with respect to an analyzer is therefore effected by a round-trip to and from the canonical form." (:require [emmy.expression :as x] + [emmy.generic :as g] [emmy.numsymb :as sym] [emmy.util :as u] [emmy.value :as v])) @@ -231,7 +232,7 @@ ;; ;; NOTE: Make sure to use the FROZEN version of the expression ;; as the key! - (let [expr-k (v/freeze expr)] + (let [expr-k (g/freeze expr)] (#?(:clj dosync :cljs identity) (if-let [existing-expr (@expr->var expr-k)] existing-expr diff --git a/src/emmy/expression/compile.cljc b/src/emmy/expression/compile.cljc index 4df2230c..2eed4f5e 100644 --- a/src/emmy/expression/compile.cljc +++ b/src/emmy/expression/compile.cljc @@ -621,7 +621,7 @@ code (-> (if simplify? (g/simplify h) h) - (v/freeze) + (g/freeze) (wrap :calling-convention calling-convention :params (when generic-params? params) :state-model generic-state) diff --git a/src/emmy/expression/render.cljc b/src/emmy/expression/render.cljc index 28de77ec..7d191b89 100644 --- a/src/emmy/expression/render.cljc +++ b/src/emmy/expression/render.cljc @@ -216,7 +216,7 @@ (or (and render-primitive (render-primitive n)) n))))] (fn [expr] - (let [result (-> (v/freeze expr) + (let [result (-> (g/freeze expr) (z/seq-zip) (render-loc))] (if (string? result) diff --git a/src/emmy/function.cljc b/src/emmy/function.cljc index c62f962b..650fa8c7 100644 --- a/src/emmy/function.cljc +++ b/src/emmy/function.cljc @@ -141,12 +141,12 @@ (defn- zero-like [f] (-> (fn [& args] - (v/zero-like (apply f args))) + (g/zero-like (apply f args))) (with-arity (arity f) {:from :zero-like}))) (defn- one-like [f] (-> (fn [& args] - (v/one-like (apply f args))) + (g/one-like (apply f args))) (with-arity (arity f) {:from :one-like}))) (def I @@ -186,58 +186,18 @@ (apply f (map g/* xs factors))) (with-arity (arity f))))) -(extend-protocol v/Value +(extend-protocol v/IKind MultiFn - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [f] (zero-like f)) - (one-like [f] (one-like f)) - (identity-like [f] (identity-like f)) - (exact? [f] (compose v/exact? f)) - (freeze [f] - (if-let [m (get-method f [Keyword])] - (m :name) - (core/get @v/object-name-map f f))) (kind [_] ::v/function) #?(:clj AFunction :cljs function) - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [f] (zero-like f)) - (one-like [f] (one-like f)) - (identity-like [f] (identity-like f)) - (exact? [f] (compose v/exact? f)) - (freeze [f] (core/get - @v/object-name-map - f #?(:clj (:name (meta f) f) - :cljs f))) (kind [_] ::v/function) Var - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [f] (zero-like f)) - (one-like [f] (one-like f)) - (identity-like [f] (identity-like f)) - (exact? [f] (compose v/exact? f)) - (freeze [f] (core/get @v/object-name-map @f f)) (kind [_] ::v/function) - #?@(:cljs - [MetaFn - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [f] (zero-like f)) - (one-like [f] (one-like f)) - (identity-like [f] (identity-like f)) - (exact? [f] (compose v/exact? f)) - (freeze [f] (core/get - @v/object-name-map f (:name (.-meta f) f))) - (kind [_] ::v/function)])) + #?@(:cljs [MetaFn + (kind [_] ::v/function)])) ;; we record arities as a vector with an initial keyword: ;; [:exactly m] @@ -583,3 +543,32 @@ (defunary g/magnitude) (defunary g/angle) (defunary g/conjugate) + +;; Generic Methods + +(defmethod g/zero? [::v/function] [_] false) +(defmethod g/one? [::v/function] [_] false) +(defmethod g/identity? [::v/function] [_] false) +(defmethod g/zero-like [::v/function] [f] (zero-like f)) +(defmethod g/one-like [::v/function] [f] (one-like f)) +(defmethod g/identity-like [::v/function] [f] (identity-like f)) +(defmethod g/exact? [::v/function] [f] (compose g/exact? f)) + +(defmethod g/freeze [::v/function] [f] + (core/get @v/object-name-map f + (cond + (instance? MultiFn f) + (if-let [m (get-method f [Keyword])] + (m :name) + f) + + #?@(:clj [(instance? AFunction f) + (:name (meta f) f)] + + :cljs [(instance? MetaFn f) + (:name (.-meta f) f)]) + + (var? f) + (g/freeze @f) + + :else f))) diff --git a/src/emmy/generic.cljc b/src/emmy/generic.cljc index b0803be2..87980095 100644 --- a/src/emmy/generic.cljc +++ b/src/emmy/generic.cljc @@ -11,7 +11,7 @@ cljdocs](https://cljdoc.org/d/org.mentat/emmy/CURRENT/doc/basics/generics) for a detailed discussion of how to use and extend the generic operations defined in [[emmy.generic]] and [[emmy.value]]." - (:refer-clojure :exclude [/ + - * divide infinite? abs]) + (:refer-clojure :exclude [/ + - * divide infinite? abs zero?]) (:require [emmy.util :as u] [emmy.util.def :refer [defgeneric]] [emmy.value :as v]) @@ -20,8 +20,8 @@ ;; ## Generic Numerics ;; ;; The first section introduces generic versions of -;; Clojure's [[+]], [[-]], [[*]] and [[/]] operations. Any type that can -;; implement all four of these operations forms a +;; Clojure's [[zero?]], [[+]], [[-]], [[*]] and [[/]] operations. Any type that can +;; implement all of of these operations forms a ;; mathematical [Field](https://en.wikipedia.org/wiki/Field_(mathematics)). ;; ;; There are, of course, other technical names for types that can only implement @@ -49,6 +49,41 @@ ;; each generic operation to some new type is sparse. Have a look ;; at [[emmy.complex]] for an example of how to do this. +(defgeneric zero? 1 + "Is true if `x` is an additive identity.") +(defgeneric one? 1 + "Is true if `x` is a multiplicative identity.") +(defgeneric identity? 1 + "Like `one?`, but this is true of square identity matrices as well. + No matrix is considered `one?` because its function as a multiplicative + identity depends on the shape of the other multiplicand.") +(defgeneric zero-like 1 + "In general, this procedure returns the additive identity of the type of its + argument, if it exists. For numbers this is 0.") +(defgeneric one-like 1 + "In general, this procedure returns the multiplicative identity of the type of + its argument, if it exists. For numbers this is 1.") +(defgeneric identity-like 1 + "Like `one-like` but works for square matrices.") + +(defgeneric exact? 1 + "Entries that are exact are available for `gcd`, among other operations.") + +(defgeneric freeze 1 + "Freezing an expression means removing wrappers and other metadata from + subexpressions, so that the result is basically a pure S-expression with the + same structure as the input. Doing this will rob an expression of useful + information for further computation; so this is intended to be done just + before simplification and printing, to simplify those processes.") +(defmethod freeze [#?(:clj String :cljs js/String)] [s] s) +(defmethod freeze [nil] [_] nil) + +(defn numeric-zero? + "Returns `true` if `x` is both a [[number?]] and [[zero?]], false otherwise." + [x] + (and (v/number? x) + (zero? x))) + (defgeneric ^:no-doc add 2 "Returns the sum of arguments `a` and `b`. @@ -74,8 +109,8 @@ ([] 0) ([x] x) ([x y] - (cond (v/numeric-zero? x) y - (v/numeric-zero? y) x + (cond (numeric-zero? x) y + (numeric-zero? y) x :else (add x y))) ([x y & more] (reduce + (+ x y) more))) @@ -83,7 +118,7 @@ (defgeneric negate 1 "Returns the negation of `a`. - Equivalent to `(- (v/zero-like a) a)`." + Equivalent to `(- (g/zero-like a) a)`." {:name '- :dfdx (fn [_] -1)}) @@ -123,8 +158,8 @@ ([] 0) ([x] (negate x)) ([x y] - (cond (v/numeric-zero? y) x - (v/numeric-zero? x) (negate y) + (cond (numeric-zero? y) x + (numeric-zero? x) (negate y) :else (sub x y))) ([x y & more] (- x (apply + y more)))) @@ -144,7 +179,7 @@ ;;; |a b c| |0| |0| |0| ;;; |d e f| |0| = |0|, not |0| ;;; -;;; We are less worried about the v/zero? below, +;;; We are less worried about the zero? below, ;;; because any invertible matrix is square. (defn * @@ -166,10 +201,10 @@ ([x y] (let [numx? (v/numerical? x) numy? (v/numerical? y)] - (cond (and numx? (v/zero? x)) (v/zero-like y) - (and numy? (v/zero? y)) (v/zero-like x) - (and numx? (v/one? x)) y - (and numy? (v/one? y)) x + (cond (and numx? (zero? x)) (zero-like y) + (and numy? (zero? y)) (zero-like x) + (and numx? (one? x)) y + (and numy? (one? y)) x :else (mul x y)))) ([x y & more] (reduce * (* x y) more))) @@ -230,7 +265,7 @@ ([] 1) ([x] (invert x)) ([x y] - (if (and (v/number? y) (v/one? y)) + (if (and (v/number? y) (one? y)) x (div x y))) ([x y & more] @@ -241,7 +276,7 @@ /) (defgeneric exact-divide 2 - "Similar to the binary case of [[/]], but throws if `(v/exact? )` + "Similar to the binary case of [[/]], but throws if `(g/exact? )` returns false.") ;; ### Exponentiation, Log, Roots @@ -288,7 +323,7 @@ {:dfdx (fn [x y] (mul y (expt x (sub y 1)))) :dfdy (fn [x y] - (if (and (v/number? x) (v/zero? x)) + (if (and (v/number? x) (zero? x)) (if (v/number? y) (if (not (negative? y)) 0 @@ -311,7 +346,7 @@ (if-let [mul' (get-method mul [kind kind])] (letfn [(expt' [base pow] (loop [n pow - y (v/one-like base) + y (one-like base) z base] (let [t (even? n) n (quot n 2)] @@ -320,10 +355,38 @@ (zero? n) (mul' z y) :else (recur n (mul' z y) (mul' z z))))))] (cond (pos? e) (expt' s e) - (zero? e) (v/one-like e) + (zero? e) (one-like s) :else (invert (expt' s (negate e))))) (u/illegal (str "No g/mul implementation registered for kind " kind))))) +(def ^:no-doc relative-integer-tolerance (clojure.core/* 100 u/machine-epsilon)) +(def ^:no-doc absolute-integer-tolerance 1e-20) + +(defn almost-integral? + "Returns true if `x` is either: + + - [[integral?]], + - a floating point number either < [[absolute-integer-tolerance]] (if near + zero) or within [[relative-integer-tolerance]] of the closest integer, + + false otherwise." + [x] + (or (v/integral? x) + (and (float? x) + (let [x (double x) + z (Math/round x)] + (if (zero? z) + (< (Math/abs x) absolute-integer-tolerance) + (< (Math/abs (/ (- x z) z)) relative-integer-tolerance)))))) + +(defn exact-zero? + "Returns true if the supplied argument is an exact numerical zero, false + otherwise." + [n] + (and (v/number? n) + (exact? n) + (zero? n))) + ;; [[expt]] can be defined (as a default) in terms of repeated multiplication, ;; if the exponent is a (native) integer. The native requirement is simply due ;; to the [[default-expt]] implementation above, which uses functions like @@ -357,12 +420,12 @@ ;; ## More Generics (defgeneric negative? 1 - "Returns true if the argument `a` is less than `(v/zero-like a)`, + "Returns true if the argument `a` is less than `(g/zero-like a)`, false otherwise. The default implementation depends on a proper Comparable implementation on the type.`") (defmethod negative? :default [a] - (< a (v/zero-like a))) + (< (compare a (zero-like a)) 0)) (defgeneric infinite? 1 "Returns true if `a` is either numerically infinite (i.e., equal to `##Inf`) or @@ -485,7 +548,7 @@ (defmethod lcm :default [a b] (let [g (gcd a b)] - (if (v/zero? g) + (if (zero? g) g (abs (* (exact-divide a g) b))))) @@ -853,14 +916,14 @@ defaults to `ln((1 + sqrt(1+x^2)) / x)`." - [Boost notes on [[sinc]] and [[sinch]]](https://www.boost.org/doc/libs/1_65_0/libs/math/doc/html/math_toolkit/sinc/sinc_overview.html)" {:dfdx (fn [x] - (if (v/zero? x) + (if (zero? x) x (sub (div (cos x) x) (div (sin x) (square x)))))}) (defmethod sinc :default [x] - (if (v/zero? x) - (v/one-like x) + (if (zero? x) + (one-like x) (div (sin x) x))) ;; > NOTE that we don't define `cosc`. [This StackExchange @@ -889,15 +952,15 @@ defaults to `ln((1 + sqrt(1+x^2)) / x)`." - [Wikipedia page](https://en.wikipedia.org/wiki/Tanc_function) - [Mathworld page on Sinc](https://mathworld.wolfram.com/TancFunction.html)" {:dfdx (fn [x] - (if (v/zero? x) + (if (zero? x) x (let [sx (sec x)] (sub (div (* sx sx) x) (div (tan x) (square x))))))}) (defmethod tanc :default [x] - (if (v/zero? x) - (v/one-like x) + (if (zero? x) + (one-like x) (div (tan x) x))) ;; ### Hyperbolic Variants @@ -911,14 +974,14 @@ defaults to `ln((1 + sqrt(1+x^2)) / x)`." - [Wikipedia page](https://en.wikipedia.org/wiki/Sinhc_function) - [Mathworld page on Sinhc](https://mathworld.wolfram.com/SinhcFunction.html)" {:dfdx (fn [x] - (if (v/zero? x) + (if (zero? x) x (sub (div (cosh x) x) (div (sinh x) (square x)))))}) (defmethod sinhc :default [x] - (if (v/zero? x) - (v/one-like x) + (if (zero? x) + (one-like x) (div (sinh x) x))) (defgeneric tanhc 1 @@ -930,15 +993,15 @@ defaults to `ln((1 + sqrt(1+x^2)) / x)`." - [Wikipedia page](https://en.wikipedia.org/wiki/Tanhc_function) - [Mathworld page on Tanhc](https://mathworld.wolfram.com/TanhcFunction.html)" {:dfdx (fn [x] - (if (v/zero? x) + (if (zero? x) x (let [sx (sech x)] (sub (div (* sx sx) x) (div (tanh x) (square x))))))}) (defmethod tanhc :default [x] - (if (v/zero? x) - (v/one-like x) + (if (zero? x) + (one-like x) (div (tanh x) x))) ;; ## Complex Operators @@ -1023,7 +1086,7 @@ defaults to `ln((1 + sqrt(1+x^2)) / x)`." ;; This call registers a symbol for any non-multimethod we care about. These ;; will be returned instead of the actual function body when the user -;; calls `(v/freeze fn)`, for example. +;; calls `(g/freeze fn)`, for example. (v/add-object-symbols! {+ '+ diff --git a/src/emmy/matrix.cljc b/src/emmy/matrix.cljc index fd10ffd2..89e6a111 100644 --- a/src/emmy/matrix.cljc +++ b/src/emmy/matrix.cljc @@ -28,18 +28,7 @@ (derive ::matrix ::f/cofunction) (deftype Matrix [r c v] - v/Value - (zero? [_] (every? #(every? v/zero? %) v)) - (one? [_] false) - (identity? [m] (identity? m)) - (zero-like [this] (fmap v/zero-like this)) - (one-like [this] (identity-like this)) - (identity-like [this] (identity-like this)) - - (freeze [_] (if (= c 1) - `(~'column-matrix ~@(map (comp v/freeze first) v)) - `(~'matrix-by-rows ~@(map #(mapv v/freeze %) v)))) - (exact? [_] (every? #(every? v/exact? %) v)) + v/IKind (kind [_] (cond (= r c) ::square-matrix (= r 1) ::row-matrix (= c 1) ::column-matrix @@ -68,7 +57,7 @@ (seq [_] (seq v)) (valAt [_ key] (get v key)) (valAt [_ key default] (get v key default)) - (empty [this] (fmap v/zero-like this)) + (empty [this] (fmap g/zero-like this)) (equiv [this that] (m:= this that)) IFn @@ -129,7 +118,7 @@ "\"]")) IEmptyableCollection - (-empty [this] (v/zero-like this)) + (-empty [this] (g/zero-like this)) ISequential @@ -936,7 +925,7 @@ "Returns the determinant of the supplied square matrix `m`. Generic operations are used, so this works on symbolic square matrices." - (general-determinant g/+ g/- g/* v/numeric-zero?)) + (general-determinant g/+ g/- g/* g/numeric-zero?)) (defn cofactors "Returns the matrix of cofactors of the supplied square matrix `m`." @@ -972,15 +961,15 @@ (->Matrix 1 1 [[(div (core/get-in A [0 0]))]]) (let* [d (det A) -d (sub d)] - (generate dim dim - (fn [i j] - (let [denom (if (even? (+ i j)) d -d)] - (div (det (without A j i)) denom)))))))))) + (generate dim dim + (fn [i j] + (let [denom (if (even? (+ i j)) d -d)] + (div (det (without A j i)) denom)))))))))) (def ^{:arglists '([A])} invert "Returns the inverse of the supplied square matrix `m`." - (classical-adjoint-formula g/+ g/- g/* g// v/numeric-zero?)) + (classical-adjoint-formula g/+ g/- g/* g// g/numeric-zero?)) (defn- m-div-m [m1 m2] (mul m1 (invert m2))) @@ -1038,8 +1027,8 @@ (u/illegal "identity-like on non-square") (fmap-indexed (fn [elem i j] (if (= i j) - (v/one-like elem) - (v/zero-like elem))) + (g/one-like elem) + (g/zero-like elem))) M))) (defn identity? @@ -1053,8 +1042,8 @@ j (range n) :let [entry (core/get-in m [i j])]] (if (= i j) - (v/one? entry) - (v/zero? entry))))))) + (g/one? entry) + (g/zero? entry))))))) (defn make-diagonal "Given a single (sequential) argument `v`, returns the diagonal matrix of @@ -1083,13 +1072,13 @@ j (range n) :when (not= i j) :let [entry (core/get-in m [i j])]] - (v/zero? entry)))))) + (g/zero? entry)))))) (defn symmetric? "Returns true if the supplied matrix `M` is equal to its own transpose (i.e., symmetric), false otherwise." [M] - (v/zero? + (g/zero? (g/simplify (g/sub (transpose M) M)))) @@ -1097,7 +1086,7 @@ "Returns true if the supplied matrix `M` is equal to the negation of its own transpose (i.e., antisymmetric), false otherwise." [M] - (v/zero? + (g/zero? (g/simplify (g/add (transpose M) M)))) @@ -1163,7 +1152,7 @@ Returns the column matrix `x`. Unlike LU decomposition, Cramer's rule generalizes to symbolic solutions." - (cramers-rule g/+ g/- g/* g// v/numeric-zero?)) + (cramers-rule g/+ g/- g/* g// g/numeric-zero?)) (defn rsolve "Generalization of [[solve]] that can handle `up` and `down` structures, as well @@ -1188,6 +1177,19 @@ ;; ## Generic Operation Installation + +(defmethod g/zero? [::matrix] [a] (every? #(every? g/zero? %) a)) +(defmethod g/one? [::matrix] [_] false) +(defmethod g/identity? [::matrix] [m] (identity? m)) +(defmethod g/zero-like [::matrix] [m] (fmap g/zero-like m)) +(defmethod g/one-like [::matrix] [m] (identity-like m)) +(defmethod g/identity-like [::matrix] [m] (identity-like m)) +(defmethod g/freeze [::matrix] [^Matrix m] + (if (= (.-c m) 1) + `(~'column-matrix ~@(map (comp g/freeze first) (.-v m))) + `(~'matrix-by-rows ~@(map #(mapv g/freeze %) (.-v m))))) +(defmethod g/exact? [::matrix] [^Matrix m] (every? #(every? g/exact? %) (.-v m))) + (defmethod v/= [::matrix ::matrix] [a b] (m:= a b)) (defmethod v/= [::square-matrix ::v/scalar] [m c] (matrix=scalar m c)) (defmethod v/= [::v/scalar ::square-matrix] [c m] (scalar=matrix c m)) diff --git a/src/emmy/mechanics/hamilton.cljc b/src/emmy/mechanics/hamilton.cljc index 09381d55..b3a3058b 100644 --- a/src/emmy/mechanics/hamilton.cljc +++ b/src/emmy/mechanics/hamilton.cljc @@ -221,12 +221,13 @@ M (Dw-of-v z) b (w-of-v z)] (if (and *validate-Legendre-transform?* - (v/zero? + (g/zero? (g/simplify (g/determinant M)))) - (throw - (ex-info "Legendre Transform Failure: determinant = 0" - {:F F :w w})) + (do (println "determinant" (g/determinant M)) + (throw + (ex-info "Legendre Transform Failure: determinant = 0" + {:F F :w w}))) (let [v (g/solve-linear-left M (- w b))] (- (* w v) (F v))))))] (let [Dpg (D putative-G)] @@ -413,8 +414,8 @@ (defn T-func [s] (up 1 - (v/zero-like (l/coordinates s)) - (v/zero-like (momenta s)))) + (g/zero-like (l/coordinates s)) + (g/zero-like (momenta s)))) (defn canonical-H? [C H] (- (f/compose (D-phase-space H) C) diff --git a/src/emmy/mechanics/rotation.cljc b/src/emmy/mechanics/rotation.cljc index d5ec5e14..f07007ec 100644 --- a/src/emmy/mechanics/rotation.cljc +++ b/src/emmy/mechanics/rotation.cljc @@ -5,6 +5,7 @@ (:require [emmy.generic :as g :refer [cos sin + - * /]] [emmy.matrix :as matrix] [emmy.structure :as s :refer [up]] + [emmy.util :as u] [emmy.util.stream :as us] [emmy.value :as v])) @@ -161,8 +162,8 @@ (M->Euler M nil)) ([M tolerance-in-ulps] (let [tolerance (if (nil? tolerance-in-ulps) - v/machine-epsilon - (* tolerance-in-ulps v/machine-epsilon)) + u/machine-epsilon + (* tolerance-in-ulps u/machine-epsilon)) close? (us/close-enuf? tolerance) cx (get-in M [2 2]) cx-number? (v/number? cx)] diff --git a/src/emmy/modint.cljc b/src/emmy/modint.cljc index 09f3edb0..d5b26ba5 100644 --- a/src/emmy/modint.cljc +++ b/src/emmy/modint.cljc @@ -16,15 +16,7 @@ (declare mod:=) (deftype ModInt [i m] - v/Value - (zero? [_] (v/zero? i)) - (one? [_] (v/one? i)) - (identity? [_] (v/one? i)) - (zero-like [_] (ModInt. (v/zero-like i) m)) - (one-like [_] (ModInt. (v/one-like i) m)) - (identity-like [_] (ModInt. (v/one-like i) m)) - (freeze [_] (list 'modint i m)) - (exact? [_] true) + v/IKind (kind [_] ::modint) #?@(:clj @@ -140,7 +132,7 @@ [& modints] (let [prod (transduce (map modulus) g/* modints) xform (map (fn [mi] - (let [i (residue mi) + (let [i (residue mi) m (modulus mi) c (g/quotient prod m)] (g/* i c (residue (invert c m))))))] @@ -159,7 +151,16 @@ (defmethod v/= [::v/number ::modint] [l r] (mod:= r l)) (defmethod v/= [::modint ::v/number] [l r] (mod:= l r)) -(defmethod g/integer-part [::modint] [a] (residue a)) +(defmethod g/zero? [::modint] [^ModInt a] (g/zero? (.-i a))) +(defmethod g/one? [::modint] [^ModInt a] (g/one? (.-i a))) +(defmethod g/identity? [::modint] [^ModInt a] (g/one? (.-i a))) +(defmethod g/zero-like [::modint] [^ModInt a] (ModInt. (g/zero-like (.-i a)) (.-m a))) +(defmethod g/one-like [::modint] [^ModInt a] (ModInt. (g/one-like (.-i a)) (.-m a))) +(defmethod g/identity-like [::modint] [^ModInt a] (ModInt. (g/one-like (.-i a)) (.-m a))) +(defmethod g/freeze [::modint] [^ModInt a] (list 'modint (.-i a) (.-m a))) +(defmethod g/exact? [::modint] [_] true) + +(defmethod g/integer-part [::modint] [^ModInt a] (.-i a)) (defmethod g/fractional-part [::modint] [_] 0) (defmethod g/floor [::modint] [a] a) (defmethod g/ceiling [::modint] [a] a) diff --git a/src/emmy/numbers.cljc b/src/emmy/numbers.cljc index 2508670a..5d3fab2c 100644 --- a/src/emmy/numbers.cljc +++ b/src/emmy/numbers.cljc @@ -24,8 +24,30 @@ (java.math BigInteger) (org.apache.commons.math3.util ArithmeticUtils)))) +(def ^:private boolean-type #?(:clj Boolean :cljs js/Boolean)) +(defmethod g/zero? [boolean-type] [b] (not b)) +(defmethod g/one? [boolean-type] [b] b) +(defmethod g/identity? [boolean-type] [b] b) +(defmethod g/zero-like [boolean-type] [_] false) +(defmethod g/one-like [boolean-type] [_] true) +(defmethod g/identity-like [boolean-type] [_] true) + +(defmethod g/zero-like [::v/floating-point] [_] 0.0) +(defmethod g/one-like [::v/floating-point] [_] 1.0) +(defmethod g/identity-like [::v/floating-point] [_] 1.0) + ;; "Backstop" implementations that apply to anything that descends from ;; ::v/real. +(defmethod g/zero? [::v/real] [a] (core/zero? a)) +(defmethod g/one? [::v/real] [a] (== 1 a)) +(defmethod g/identity? [::v/real] [a] (== 1 a)) +(defmethod g/zero-like [::v/real] [_] 0) +(defmethod g/one-like [::v/real] [_] 1) +(defmethod g/identity-like [::v/real] [_] 1) +(defmethod g/exact? [::v/integral] [_] true) +(defmethod g/exact? [::v/floating-point] [_] false) +(defmethod g/freeze [::v/real] [a] a) + (defmethod g/add [::v/real ::v/real] [a b] (#?(:clj +' :cljs core/+) a b)) (defmethod g/mul [::v/real ::v/real] [a b] (#?(:clj *' :cljs core/*) a b)) (defmethod g/sub [::v/real ::v/real] [a b] (#?(:clj -' :cljs core/-) a b)) @@ -62,14 +84,14 @@ (defmethod g/angle [::v/real] [a] (if (neg? a) Math/PI - (v/zero-like a))) + (g/zero-like a))) (defmethod g/conjugate [::v/real] [a] a) ;; ## Trig Operations (defmethod g/sinc [::v/real] [a] - (cond (v/zero? a) 1 + (cond (g/zero? a) 1 (g/infinite? a) 0 :else (g// (g/sin a) a))) @@ -158,10 +180,10 @@ returns 1 or -1 respectively. If `a` is 1 or -1, returns `b` or `-b` respectively. Else, returns nil." [b a] - (cond (v/= a b) (v/one-like a) - (v/= a (g/negate b)) (g/negate (v/one-like a)) - (v/one? a) b - (v/one? (g/negate a)) (g/negate b) + (cond (v/= a b) (g/one-like a) + (v/= a (g/negate b)) (g/negate (g/one-like a)) + (g/one? a) b + (g/one? (g/negate a)) (g/negate b) :else nil)) (defmethod g/exact-divide [::v/scalar ::v/real] [b a] @@ -178,7 +200,7 @@ "Checked implementation of g/exact-divide general enough to use for any type that defines g/remainder and g/quotient." [a b] - {:pre [(v/zero? (g/remainder a b))]} + {:pre [(g/zero? (g/remainder a b))]} (g/quotient a b)) (defmethod g/exact-divide [::v/integral ::v/integral] [b a] @@ -265,12 +287,12 @@ (defmethod g/div [::v/integral ::v/integral] [a b] (let [rem (g/remainder a b)] - (if (v/zero? rem) + (if (g/zero? rem) (g/quotient a b) (r/rationalize a b)))) (defmethod g/invert [::v/integral] [a] - (if (v/one? a) + (if (g/one? a) a (r/rationalize 1 a))))) @@ -278,8 +300,9 @@ ;; don't respond true to number? These each require their own block of method ;; implementations. #?(:cljs - (do - ;; native BigInt type in JS. + ;; native BigInt type in JS. + (let [big-zero (js/BigInt 0) + big-one (js/BigInt 1)] (defmethod g/add [js/BigInt js/BigInt] [a b] (core/+ a b)) (defmethod g/mul [js/BigInt js/BigInt] [a b] (core/* a b)) (defmethod g/modulo [js/BigInt js/BigInt] [a b] (g/modulo-default a b)) @@ -291,6 +314,14 @@ (g/invert (js* "~{} ** ~{}" a (core/- b))) (js* "~{} ** ~{}" a b))) + (defmethod g/zero? [js/BigInt] [a] (coercive-= big-zero a)) + (defmethod g/one? [js/BigInt] [a] (coercive-= big-one a)) + (defmethod g/identity? [js/BigInt] [a] (coercive-= big-one a)) + (defmethod g/zero-like [js/BigInt] [_] big-zero) + (defmethod g/one-like [js/BigInt] [_] big-one) + (defmethod g/identity-like [js/BigInt] [_] big-one) + (defmethod g/exact? [js/BigInt] [_] true) + (defmethod g/abs [js/BigInt] [a] (if (neg? a) (core/- a) a)) (defmethod g/quotient [js/BigInt js/BigInt] [a b] (core// a b)) (defmethod g/remainder [js/BigInt js/BigInt] [a b] (js-mod a b)) @@ -299,7 +330,7 @@ (defmethod g/div [js/BigInt js/BigInt] [a b] (let [rem (js-mod a b)] - (if (v/zero? rem) + (if (g/zero? rem) (core// a b) (r/rationalize a b)))) @@ -333,8 +364,33 @@ (defmethod g/atan [js/BigInt ::v/real] [l r] (g/atan (js/Number l) r)) (defmethod g/atan [::v/real js/BigInt] [l r] (g/atan l (js/Number r))) (defmethod g/atan [js/BigInt js/BigInt] [l r] (g/atan (js/Number l) (js/Number r))) + (defmethod g/exact? [js/BigInt] [_] true) + (defmethod g/freeze [js/BigInt] [x] + ;; Bigint freezes into a non-bigint if it can be represented as a + ;; number; otherwise, it turns into its own literal. + (if (< (g/abs x) (.-MAX_SAFE_INTEGER js/Number)) + (js/Number x) + x)) + + ;; Google Closure library's Integer: + (defmethod g/zero? [Integer] [^Integer x] (.isZero x)) + (defmethod g/one? [Integer] [x] (core/= (.-ONE goog.math.Integer) x)) + (defmethod g/identity? [Integer] [x] (core/= (.-ONE goog.math.Integer) x)) + (defmethod g/zero-like [Integer] [_] (.-ZERO goog.math.Integer)) + (defmethod g/one-like [Integer] [_] (.-ONE goog.math.Integer)) + (defmethod g/identity-like [Integer] [_] (.-ONE goog.math.Integer)) + (defmethod g/exact? [Integer] [_] true) + (defmethod g/freeze [Integer] [x] x) ;; Google Closure library's 64-bit Long: + (defmethod g/zero? [Long] [^Long x] (.isZero x)) + (defmethod g/one? [Long] [x] (core/= (Long/getOne) x)) + (defmethod g/identity? [Long] [x] (core/= (Long/getOne) x)) + (defmethod g/zero-like [Long] [_] (Long/getZero)) + (defmethod g/one-like [Long] [_] (Long/getOne)) + (defmethod g/identity-like [Long] [_] (Long/getOne)) + (defmethod g/exact? [Long] [_] true) + (defmethod g/freeze [Long] [x] x) (defmethod g/add [Long Long] [a b] (.add a b)) (defmethod g/mul [Long Long] [a b] (.multiply a b)) (defmethod g/sub [Long Long] [^Long a ^Long b] (.subtract a b)) diff --git a/src/emmy/numerical/derivative.cljc b/src/emmy/numerical/derivative.cljc index 05cffead..e96d0fd6 100644 --- a/src/emmy/numerical/derivative.cljc +++ b/src/emmy/numerical/derivative.cljc @@ -25,7 +25,6 @@ [emmy.series :as series] [emmy.util :as u] [emmy.util.stream :as us] - [emmy.value :as v] [mentat.clerk-utils :refer [->clerk ->clerk-only]])) ^{:nextjournal.clerk/visibility {:code :hide :result :hide}} @@ -275,7 +274,7 @@ ;; $1.0 + \epsilon$ can be distinguished." ;; ;; -;; In the current library, `v/machine-epsilon` holds this value. +;; In the current library, `u/machine-epsilon` holds this value. ;; ;; Our goal, then, is to see if we can figure out when the error due to roundoff ;; grows so large that it exceeds the tolerance we want to apply to our @@ -330,7 +329,7 @@ `initial-error` * 2^n <= `tolerance`" [units tolerance] - (let [initial-error (* v/machine-epsilon units)] + (let [initial-error (* u/machine-epsilon units)] (Math/floor (/ (Math/log (/ tolerance initial-error)) (Math/log 2))))) @@ -468,7 +467,7 @@ "Fills in default values required by `D-numeric`. Any option not used by `D-numeric` gets passed on to `us/seq-limit`." [m] - (let [defaults {:tolerance v/sqrt-machine-epsilon + (let [defaults {:tolerance u/sqrt-machine-epsilon :method :central} {:keys [method] :as opts} (merge defaults m)] (assert (contains? valid-methods method) diff --git a/src/emmy/numerical/roots/bisect.cljc b/src/emmy/numerical/roots/bisect.cljc index 309acbc7..4b6f0ede 100644 --- a/src/emmy/numerical/roots/bisect.cljc +++ b/src/emmy/numerical/roots/bisect.cljc @@ -10,8 +10,7 @@ NOTE: This namespace is not yet stable: Expect these functions to change as new root-finding methods are added. " (:require [emmy.util :as u] - [emmy.util.stream :as us] - [emmy.value :as v])) + [emmy.util.stream :as us])) ;; ## Root finding by successive bisection ;; @@ -140,7 +139,7 @@ maxiter maxfun callback] - :or {eps v/machine-epsilon + :or {eps u/machine-epsilon maxiter 1000 callback (constantly nil)} :as opts}] diff --git a/src/emmy/numerical/unimin/brent.cljc b/src/emmy/numerical/unimin/brent.cljc index 7768bd80..d365156b 100644 --- a/src/emmy/numerical/unimin/brent.cljc +++ b/src/emmy/numerical/unimin/brent.cljc @@ -7,8 +7,7 @@ [emmy.numbers] [emmy.numerical.unimin.bracket :as ub] [emmy.numerical.unimin.golden :as ug] - [emmy.util :as u] - [emmy.value :as v]) + [emmy.util :as u]) #?(:clj (:import (org.apache.commons.math3.optim.univariate BrentOptimizer @@ -187,7 +186,7 @@ maxiter maxfun callback] - :or {relative-threshold (g/sqrt v/machine-epsilon) + :or {relative-threshold (g/sqrt u/machine-epsilon) absolute-threshold 1.0e-11 maxiter 1000 callback (constantly nil)}}] @@ -291,7 +290,7 @@ maxiter maxfun callback] - :or {relative-threshold (g/sqrt v/machine-epsilon) + :or {relative-threshold (g/sqrt u/machine-epsilon) absolute-threshold 1.0e-11 maxiter 1000 callback (constantly nil)}}] diff --git a/src/emmy/numsymb.cljc b/src/emmy/numsymb.cljc index 25b1865a..8dca0931 100644 --- a/src/emmy/numsymb.cljc +++ b/src/emmy/numsymb.cljc @@ -58,9 +58,9 @@ (fn [s] (if (v/number? s) (let [q (f s)] - (if-not (v/exact? s) + (if-not (g/exact? s) q - (if (v/exact? q) + (if (g/exact? q) q (process s)))) (process s))))) @@ -71,18 +71,18 @@ [a b f sym] (cond (and (v/number? a) (v/number? b)) (f a b) (= a b) 0 - (v/zero? a) 0 - (v/one? b) a + (g/zero? a) 0 + (g/one? b) a :else (list sym a b))) ;; these are without constructor simplifications! (defn- add [a b] (cond (and (v/number? a) (v/number? b)) (g/add a b) - (v/number? a) (cond (v/zero? a) b + (v/number? a) (cond (g/zero? a) b (sum? b) `(~'+ ~a ~@(operands b)) :else `(~'+ ~a ~b)) - (v/number? b) (cond (v/zero? b) a + (v/number? b) (cond (g/zero? b) a (sum? a) `(~'+ ~@(operands a) ~b) :else `(~'+ ~a ~b)) (sum? a) (cond (sum? b) `(~'+ ~@(operands a) ~@(operands b)) @@ -92,8 +92,8 @@ (defn- sub [a b] (cond (and (v/number? a) (v/number? b)) (g/sub a b) - (v/number? a) (if (v/zero? a) `(~'- ~b) `(~'- ~a ~b)) - (v/number? b) (if (v/zero? b) a `(~'- ~a ~b)) + (v/number? a) (if (g/zero? a) `(~'- ~b) `(~'- ~a ~b)) + (v/number? b) (if (g/zero? b) a `(~'- ~a ~b)) (= a b) 0 :else `(~'- ~a ~b))) @@ -101,12 +101,12 @@ (defn- mul [a b] (cond (and (v/number? a) (v/number? b)) (g/mul a b) - (v/number? a) (cond (v/zero? a) a - (v/one? a) b + (v/number? a) (cond (g/zero? a) a + (g/one? a) b (product? b) `(~'* ~a ~@(operands b)) :else `(~'* ~a ~b)) - (v/number? b) (cond (v/zero? b) b - (v/one? b) a + (v/number? b) (cond (g/zero? b) b + (g/one? b) a (product? a) `(~'* ~@(operands a) ~b) :else `(~'* ~a ~b)) (product? a) (cond (product? b) `(~'* ~@(operands a) ~@(operands b)) @@ -116,9 +116,9 @@ (defn- div [a b] (cond (and (v/number? a) (v/number? b)) (g/div a b) - (v/number? a) (if (v/zero? a) a `(~'/ ~a ~b)) - (v/number? b) (cond (v/zero? b) (u/arithmetic-ex "division by zero") - (v/one? b) a + (v/number? a) (if (g/zero? a) a `(~'/ ~a ~b)) + (v/number? b) (cond (g/zero? b) (u/arithmetic-ex "division by zero") + (g/one? b) a :else `(~'/ ~a ~b)) :else `(~'/ ~a ~b))) @@ -158,28 +158,28 @@ (def ^:private pi-over-2 (* 2 pi-over-4)) (defn ^:private n:zero-mod-pi? [x] - (v/almost-integral? (/ x pi))) + (g/almost-integral? (/ x pi))) (defn ^:private n:pi-over-2-mod-2pi? [x] - (v/almost-integral? (/ (- x pi-over-2 two-pi)))) + (g/almost-integral? (/ (- x pi-over-2 two-pi)))) (defn ^:private n:-pi-over-2-mod-2pi? [x] - (v/almost-integral? (/ (+ x pi-over-2) two-pi))) + (g/almost-integral? (/ (+ x pi-over-2) two-pi))) (defn ^:private n:pi-mod-2pi? [x] - (v/almost-integral? (/ (- x pi) two-pi))) + (g/almost-integral? (/ (- x pi) two-pi))) (defn ^:private n:pi-over-2-mod-pi? [x] - (v/almost-integral? (/ (- x pi-over-2) pi))) + (g/almost-integral? (/ (- x pi-over-2) pi))) (defn ^:private n:zero-mod-2pi? [x] - (v/almost-integral? (/ x two-pi))) + (g/almost-integral? (/ x two-pi))) (defn ^:private n:-pi-over-4-mod-pi? [x] - (v/almost-integral? (/ (+ x pi-over-4) pi))) + (g/almost-integral? (/ (+ x pi-over-4) pi))) (defn ^:private n:pi-over-4-mod-pi? [x] - (v/almost-integral? (/ (- x pi-over-4) pi))) + (g/almost-integral? (/ (- x pi-over-4) pi))) (def ^:no-doc zero-mod-pi? #{'-pi 'pi '-two-pi 'two-pi}) (def ^:no-doc pi-over-2-mod-2pi? #{'pi-over-2}) @@ -195,8 +195,8 @@ If it's not possible to do this (if the expression is symbolic, say), returns a symbolic form." [x] - (cond (v/number? x) (if (v/exact? x) - (if (v/zero? x) 0 (list 'sin x)) + (cond (v/number? x) (if (g/exact? x) + (if (g/zero? x) 0 (list 'sin x)) (cond (n:zero-mod-pi? x) 0 (n:pi-over-2-mod-2pi? x) 1 (n:-pi-over-2-mod-2pi? x) -1 @@ -212,8 +212,8 @@ If it's not possible to do this (if the expression is symbolic, say), returns a symbolic form." [x] - (cond (v/number? x) (if (v/exact? x) - (if (v/zero? x) 1 (list 'cos x)) + (cond (v/number? x) (if (g/exact? x) + (if (g/zero? x) 1 (list 'cos x)) (cond (n:pi-over-2-mod-pi? x) 0 (n:zero-mod-2pi? x) 1 (n:pi-mod-2pi? x) -1 @@ -229,8 +229,8 @@ If it's not possible to do this (if the expression is symbolic, say), returns a symbolic form." [x] - (cond (v/number? x) (if (v/exact? x) - (if (v/zero? x) 0 (list 'tan x)) + (cond (v/number? x) (if (g/exact? x) + (if (g/zero? x) 0 (list 'tan x)) (cond (n:zero-mod-pi? x) 0 (n:pi-over-4-mod-pi? x) 1 (n:-pi-over-4-mod-pi? x) -1 @@ -245,36 +245,36 @@ (defn- csc [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/csc x) - (if (v/zero? x) + (if (g/zero? x) (u/illegal (str "Zero argument -- g/csc" x)) `(~'/ 1 ~(sin x)))) `(~'/ 1 ~(sin x)))) (defn- sec [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/sec x) - (if (v/zero? x) + (if (g/zero? x) 1 `(~'/ 1 ~(cos x)))) `(~'/ 1 ~(cos x)))) (defn- asin [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/asin x) - (if (v/zero? x) + (if (g/zero? x) 0 (list 'asin x))) (list 'asin x))) (defn- acos [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/acos x) - (if (v/one? x) + (if (g/one? x) 0 (list 'acos x))) (list 'acos x))) @@ -282,22 +282,22 @@ (defn- atan ([y] (if (v/number? y) - (if-not (v/exact? y) + (if-not (g/exact? y) (g/atan y) - (if (v/zero? y) + (if (g/zero? y) 0 (list 'atan y))) (list 'atan y))) ([y x] - (cond (v/one? x) (atan y) + (cond (g/one? x) (atan y) - (v/exact-zero? y) + (g/exact-zero? y) (if (v/number? x) (if (g/negative? x) 'pi 0) (and (ul/assume! `(~'non-negative? ~x) 'numsymb-atan) 0)) - (v/exact-zero? x) + (g/exact-zero? x) (if (v/number? y) (if (g/negative? y) '(- (/ pi 2)) @@ -307,26 +307,26 @@ (and (v/number? x) (v/number? y) - (or (not (v/exact? x)) - (not (v/exact? y)))) + (or (not (g/exact? x)) + (not (g/exact? y)))) (g/atan y x) :else (list 'atan y x)))) (defn- cosh [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/cosh x) - (if (v/zero? x) + (if (g/zero? x) 1 (list 'cosh x))) (list 'cosh x))) (defn- sinh [x] (if (v/number? x) - (if-not (v/exact? x) + (if-not (g/exact? x) (g/sinh x) - (if (v/zero? x) + (if (g/zero? x) 0 (list 'sinh x))) (list 'sinh x))) @@ -357,22 +357,22 @@ (defn- gcd [a b] (cond (and (v/number? a) (v/number? b)) (g/gcd a b) - (v/number? a) (cond (v/zero? a) b - (v/one? a) 1 + (v/number? a) (cond (g/zero? a) b + (g/one? a) 1 :else (list 'gcd a b)) - (v/number? b) (cond (v/zero? b) a - (v/one? b) 1 + (v/number? b) (cond (g/zero? b) a + (g/one? b) 1 :else (list 'gcd a b)) (= a b) a :else (list 'gcd a b))) (defn- lcm [a b] (cond (and (v/number? a) (v/number? b)) (g/lcm a b) - (v/number? a) (cond (v/zero? a) 0 - (v/one? a) b + (v/number? a) (cond (g/zero? a) 0 + (g/one? a) b :else (list 'lcm a b)) - (v/number? b) (cond (v/zero? b) 0 - (v/one? b) a + (v/number? b) (cond (g/zero? b) 0 + (g/one? b) a :else (list 'lcm a b)) (= a b) a :else (list 'lcm a b))) @@ -397,10 +397,10 @@ evaluates symbolically or numerically." [b e] (cond (and (v/number? b) (v/number? e)) (g/expt b e) - (v/number? b) (cond (v/one? b) 1 + (v/number? b) (cond (g/one? b) 1 :else `(~'expt ~b ~e)) - (v/number? e) (cond (v/zero? e) 1 - (v/one? e) b + (v/number? e) (cond (g/zero? e) 1 + (g/one? e) b (and (integer? e) (even? e) (sqrt? b)) (expt (first (operands b)) (quot e 2)) (and (expt? b) @@ -425,7 +425,7 @@ '+ '- '* '/ 'expt 'up 'down}) (defn- make-rectangular [r i] - (cond (v/exact-zero? i) r + (cond (g/exact-zero? i) r (and (v/real? r) (v/real? i)) (g/make-rectangular r i) @@ -433,8 +433,8 @@ :else (add r (mul c/I i)))) (defn- make-polar [m a] - (cond (v/exact-zero? m) m - (v/exact-zero? a) m + (cond (g/exact-zero? m) m + (g/exact-zero? a) m (and (v/real? m) (v/real? a)) (g/make-polar m a) :else (mul m (add (cos a) @@ -580,12 +580,12 @@ (defn- sym:zero? [x] (if (v/number? x) - (v/zero? x) + (g/zero? x) (list '= 0 x))) (defn- sym:one? [x] (if (v/number? x) - (v/one? x) + (g/one? x) (list '= 1 x))) ;; ## Table @@ -602,12 +602,12 @@ 'invert invert '+ (ua/monoid add 0) '- (ua/group sub add negate 0) - '* (ua/monoid mul 1 v/zero?) - '/ (ua/group div mul invert 1 v/zero?) + '* (ua/monoid mul 1 g/zero?) + '/ (ua/group div mul invert 1 g/zero?) 'modulo modulo 'remainder remainder 'gcd (ua/monoid gcd 0) - 'lcm (ua/monoid lcm 1 v/zero?) + 'lcm (ua/monoid lcm 1 g/zero?) 'floor floor 'ceiling ceiling 'integer-part integer-part diff --git a/src/emmy/operator.cljc b/src/emmy/operator.cljc index 1bad5af6..504c501d 100644 --- a/src/emmy/operator.cljc +++ b/src/emmy/operator.cljc @@ -26,46 +26,7 @@ (declare op:get) (deftype Operator [o arity name context m] - v/Value - (zero? [this] - (if-let [z-fn (:zero? context)] - (z-fn this) - (= o v/zero-like))) - - ;; NOTE: `one?` is the multiplicative identity; by default, we return false - ;; because the system doesn't currently check if the types match for - ;; multiplicative identity. So `(* o:identity 5)` would return 5, which is - ;; incorrect. (We should get back a new operator that carries the scale-by-5 - ;; along until the final function resolves.) - (one? [this] - (if-let [one-fn (:one? context)] - (one-fn this) - false)) - - (identity? [this] - (if-let [id-fn (:identity? context)] - (id-fn this) - (= o core/identity))) - - (zero-like [this] - (if-let [z-fn (:zero-like context)] - (z-fn this) - (Operator. v/zero-like arity 'zero context m))) - - (one-like [this] - (if-let [one-fn (:one-like context)] - (one-fn this) - (Operator. core/identity arity 'identity context m))) - - (identity-like [this] - (if-let [id-fn (:identity-like context)] - (id-fn this) - (Operator. core/identity arity 'identity context m))) - - (freeze [_] - (simplify-operator-name - (v/freeze name))) - + v/IKind (kind [_] (:subtype context)) f/IArity @@ -86,7 +47,7 @@ Object (toString [o] - (let [n (v/freeze o)] + (let [n (g/freeze o)] (str (if (seqable? n) (seq n) n)))) @@ -230,7 +191,7 @@ (make-operator (f/get (procedure o) k) `(~'compose (~'component ~k) - ~(name o)))) + ~(name o)))) (def identity "Identity operator. Returns its argument unchanged." @@ -286,7 +247,7 @@ (let [h (f/coerce-to-fn f [:exactly 1])] (->Operator (fn [g] (op (f/compose h g) (o g))) (arity o) - `(~sym ~(v/freeze f) ~(name o)) + `(~sym ~(g/freeze f) ~(name o)) (context o) nil))) @@ -311,7 +272,7 @@ (let [h (f/coerce-to-fn f [:exactly 1])] (->Operator (fn [g] (op (o g) (f/compose h g))) (arity o) - `(~sym ~(name o) ~(v/freeze f)) + `(~sym ~(name o) ~(g/freeze f)) (context o) nil))) @@ -325,7 +286,7 @@ (g/negate (apply o fs))) (arity o) (list '- (name o)) - (context o) + (context o) (meta o))) (defn- o:- @@ -333,7 +294,7 @@ difference of applying the supplied operators." [o p] (let [ctx (joint-context o p)] - (if (v/zero? p) + (if (g/zero? p) (with-context o ctx) (->Operator (fn [& xs] (g/sub (apply o xs) @@ -351,8 +312,8 @@ given operators." [o p] (let [ctx (joint-context o p)] - (cond (v/zero? o) (with-context p ctx) - (v/zero? p) (with-context o ctx) + (cond (g/zero? o) (with-context p ctx) + (g/zero? p) (with-context o ctx) :else (->Operator (fn [& xs] (g/add (apply o xs) @@ -371,9 +332,9 @@ ([o] o) ([o p] (let [ctx (joint-context o p)] - (cond (v/identity? o) (with-context p ctx) - (v/identity? p) (with-context o ctx) - (v/zero? o) (with-context o ctx) + (cond (g/identity? o) (with-context p ctx) + (g/identity? p) (with-context o ctx) + (g/zero? o) (with-context o ctx) :else (->Operator (f/compose o p) (arity p) @@ -388,7 +349,7 @@ (->Operator (fn [& gs] (g/mul f (apply o gs))) (arity o) - `(~'* ~(v/freeze f) ~(name o)) + `(~'* ~(g/freeze f) ~(name o)) (context o) (meta o))) @@ -399,7 +360,7 @@ (->Operator (fn [& gs] (apply o (map (fn [g] (g/mul f g)) gs))) (arity o) - `(~'* ~(name o) ~(v/freeze f)) + `(~'* ~(name o) ~(g/freeze f)) (context o) (meta o))) @@ -410,7 +371,7 @@ (->Operator (fn [& gs] (g/mul (g/invert n) (apply o gs))) (arity o) - `(~'/ ~(name o) ~n) + `(~'/ ~(name o) ~n) (context o) (meta o))) @@ -479,6 +440,45 @@ (context g) nil)))) +(defmethod g/zero? [::operator] [^Operator o] + (if-let [z-fn (:zero? (.-context o))] + (z-fn o) + (= (.-o o) g/zero-like))) + +;; NOTE: `one?` is the multiplicative identity; by default, we return false +;; because the system doesn't currently check if the types match for +;; multiplicative identity. So `(* o:identity 5)` would return 5, which is +;; incorrect. (We should get back a new operator that carries the scale-by-5 +;; along until the final function resolves.) +(defmethod g/one? [::operator] [^Operator o] + (if-let [one-fn (:one? (.-context o))] + (one-fn o) + false)) + +(defmethod g/identity? [::operator] [^Operator o] + (if-let [id-fn (:identity? (.-context o))] + (id-fn o) + (= (.-o o) core/identity))) + +(defmethod g/zero-like [::operator] [^Operator o] + (if-let [z-fn (:zero-like (.-context o))] + (z-fn o) + (Operator. g/zero-like (.-arity o) 'zero (.-context o) (.-m o)))) + +(defmethod g/one-like [::operator] [^Operator o] + (if-let [one-fn (:one-like (.-context o))] + (one-fn o) + (Operator. core/identity (.-arity o) 'identity (.-context o) (.-m o)))) + +(defmethod g/identity-like [::operator] [^Operator o] + (if-let [id-fn (:identity-like (.-context o))] + (id-fn o) + (Operator. core/identity (.-arity o) 'identity (.-context o) (.-m o)))) + +(defmethod g/freeze [::operator] [^Operator o] + (simplify-operator-name + (g/freeze (.-name o)))) + (defmethod g/add [::operator ::operator] [o p] (o:+ o p)) (defmethod g/add [::operator ::co-operator] [o f] (o+f o f)) (defmethod g/add [::co-operator ::operator] [f o] (f+o f o)) diff --git a/src/emmy/polynomial.cljc b/src/emmy/polynomial.cljc index 51b96955..21b2e227 100644 --- a/src/emmy/polynomial.cljc +++ b/src/emmy/polynomial.cljc @@ -1,8 +1,8 @@ #_"SPDX-License-Identifier: GPL-3.0" ^#:nextjournal.clerk -{:toc true - :visibility :hide-ns} + {:toc true + :visibility :hide-ns} (ns emmy.polynomial (:refer-clojure :exclude [extend divide identity abs]) (:require [clojure.set :as set] @@ -135,44 +135,7 @@ (extract-tangent [this tag] (map-coefficients #(sd/extract-tangent % tag) this)) - v/Value - (zero? [_] - (empty? terms)) - - (one? [_] - (and (= (count terms) 1) - (let [[term] terms] - (and (i/constant-term? term) - (v/one? (i/coefficient term)))))) - - (identity? [_] - (and (v/one? arity) - (= (count terms) 1) - (let [[term] terms] - (and (= {0 1} (i/exponents term)) - (v/one? (i/coefficient term)))))) - - (zero-like [_] - (if-let [term (nth terms 0)] - (v/zero-like (i/coefficient term)) - 0)) - - (one-like [_] - (if-let [term (nth terms 0)] - (v/one-like (i/coefficient term)) - 1)) - - (identity-like [_] - (assert (v/one? arity) - "identity-like unsupported on multivariate monomials!") - (let [one (if-let [term (nth terms 0)] - (v/one-like (i/coefficient term)) - 1) - term (i/make-term (xpt/make 0 1) one)] - (Polynomial. 1 [term] m))) - - (exact? [_] false) - (freeze [_] `(~'polynomial ~arity ~terms)) + v/IKind (kind [_] ::polynomial) #?@(:clj @@ -466,7 +429,7 @@ If `root` is 0, [[linear]] is equivalent to the two-argument version of [[identity]]." [arity i root] - (if (v/zero? root) + (if (g/zero? root) (identity arity i) (add (constant arity (g/negate root)) (identity arity i)))) @@ -484,7 +447,7 @@ NOTE that negative exponents are not allowed." [arity c n] {:pre [(>= n 0)]} - (cond (v/zero? c) c + (cond (g/zero? c) c (zero? n) (constant arity c) :else (let [term (i/make-term (xpt/make 0 n) c)] @@ -530,7 +493,7 @@ [p] (cond (polynomial? p) (bare-terms p) (vector? p) p - (v/zero? p) [] + (g/zero? p) [] :else [(i/make-term p)])) (defn ^:no-doc check-same-arity @@ -592,7 +555,7 @@ for color on why this is the case. " ([p] - (cond (v/zero? p) zero-degree + (cond (g/zero? p) zero-degree (polynomial? p) (xpt/monomial-degree (i/exponents @@ -600,7 +563,7 @@ :else coeff-arity)) ([p i] (let [i (validate-arity! p i)] - (cond (v/zero? p) zero-degree + (cond (g/zero? p) zero-degree (polynomial? p) (letfn [(i-degree [term] (-> (i/exponents term) @@ -655,7 +618,7 @@ If `p` is zero, returns an empty list." [p] (cond (polynomial? p) (map i/coefficient (->terms p)) - (v/zero? p) [] + (g/zero? p) [] :else [p])) (defn leading-term @@ -722,7 +685,7 @@ (xpt/monomial-degree (i/exponents (nth (bare-terms p) 0))) - (v/zero? p) zero-degree + (g/zero? p) zero-degree :else coeff-arity)) (defn monomial? @@ -747,9 +710,9 @@ [p] (if (polynomial? p) (and (= 1 (arity p)) - (v/one? + (g/one? (leading-coefficient p))) - (v/one? p))) + (g/one? p))) (defn univariate? "Returns true if `p` is a [[Polynomial]] of arity 1, false otherwise." @@ -818,7 +781,7 @@ (for [[expts c] (bare-terms p) :let [f-expts (f expts)]] (i/make-term f-expts c))) - (v/zero? p) p + (g/zero? p) p :else (handle-constant))))) ;; ## Manipulations @@ -911,7 +874,7 @@ NOTE that [[scale]] will return a non-[[Polynomial]] if the result of the mapping has only a constant term." [p c] - (if (v/zero? c) + (if (g/zero? c) c (map-coefficients #(g/* % c) p))) @@ -924,7 +887,7 @@ NOTE that [[scale-l]] will return a non-[[Polynomial]] if the result of the mapping has only a constant term." [c p] - (if (v/zero? c) + (if (g/zero? c) c (map-coefficients #(g/* c %) p))) @@ -945,8 +908,8 @@ ([p] (normalize p (leading-coefficient p))) ([p c] - (cond (v/one? c) p - (v/zero? c) (u/arithmetic-ex + (cond (g/one? c) p + (g/zero? c) (u/arithmetic-ex (str "Divide by zero: " p c)) (polynomial? c) (evenly-divide p c) :else (scale p (g/invert c))))) @@ -1105,8 +1068,8 @@ (neg? n) (u/illegal (str "No inverse -- FPF:EXPT:" p n)) - (v/one? p) p - (v/zero? p) (if (v/zero? n) + (g/one? p) p + (g/zero? p) (if (g/zero? n) (u/arithmetic-ex "poly 0^0") p) @@ -1123,10 +1086,10 @@ u == (add (mul quotient v) remainder) ```" [u v] - (cond (v/zero? v) + (cond (g/zero? v) (u/illegal "internal polynomial division by zero") - (or (v/zero? u) (v/one? v)) + (or (g/zero? u) (g/one? v)) [u 0] :else @@ -1147,7 +1110,7 @@ satisfies [[emmy.value/zero?]]." [n d] (let [[_ r] (divide n d)] - (v/zero? r))) + (g/zero? r))) (defn evenly-divide "Returns the result of dividing the polynomial `u` by `v` (non-[[Polynomial]] @@ -1156,10 +1119,10 @@ Throws an exception if the division leaves a remainder. Else, returns the quotient." [u v] - (if (v/one? v) + (if (g/one? v) u (let [[q r] (divide u v)] - (when-not (v/zero? r) + (when-not (g/zero? r) (u/illegal-state (str "expected even division left a remainder! " u " / " v " r " r))) q))) @@ -1198,7 +1161,7 @@ [u v] {:pre [(univariate? u) (univariate? v) - (not (v/zero? v))]} + (not (g/zero? v))]} (let [[vn-expts vn-coeff] (leading-term v) #?@(:cljs [vn-coeff (->big vn-coeff)]) *vn (fn [p] (scale p vn-coeff)) @@ -1410,7 +1373,7 @@ (assert (<= (count xs) a) (str "Too many args: " xs)) (cond (empty? xs) p - (v/zero? p) 0 + (g/zero? p) 0 :else (let [x (first xs) x (if (and (polynomial? x) (> a 1)) @@ -1463,7 +1426,7 @@ (call next-degree np nq nr ne (drop-leading-term a)) (cont np nq (* 2 nr) - (* v/machine-epsilon + (* u/machine-epsilon (+ (- ne (Math/abs ^double np)) ne)))))] (cond (= n 1) (let [np (+ (* z p) (leading-coefficient a)) @@ -1577,13 +1540,13 @@ instances." {'+ (ua/monoid g/add 0) '- (ua/group g/sub g/add g/negate 0) - '* (ua/monoid g/mul 1 v/zero?) + '* (ua/monoid g/mul 1 g/zero?) 'negate g/negate 'expt g/expt 'square g/square 'cube g/cube 'gcd (ua/monoid g/gcd 0) - 'lcm (ua/monoid g/lcm 1 v/zero?)}) + 'lcm (ua/monoid g/lcm 1 g/zero?)}) (def ^:no-doc operators-known "Set of all arithmetic functions allowed between [[Polynomial]] and coefficient @@ -1719,6 +1682,45 @@ ;; NOTE: What about `g/modulo`? Does that belong for [[Polynomial]] instances? ;; How does it differ from `g/remainder`? +(defmethod g/zero? [::polynomial] [^Polynomial a] (empty? (.-terms a))) + +(defmethod g/one? [::polynomial] [^Polynomial a] + (let [terms (.-terms a)] + (and (= (count terms) 1) + (let [[term] terms] + (and (i/constant-term? term) + (g/one? (i/coefficient term))))))) + +(defmethod g/identity? [::polynomial] [^Polynomial a] + (let [terms (.-terms a) + arity (.-arity a)] + (and (g/one? arity) + (= (count terms) 1) + (let [[term] terms] + (and (= {0 1} (i/exponents term)) + (g/one? (i/coefficient term))))))) + +(defmethod g/zero-like [::polynomial] [^Polynomial a] + (if-let [term (nth (.-terms a) 0)] + (g/zero-like (i/coefficient term)) + 0)) + +(defmethod g/one-like [::polynomial] [^Polynomial a] + (if-let [term (nth (.-terms a) 0)] + (g/one-like (i/coefficient term)) + 1)) + +(defmethod g/identity-like [::polynomial] [^Polynomial a] + (assert (g/one? (.-arity a)) + "identity-like unsupported on multivariate monomials!") + (let [one (if-let [term (nth (.-terms a) 0)] + (g/one-like (i/coefficient term)) + 1) + term (i/make-term (xpt/make 0 1) one)] + (Polynomial. 1 [term] (.-m a)))) + +(defmethod g/freeze [::polynomial] [^Polynomial a] `(~'polynomial ~(.-arity a) ~(.-terms a))) +(defmethod g/exact? [::polynomial] [_] false) (defmethod g/negative? [::polynomial] [a] (negative? a)) (defmethod g/abs [::polynomial] [a] (abs a)) (defmethod g/negate [::polynomial] [a] (negate a)) diff --git a/src/emmy/polynomial/factor.cljc b/src/emmy/polynomial/factor.cljc index f149a696..52a97d8a 100644 --- a/src/emmy/polynomial/factor.cljc +++ b/src/emmy/polynomial/factor.cljc @@ -6,6 +6,7 @@ (:require [clojure.walk :as w] [emmy.expression :as x] [emmy.expression.analyze :as a] + [emmy.generic :as g] [emmy.numsymb :as sym] [emmy.pattern.rule :as r :refer [=> rule-simplifier]] [emmy.polynomial :as poly] @@ -34,7 +35,7 @@ tracker [] old-s p old-m 1] - (if (v/one? m) + (if (g/one? m) (answer tracker h) (let [gg (gcd-Dp h) new-s (poly/evenly-divide h (gcd h gg)) diff --git a/src/emmy/polynomial/gcd.cljc b/src/emmy/polynomial/gcd.cljc index 36090e30..f3be8049 100644 --- a/src/emmy/polynomial/gcd.cljc +++ b/src/emmy/polynomial/gcd.cljc @@ -227,7 +227,7 @@ primitive (p/map-coefficients (fn [_] 1) p)] [content primitive]) (let [content (apply gcd coeffs) - primitive (if (v/one? content) + primitive (if (g/one? content) p (p/map-coefficients #(g/exact-divide % content) p))] @@ -296,10 +296,10 @@ reached. NOTE: This is only appropriate if you don't expect rational coefficients; the - GCD of 1 and a rational number IS that other number, so the `v/one?` guard is + GCD of 1 and a rational number IS that other number, so the `g/one?` guard is not appropriate." [binary-gcd] - (ua/monoid binary-gcd 0 v/one?)) + (ua/monoid binary-gcd 0 g/one?)) (def ^:no-doc primitive-gcd (->gcd (fn [l r] @@ -328,8 +328,8 @@ divisor of `u` and `v` by testing for trivial cases. If no trivial case applies, returns `nil`." [u v] - (cond (v/zero? u) (g/abs v) - (v/zero? v) (g/abs u) + (cond (g/zero? u) (g/abs v) + (g/zero? v) (g/abs u) (p/coeff? u) (if (p/coeff? v) (primitive-gcd u v) (gcd-poly-number v u)) @@ -387,7 +387,7 @@ (maybe-bail-out! "euclid inner loop") (or (trivial-gcd u v) (let [[r _] (p/pseudo-remainder u v)] - (if (v/zero? r) + (if (g/zero? r) (g/abs v) (let [[_ prim] (->content+primitive r gcd)] (recur v prim))))))) @@ -516,7 +516,7 @@ If a non-[[p/Polynomial]] is supplied, returns 1." [p] (if (p/polynomial? p) - (transduce (halt-when v/one?) + (transduce (halt-when g/one?) gcd (p/partial-derivatives p)) 1)) @@ -532,11 +532,11 @@ (gcd-dispatch u v)) (defmethod g/gcd [::p/polynomial ::p/coeff] [u v] - (if (v/zero? v) + (if (g/zero? v) u (gcd-poly-number u v))) (defmethod g/gcd [::p/coeff ::p/polynomial] [u v] - (if (v/zero? u) + (if (g/zero? u) v (gcd-poly-number v u))) diff --git a/src/emmy/polynomial/impl.cljc b/src/emmy/polynomial/impl.cljc index 607d7c2b..a32054d2 100644 --- a/src/emmy/polynomial/impl.cljc +++ b/src/emmy/polynomial/impl.cljc @@ -7,8 +7,7 @@ (:require [emmy.generic :as g] [emmy.polynomial.exponent :as xpt] [emmy.util :as u] - [emmy.util.aggregate :as ua] - [emmy.value :as v])) + [emmy.util.aggregate :as ua])) ;; ## Flat Polynomial Form ;; @@ -88,7 +87,7 @@ (defn constant->terms "Given some constant coefficient `coef`, returns a constant polynomial." [coef] - (if (v/zero? coef) + (if (g/zero? coef) empty-terms [(make-term xpt/empty coef)])) @@ -107,7 +106,7 @@ ```" [coefs] (let [->term (fn [i coef] - (when-not (v/zero? coef) + (when-not (g/zero? coef) (let [expts (if (zero? i) xpt/empty (xpt/make 0 i))] @@ -151,7 +150,7 @@ (->> (for [[expts terms] (group-by exponents expts->coef) :let [coef-sum (transduce (map coefficient) g/+ terms)] - :when (not (v/zero? coef-sum)) + :when (not (g/zero? coef-sum)) :let [expts (cond (vector? expts) (xpt/dense->exponents expts) (sorted? expts) expts (map? expts) (into xpt/empty expts) @@ -176,14 +175,14 @@ (into empty-terms (for [[expts c] terms :let [f-c (f c)] - :when (not (v/zero? f-c))] + :when (not (g/zero? f-c))] (make-term expts f-c)))) (def ^{:arglists '([u v])} add "Returns the sum of polynomials `u` and `v`. Coefficients paired with matching exponents are combined with [[emmy.generic/add]]." - (ua/merge-fn #'*monomial-order* g/add v/zero? make-term)) + (ua/merge-fn #'*monomial-order* g/add g/zero? make-term)) (defn sub "Returns the difference of polynomials `u` and `v`. diff --git a/src/emmy/polynomial/richardson.cljc b/src/emmy/polynomial/richardson.cljc index 5da49483..14a1e7d8 100644 --- a/src/emmy/polynomial/richardson.cljc +++ b/src/emmy/polynomial/richardson.cljc @@ -10,8 +10,8 @@ (:require [emmy.algebra.fold :as af] [emmy.generic :as g] [emmy.polynomial.interpolate :as pi] + [emmy.util :as u] [emmy.util.stream :as us] - [emmy.value :as v] [mentat.clerk-utils :refer [->clerk-only]])) ;; ## Richardson Interpolation @@ -78,7 +78,7 @@ (->clerk-only (-> archimedean-pi-sequence - (us/seq-limit {:tolerance v/machine-epsilon}))) + (us/seq-limit {:tolerance u/machine-epsilon}))) ;; Enter Sussman: "Imagine poor Archimedes doing the arithmetic by hand: square ;; roots without even the benefit of our place value system! He would be @@ -256,7 +256,7 @@ (comment (= (-> (richardson-sequence archimedean-pi-sequence 2 2 2) - (us/seq-limit {:tolerance v/machine-epsilon})) + (us/seq-limit {:tolerance u/machine-epsilon})) {:converged? true :terms-checked 7 diff --git a/src/emmy/quaternion.cljc b/src/emmy/quaternion.cljc index be475643..2249e29f 100644 --- a/src/emmy/quaternion.cljc +++ b/src/emmy/quaternion.cljc @@ -1,8 +1,8 @@ #_"SPDX-License-Identifier: GPL-3.0" ^#:nextjournal.clerk -{:toc true - :visibility :hide-ns} + {:toc true + :visibility :hide-ns} (ns emmy.quaternion "This namespace provides a number of functions and constructors for working with [[Quaternion]] instances in Clojure and ClojureScript, and @@ -103,7 +103,7 @@ ;; - The `D` operator should work on quaternions, so derivatives of functions ;; that return quaternions work well. ;; -;; - All [[emmy.value/Value]] functions should work well. +;; - All [[emmy.generic]] functions should work well. (deftype Quaternion [r i j k m] f/IArity @@ -132,29 +132,7 @@ (d/extract-tangent k tag) m)) - v/Value - (zero? [this] (zero? this)) - (one? [this] (one? this)) - (identity? [this] (one? this)) - - (zero-like [_] - (Quaternion. (v/zero-like r) 0 0 0 m)) - (one-like [_] - (Quaternion. (v/one-like r) 0 0 0 m)) - (identity-like [_] - (Quaternion. (v/one-like r) 0 0 0 m)) - - (exact? [_] - (and (v/exact? r) - (v/exact? i) - (v/exact? j) - (v/exact? k))) - (freeze [_] - (list 'quaternion - (v/freeze r) - (v/freeze i) - (v/freeze j) - (v/freeze k))) + v/IKind (kind [_] ::quaternion) #?@(:clj @@ -493,28 +471,28 @@ "Returns true if the quaternion `q` has zero entries for all non-real fields, false otherwise." [q] - (and (v/zero? (get-i q)) - (v/zero? (get-j q)) - (v/zero? (get-k q)))) + (and (g/zero? (get-i q)) + (g/zero? (get-j q)) + (g/zero? (get-k q)))) -(defn zero? +(defn- zero? "Returns true if `q` is a quaternion with zeros in all coefficient positions, false otherwise." [q] - (and (real? q) (v/zero? (get-r q)))) + (and (real? q) (g/zero? (get-r q)))) -(defn one? +(defn- one? "Returns true if `q` is a [[real?]] quaternion with a one-like coefficient in the real position, false otherwise." [q] - (and (real? q) (v/one? (get-r q)))) + (and (real? q) (g/one? (get-r q)))) (defn pure? "Returns true if the quaternion `q` has a zero real entry, false otherwise. A 'pure' quaternion is sometimes called an 'imaginary' quaternion." [q] - (v/zero? (get-r q))) + (g/zero? (get-r q))) (declare magnitude-sq) @@ -530,7 +508,7 @@ (let [mag-sq (magnitude-sq q)] (if epsilon ((v/within epsilon) 1 (g/sqrt mag-sq)) - (v/one? mag-sq))))) + (g/one? mag-sq))))) (defn eq "Returns true if the supplied quaternion `q1` is equal to the value `q2`. The @@ -564,8 +542,8 @@ (sc/complex? q2) (and (v/= r (sc/real q2)) (v/= i (sc/imaginary q2)) - (v/zero? j) - (v/zero? k)) + (g/zero? j) + (g/zero? k)) (sequential? q2) (and (= (count q2) 4) @@ -1069,9 +1047,9 @@ $$" [q] (let [[r i j k] q] - (if (and (v/zero? j) - (v/zero? k)) - (if (v/zero? i) + (if (and (g/zero? j) + (g/zero? k)) + (if (g/zero? i) (make (g/log r)) (make (g/log (g/abs [r i])) (g/atan i r) @@ -1099,7 +1077,7 @@ exp-r (g/exp r) v (three-vector q) v-mag (g/abs v)] - (if (v/zero? v-mag) + (if (g/zero? v-mag) (make exp-r 0 0 0) (make (g/* exp-r (g/cos v-mag)) (g/* exp-r (g/sinc v-mag) v))))) @@ -1472,21 +1450,21 @@ q2 (g// q2q3 q3)] (make q0 q1 q2 q3))) - (not (v/numeric-zero? q0-2s)) + (not (g/zero? q0-2s)) (let [q0 (g/sqrt q0-2) q1 (g// q0q1 q0) q2 (g// q0q2 q0) q3 (g// q0q3 q0)] (make q0 q1 q2 q3)) - (not (v/numeric-zero? q1-2s)) + (not (g/zero? q1-2s)) (let [q1 (g/sqrt q1-2) q0 0 q2 (g// q1q2 q1) q3 (g// q1q3 q1)] (make q0 q1 q2 q3)) - (not (v/numeric-zero? q2-2s)) + (not (g/zero? q2-2s)) (let [q2 (g/sqrt q2-2) q0 0 q1 0 @@ -1544,6 +1522,28 @@ ;; ## Generic Method Installation ;; + +(defmethod g/zero? [::quaternion] [a] (zero? a)) +(defmethod g/one? [::quaternion] [a] (one? a)) +(defmethod g/identity? [::quaternion] [a] (one? a)) +(defmethod g/zero-like [::quaternion] [^Quaternion a] + (Quaternion. (g/zero-like (.-r a)) 0 0 0 (.-m a))) +(defmethod g/one-like [::quaternion] [^Quaternion a] + (Quaternion. (g/one-like (.-r a)) 0 0 0 (.-m a))) +(defmethod g/identity-like [::quaternion] [^Quaternion a] + (Quaternion. (g/one-like (.-r a)) 0 0 0 (.-m a))) +(defmethod g/exact? [::quaternion] [^Quaternion a] + (and (g/exact? (.-r a)) + (g/exact? (.-i a)) + (g/exact? (.-j a)) + (g/exact? (.-k a)))) +(defmethod g/freeze [::quaternion] [^Quaternion a] + (list 'quaternion + (g/freeze (.-r a)) + (g/freeze (.-i a)) + (g/freeze (.-j a)) + (g/freeze (.-k a)))) + ;; ### Equality ;; ;; Because equality is a symmetric relation, these methods arrange their diff --git a/src/emmy/ratio.cljc b/src/emmy/ratio.cljc index 91faf482..d746e4e6 100644 --- a/src/emmy/ratio.cljc +++ b/src/emmy/ratio.cljc @@ -12,7 +12,7 @@ (:refer-clojure :exclude [ratio? numerator denominator rationalize]) (:require #?(:clj [clojure.core :as core]) #?(:clj [clojure.edn] :cljs [cljs.reader]) - #?(:cljs ["fraction.js/bigfraction.js$default" :as Fraction]) + #?(:cljs ["fraction.js/bigfraction.js" :as Fraction]) #?(:cljs [emmy.complex :as c]) #?(:cljs [goog.array :as garray]) #?(:cljs [goog.object :as obj]) @@ -57,7 +57,7 @@ #?(:cljs (defn- promote [x] - (if (v/one? (denominator x)) + (if (g/one? (denominator x)) (numerator x) x))) @@ -69,7 +69,7 @@ (Fraction. x)) :clj (core/rationalize x))) ([n d] - #?(:cljs (if (v/one? d) + #?(:cljs (if (g/one? d) n (promote (Fraction. n d))) :clj (core/rationalize (/ n d))))) @@ -110,54 +110,44 @@ :else (u/illegal (str "Invalid ratio: " x)))) #?(:clj - (extend-type Ratio - v/Numerical - (numerical? [_] true) - - v/Value - (zero? [c] (zero? c)) - (one? [c] (= 1 c)) - (identity? [c] (= 1 c)) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (freeze [x] (let [n (numerator x) - d (denominator x)] - (if (v/one? d) - n - `(~'/ ~n ~d)))) - (exact? [_] true) - (kind [_] Ratio)) + (do + (defmethod g/exact? [Ratio] [_] true) + (defmethod g/freeze [Ratio] [x] + (let [n (numerator x) + d (denominator x)] + (if (g/one? d) + n + `(~'/ ~n ~d)))) + (extend-type Ratio + v/Numerical + (numerical? [_] true) + + v/IKind + (kind [_] Ratio))) :cljs - (let [ZERO (Fraction. 0) - ONE (Fraction. 1)] + (do + (defmethod g/exact? [Fraction] [_] true) + (defmethod g/freeze [Fraction] [x] + (let [n (numerator x) + d (denominator x)] + (if (g/one? d) + (g/freeze n) + `(~'/ + ~(g/freeze n) + ~(g/freeze d))))) (extend-type Fraction v/Numerical (numerical? [_] true) - v/Value - (zero? [c] (.equals c ZERO)) - (one? [c] (.equals c ONE)) - (identity? [c] (.equals c ONE)) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (freeze [x] (let [n (numerator x) - d (denominator x)] - (if (v/one? d) - (v/freeze n) - `(~'/ - ~(v/freeze n) - ~(v/freeze d))))) - (exact? [_] true) + v/IKind (kind [_] Fraction) IEquiv (-equiv [this other] (cond (ratio? other) (.equals this other) (v/integral? other) - (and (v/one? (denominator this)) + (and (g/one? (denominator this)) (v/= (numerator this) other)) ;; Enabling this would work, but would take us away from @@ -184,7 +174,7 @@ Object (toString [r] - (let [x (v/freeze r)] + (let [x (g/freeze r)] (if (number? x) x (let [[_ n d] x] @@ -194,7 +184,7 @@ (-pr-writer [x writer opts] (let [n (numerator x) d (denominator x)] - (if (v/one? d) + (if (g/one? d) (-pr-writer n writer opts) (write-all writer "#emmy/ratio \"" (str n) "/" (str d) @@ -229,7 +219,8 @@ (defmethod op [::v/integral Ratio] [a b] (f a b)))) :cljs - (do + (let [ZERO (Fraction. 0) + ONE (Fraction. 1)] (defn- pow [r m] (let [n (numerator r) d (denominator r)] @@ -255,6 +246,13 @@ (defmethod g/exact-divide [Fraction Fraction] [a b] (promote (.div ^js a b))) + (defmethod g/zero? [Fraction] [^Fraction c] (.equals c ZERO)) + (defmethod g/one? [Fraction] [^Fraction c] (.equals c ONE)) + (defmethod g/identity? [Fraction] [^Fraction c] (.equals c ONE)) + (defmethod g/zero-like [Fraction] [_] 0) + (defmethod g/one-like [Fraction] [_] 1) + (defmethod g/identity-like [Fraction] [_] 1) + (defmethod g/negate [Fraction] [a] (promote (.neg ^js a))) (defmethod g/negative? [Fraction] [a] (neg? (obj/get a "s"))) (defmethod g/infinite? [Fraction] [_] false) @@ -284,7 +282,7 @@ ;; Only integral ratios let us stay exact. If a ratio appears in the ;; exponent, convert the base to a number and call g/expt again. (defmethod g/expt [Fraction Fraction] [a b] - (if (v/one? (denominator b)) + (if (g/one? (denominator b)) (promote (.pow ^js a (numerator b))) (g/expt (.valueOf a) (.valueOf b)))) diff --git a/src/emmy/rational_function.cljc b/src/emmy/rational_function.cljc index 4001bc20..4d424b4c 100644 --- a/src/emmy/rational_function.cljc +++ b/src/emmy/rational_function.cljc @@ -43,20 +43,7 @@ (numerator [_] u) (denominator [_] v) - v/Value - (zero? [_] (v/zero? u)) - (one? [_] (and (v/one? u) (v/one? v))) - (identity? [_] (and (v/identity? u) (v/one? v))) - - (zero-like [_] (v/zero-like u)) - (one-like [_] (v/one-like u)) - (identity-like [_] - (RationalFunction. arity - (v/identity-like u) - (v/one-like v) - m)) - (exact? [_] false) - (freeze [_] (list '/ (v/freeze u) (v/freeze v))) + v/IKind (kind [_] ::rational-function) #?@(:clj @@ -283,8 +270,8 @@ NOTE: The behavior of this mildly-opinionated constructor is similar to [[polynomial/terms->polynomial]]" [arity u v] - (cond (v/zero? u) 0 - (v/one? v) u + (cond (g/zero? u) 0 + (g/one? v) u (or (p/polynomial? u) (p/polynomial? v)) @@ -323,7 +310,7 @@ The result can be either a [[RationalFunction]], [[polynomial/Polynomial]] or a `(g/div u v)`. See [[make-reduced]] for the details." [u v] - (when (v/zero? v) + (when (g/zero? v) (u/arithmetic-ex (str "Can't form rational function with zero denominator: " v))) (let [a (check-same-arity u v) @@ -337,12 +324,12 @@ (p/leading-coefficient v))) factor (g/negate factor)) - [u' v'] (if (v/one? factor) + [u' v'] (if (g/one? factor) [u v] [(g/mul factor u) (g/mul factor v)]) g (g/gcd u' v') - [u'' v''] (if (v/one? g) + [u'' v''] (if (g/one? g) [u' v'] [(p/evenly-divide u' g) (p/evenly-divide v' g)])] @@ -394,7 +381,7 @@ u-d (r/denominator u) v-n (r/numerator v) v-d (r/denominator v) - [n d] (if (and (v/one? u-d) (v/one? v-d)) + [n d] (if (and (g/one? u-d) (g/one? v-d)) [(poly-op u-n v-n) 1] (uv-op u-n u-d v-n v-d))] (make-reduced a n d))) @@ -418,10 +405,10 @@ creating large products." [u u' v v'] (letfn [(divide-through [n d] - (if (v/zero? n) + (if (g/zero? n) [0 1] (let [g (g/gcd d n)] - (if (v/one? g) + (if (g/one? g) [n d] [(p/evenly-divide n g) (p/evenly-divide d g)]))))] @@ -430,7 +417,7 @@ (let [n (p/add u v)] (divide-through n u')) (let [g (g/gcd u' v')] - (if (v/one? g) + (if (g/one? g) ;; Denominators are relatively prime: (divide-through (p/add (p/mul u v') @@ -457,7 +444,7 @@ "Returns the `[numerator, denominator]` pair resulting from rational function multiplication of `(/ u u')` and `(/ v v')`." [u u' v v'] - (if (or (v/zero? u) (v/zero? v)) + (if (or (g/zero? u) (g/zero? v)) [0 1] (let [d1 (g/gcd u v') d2 (g/gcd u' v) @@ -508,8 +495,8 @@ of [[RationalFunction]], [[polynomial/Polynomial]] or coefficients of neither type on either side." [r s] - (cond (v/zero? r) s - (v/zero? s) r + (cond (g/zero? r) s + (g/zero? s) r :else (binary-combine r s p/add uv:+))) (defn sub @@ -517,8 +504,8 @@ handling of [[RationalFunction]], [[polynomial/Polynomial]] or coefficients of neither type on either side." [r s] - (cond (v/zero? r) (negate s) - (v/zero? s) r + (cond (g/zero? r) (negate s) + (g/zero? s) r :else (binary-combine r s p/sub uv:-))) (defn mul @@ -526,10 +513,10 @@ handling of [[RationalFunction]], [[polynomial/Polynomial]] or coefficients of neither type on either side." [r s] - (cond (v/zero? r) r - (v/zero? s) s - (v/one? r) s - (v/one? s) r + (cond (g/zero? r) r + (g/zero? s) s + (g/one? r) s + (g/one? s) r :else (binary-combine r s p/mul uv:*))) (defn square @@ -580,7 +567,7 @@ (g/invert r) (let [u (bare-u r) v (bare-v r)] - (cond (v/zero? u) + (cond (g/zero? u) (u/arithmetic-ex "Can't form rational function with zero denominator.") @@ -691,7 +678,7 @@ between [[RationalFunction]], [[polynomial/Polynomial]] and coefficient instances." (assoc p/operator-table - '/ (ua/group g/div g/mul g/invert 1 v/zero?) + '/ (ua/group g/div g/mul g/invert 1 g/zero?) 'invert g/invert)) (def ^:no-doc operators-known @@ -845,6 +832,18 @@ (defbinary g/solve-linear (fn [l r] (div r l))) (defbinary g/gcd gcd) +(defmethod g/zero? [::rational-function] [^RationalFunction a] (g/zero? (.-u a))) +(defmethod g/one? [::rational-function] [^RationalFunction a] (and (g/one? (.-u a)) (g/one? (.-v a)))) +(defmethod g/identity? [::rational-function] [^RationalFunction a] (and (g/identity? (.-u a)) (g/one? (.-v a)))) +(defmethod g/zero-like [::rational-function] [^RationalFunction a] (g/zero-like (.-u a))) +(defmethod g/one-like [::rational-function] [^RationalFunction a] (g/one-like (.-u a))) +(defmethod g/identity-like [::rational-function] [^RationalFunction a] + (RationalFunction. (.-arity a) + (g/identity-like (.-u a)) + (g/one-like (.-v a)) + (.-m a))) +(defmethod g/freeze [::rational-function] [^RationalFunction a] (list '/ (g/freeze (.-u a)) (g/freeze (.-v a)))) + (defmethod g/negative? [::rational-function] [a] (negative? a)) (defmethod g/abs [::rational-function] [a] (abs a)) (defmethod g/negate [::rational-function] [a] (negate a)) diff --git a/src/emmy/series.cljc b/src/emmy/series.cljc index 1f3e089e..2d98fcd0 100644 --- a/src/emmy/series.cljc +++ b/src/emmy/series.cljc @@ -23,6 +23,7 @@ website](https://www.cs.dartmouth.edu/~doug/powser.html)." (:refer-clojure :exclude [identity]) (:require [emmy.differential :as d] + [emmy.expression] ;; for the effect of the defmethod of zero? for Literals [emmy.function :as f] [emmy.generic :as g] [emmy.series.impl :as i] @@ -42,150 +43,135 @@ (replace-tag [s old new] (fmap #(d/replace-tag % old new) s)) (extract-tangent [s tag] (fmap #(d/extract-tangent % tag) s)) - v/Value - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [_] s-zero) - (one-like [_] s-one) - - ;; This is suspect, since [[Series]], unlike [[PowerSeries]], are general - ;; infinite sequences and not necessarily interpreted as polynomials. This - ;; decision follows `scmutils` convention. - (identity-like [_] s-identity) - (exact? [_] false) - (freeze [_] - (let [prefix (v/freeze - (g/simplify (take 4 xs)))] - `(~'+ ~@prefix ~'...))) + v/IKind (kind [_] ::series) Object - (toString [S] (str (v/freeze S))) + (toString [S] (str (g/freeze S))) #?@ - (:clj - [IObj - (meta [_] m) - (withMeta [_ meta] (Series. xs meta)) + (:clj + [IObj + (meta [_] m) + (withMeta [_ meta] (Series. xs meta)) - Sequential + Sequential - Seqable - (seq [_] xs) + Seqable + (seq [_] xs) - IFn + IFn ;; Invoking a series uses `series-value` to generate a new series. - (invoke [_] - (Series. (series-value xs []) nil)) - (invoke [_ a] - (Series. (series-value xs [a]) nil)) - (invoke [_ a b] - (Series. (series-value xs [a b]) nil)) - (invoke [_ a b c] - (Series. (series-value xs [a b c]) nil)) - (invoke [_ a b c d] - (Series. (series-value xs [a b c d]) nil)) - (invoke [_ a b c d e] - (Series. (series-value xs [a b c d e]) nil)) - (invoke [_ a b c d e f] - (Series. (series-value xs [a b c d e f]) nil)) - (invoke [_ a b c d e f g] - (Series. (series-value xs [a b c d e f g]) nil)) - (invoke [_ a b c d e f g h] - (Series. (series-value xs [a b c d e f g h]) nil)) - (invoke [_ a b c d e f g h i] - (Series. (series-value xs [a b c d e f g h i]) nil)) - (invoke [_ a b c d e f g h i j] - (Series. (series-value xs [a b c d e f g h i j]) nil)) - (invoke [_ a b c d e f g h i j k] - (Series. (series-value xs [a b c d e f g h i j k]) nil)) - (invoke [_ a b c d e f g h i j k l] - (Series. (series-value xs [a b c d e f g h i j k l]) nil)) - (invoke [_ a b c d e f g h i j k l m] - (Series. (series-value xs [a b c d e f g h i j k l m]) nil)) - (invoke [_ a b c d e f g h i j k l m n] - (Series. (series-value xs [a b c d e f g h i j k l m n]) nil)) - (invoke [_ a b c d e f g h i j k l m n o] - (Series. (series-value xs [a b c d e f g h i j k l m n o]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p] - (Series. (series-value xs [a b c d e f g h i j k l m n o p]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p q] - (Series. (series-value xs [a b c d e f g h i j k l m n o p q]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p q r] - (Series. (series-value xs [a b c d e f g h i j k l m n o p q r]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p q r s] - (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p q r s t] - (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s t]) nil)) - (invoke [_ a b c d e f g h i j k l m n o p q r s t rest] - (Series. (series-value xs (concat [a b c d e f g h i j k l m n o p q r s t] rest)) nil)) - (applyTo [s xs] (AFn/applyToHelper s xs))] - - :cljs - [IMeta - (-meta [_] m) - - IWithMeta - (-with-meta [_ meta] (Series. xs meta)) - - ISequential - - ISeqable - (-seq [_] xs) - - IPrintWithWriter - (-pr-writer [x writer _] - (write-all writer - "#object[emmy.series.Series \"" - (.toString x) - "\"]")) - - IFn - (-invoke [_] + (invoke [_] (Series. (series-value xs []) nil)) - (-invoke [_ a] + (invoke [_ a] (Series. (series-value xs [a]) nil)) - (-invoke [_ a b] + (invoke [_ a b] (Series. (series-value xs [a b]) nil)) - (-invoke [_ a b c] + (invoke [_ a b c] (Series. (series-value xs [a b c]) nil)) - (-invoke [_ a b c d] + (invoke [_ a b c d] (Series. (series-value xs [a b c d]) nil)) - (-invoke [_ a b c d e] + (invoke [_ a b c d e] (Series. (series-value xs [a b c d e]) nil)) - (-invoke [_ a b c d e f] + (invoke [_ a b c d e f] (Series. (series-value xs [a b c d e f]) nil)) - (-invoke [_ a b c d e f g] + (invoke [_ a b c d e f g] (Series. (series-value xs [a b c d e f g]) nil)) - (-invoke [_ a b c d e f g h] + (invoke [_ a b c d e f g h] (Series. (series-value xs [a b c d e f g h]) nil)) - (-invoke [_ a b c d e f g h i] + (invoke [_ a b c d e f g h i] (Series. (series-value xs [a b c d e f g h i]) nil)) - (-invoke [_ a b c d e f g h i j] + (invoke [_ a b c d e f g h i j] (Series. (series-value xs [a b c d e f g h i j]) nil)) - (-invoke [_ a b c d e f g h i j k] + (invoke [_ a b c d e f g h i j k] (Series. (series-value xs [a b c d e f g h i j k]) nil)) - (-invoke [_ a b c d e f g h i j k l] + (invoke [_ a b c d e f g h i j k l] (Series. (series-value xs [a b c d e f g h i j k l]) nil)) - (-invoke [_ a b c d e f g h i j k l m] + (invoke [_ a b c d e f g h i j k l m] (Series. (series-value xs [a b c d e f g h i j k l m]) nil)) - (-invoke [_ a b c d e f g h i j k l m n] + (invoke [_ a b c d e f g h i j k l m n] (Series. (series-value xs [a b c d e f g h i j k l m n]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o] + (invoke [_ a b c d e f g h i j k l m n o] (Series. (series-value xs [a b c d e f g h i j k l m n o]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p] + (invoke [_ a b c d e f g h i j k l m n o p] (Series. (series-value xs [a b c d e f g h i j k l m n o p]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p q] + (invoke [_ a b c d e f g h i j k l m n o p q] (Series. (series-value xs [a b c d e f g h i j k l m n o p q]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p q r] + (invoke [_ a b c d e f g h i j k l m n o p q r] (Series. (series-value xs [a b c d e f g h i j k l m n o p q r]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p q r s] + (invoke [_ a b c d e f g h i j k l m n o p q r s] (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p q r s t] + (invoke [_ a b c d e f g h i j k l m n o p q r s t] (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s t]) nil)) - (-invoke [_ a b c d e f g h i j k l m n o p q r s t rest] - (Series. (series-value xs (concat [a b c d e f g h i j k l m n o p q r s t] rest)) nil))])) + (invoke [_ a b c d e f g h i j k l m n o p q r s t rest] + (Series. (series-value xs (concat [a b c d e f g h i j k l m n o p q r s t] rest)) nil)) + (applyTo [s xs] (AFn/applyToHelper s xs))] + + :cljs + [IMeta + (-meta [_] m) + + IWithMeta + (-with-meta [_ meta] (Series. xs meta)) + + ISequential + + ISeqable + (-seq [_] xs) + + IPrintWithWriter + (-pr-writer [x writer _] + (write-all writer + "#object[emmy.series.Series \"" + (.toString x) + "\"]")) + + IFn + (-invoke [_] + (Series. (series-value xs []) nil)) + (-invoke [_ a] + (Series. (series-value xs [a]) nil)) + (-invoke [_ a b] + (Series. (series-value xs [a b]) nil)) + (-invoke [_ a b c] + (Series. (series-value xs [a b c]) nil)) + (-invoke [_ a b c d] + (Series. (series-value xs [a b c d]) nil)) + (-invoke [_ a b c d e] + (Series. (series-value xs [a b c d e]) nil)) + (-invoke [_ a b c d e f] + (Series. (series-value xs [a b c d e f]) nil)) + (-invoke [_ a b c d e f g] + (Series. (series-value xs [a b c d e f g]) nil)) + (-invoke [_ a b c d e f g h] + (Series. (series-value xs [a b c d e f g h]) nil)) + (-invoke [_ a b c d e f g h i] + (Series. (series-value xs [a b c d e f g h i]) nil)) + (-invoke [_ a b c d e f g h i j] + (Series. (series-value xs [a b c d e f g h i j]) nil)) + (-invoke [_ a b c d e f g h i j k] + (Series. (series-value xs [a b c d e f g h i j k]) nil)) + (-invoke [_ a b c d e f g h i j k l] + (Series. (series-value xs [a b c d e f g h i j k l]) nil)) + (-invoke [_ a b c d e f g h i j k l m] + (Series. (series-value xs [a b c d e f g h i j k l m]) nil)) + (-invoke [_ a b c d e f g h i j k l m n] + (Series. (series-value xs [a b c d e f g h i j k l m n]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o] + (Series. (series-value xs [a b c d e f g h i j k l m n o]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p] + (Series. (series-value xs [a b c d e f g h i j k l m n o p]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p q] + (Series. (series-value xs [a b c d e f g h i j k l m n o p q]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p q r] + (Series. (series-value xs [a b c d e f g h i j k l m n o p q r]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s] + (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t] + (Series. (series-value xs [a b c d e f g h i j k l m n o p q r s t]) nil)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t rest] + (Series. (series-value xs (concat [a b c d e f g h i j k l m n o p q r s t] rest)) nil))])) ;; Unmap the auto-generated constructor and replace it with a better one. @@ -214,65 +200,48 @@ (replace-tag [s old new] (fmap #(d/replace-tag % old new) s)) (extract-tangent [s tag] (fmap #(d/extract-tangent % tag) s)) - v/Value - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [_] zero) - (one-like [_] one) - (identity-like [_] identity) - (exact? [_] false) - (freeze [_] - (let [prefix (->> (g/simplify (take 4 xs)) - (v/freeze) - (filter (complement v/zero?)) - (map-indexed - (fn [n a] - (if (v/one? a) - `(~'expt ~'_ ~n) - `(~'* ~a (~'expt ~'_ ~n))))))] - `(~'+ ~@prefix ~'...))) + v/IKind (kind [_] ::power-series) Object - (toString [S] (str (v/freeze S))) + (toString [S] (str (g/freeze S))) #?@ - (:clj - [IObj - (meta [_] m) - (withMeta [_ meta] (PowerSeries. xs meta)) + (:clj + [IObj + (meta [_] m) + (withMeta [_ meta] (PowerSeries. xs meta)) - Sequential + Sequential - Seqable - (seq [_] xs) + Seqable + (seq [_] xs) - IFn - (invoke [_ a] (Series. (power-series-value xs a) nil)) - (applyTo [s xs] (AFn/applyToHelper s xs))] + IFn + (invoke [_ a] (Series. (power-series-value xs a) nil)) + (applyTo [s xs] (AFn/applyToHelper s xs))] - :cljs - [IMeta - (-meta [_] m) + :cljs + [IMeta + (-meta [_] m) - IWithMeta - (-with-meta [_ meta] (PowerSeries. xs meta)) + IWithMeta + (-with-meta [_ meta] (PowerSeries. xs meta)) - ISequential + ISequential - ISeqable - (-seq [_] xs) + ISeqable + (-seq [_] xs) - IFn - (-invoke [_ a] (Series. (power-series-value xs a) nil)) + IFn + (-invoke [_ a] (Series. (power-series-value xs a) nil)) - IPrintWithWriter - (-pr-writer [this writer _] - (write-all writer - "#object[emmy.series.PowerSeries \"" - (.toString this) - "\"]"))])) + IPrintWithWriter + (-pr-writer [this writer _] + (write-all writer + "#object[emmy.series.PowerSeries \"" + (.toString this) + "\"]"))])) #?(:clj (defmethod print-method PowerSeries [^PowerSeries s ^java.io.Writer w] @@ -441,7 +410,7 @@ (defn- power-series-value "Evaluates the power series, and converts it back down to a normal series." [f x] - (let [one (v/one-like x) + (let [one (g/one-like x) powers (iterate #(g/* x %) one)] (map g/* f powers))) @@ -543,7 +512,7 @@ [s n] (if (<= n 1) s - (let [zero (v/zero-like (first s)) + (let [zero (g/zero-like (first s)) zeros (repeat (dec n) zero)] ((-make s) (->> (map cons s (repeat zeros)) @@ -715,6 +684,11 @@ (doseq [[ctor kind] [[->Series ::series] [->PowerSeries ::power-series]]] + + (defmethod g/zero? [kind] [_] false) + (defmethod g/one? [kind] [_] false) + (defmethod g/identity? [kind] [_] false) + (defmethod g/add [kind kind] [s t] (ctor (i/seq:+ (seq s) (seq t)) nil)) @@ -883,3 +857,29 @@ (.-m s)) (u/illegal (str "Cannot yet take partial derivatives of a power series: " s selectors)))) + +(defmethod g/zero-like [::power-series] [_] zero) +(defmethod g/one-like [::power-series] [_] one) +(defmethod g/identity-like [::power-series] [_] identity) +(defmethod g/zero-like [::series] [_] s-zero) +(defmethod g/one-like [::series] [_] s-one) +;; This is suspect, since [[Series]], unlike [[PowerSeries]], are general +;; infinite sequences and not necessarily interpreted as polynomials. This +;; decision follows `scmutils` convention. +(defmethod g/identity-like [::series] [_] s-identity) +(defmethod g/exact? [::series] [_] false) +(defmethod g/exact? [::power-series] [_] false) +(defmethod g/freeze [::power-series] [^PowerSeries s] + (let [prefix (->> (g/simplify (take 4 (.-xs s))) + (g/freeze) + (filter (complement g/zero?)) + (map-indexed + (fn [n a] + (if (g/one? a) + `(~'expt ~'_ ~n) + `(~'* ~a (~'expt ~'_ ~n))))))] + `(~'+ ~@prefix ~'...))) +(defmethod g/freeze [::series] [^Series s] + (let [prefix (g/freeze + (g/simplify (take 4 (.-xs s))))] + `(~'+ ~@prefix ~'...))) diff --git a/src/emmy/series/impl.cljc b/src/emmy/series/impl.cljc index ef716686..2f946f51 100644 --- a/src/emmy/series/impl.cljc +++ b/src/emmy/series/impl.cljc @@ -10,7 +10,6 @@ [emmy.numbers] [emmy.special.factorial :as sf] [emmy.util :as u] - [emmy.value :as v] [mentat.clerk-utils :refer [->clerk-only]])) ;; # Power Series @@ -42,7 +41,7 @@ ;; A 'series' is an infinite sequence of numbers, represented by Clojure's lazy ;; sequence. First, a function `->series` that takes some existing sequence, ;; finite or infinite, and coerces it to an infinite seq by concatenating it -;; with an infinite sequence of zeros. (We use `v/zero-like` so that everything +;; with an infinite sequence of zeros. (We use `g/zero-like` so that everything ;; plays nicely with generic arithmetic.) (defn ->series @@ -50,7 +49,7 @@ remainder of the series will be filled with the zero-value corresponding to the first of the given values." [xs] - (lazy-cat xs (repeat (v/zero-like (first xs))))) + (lazy-cat xs (repeat (g/zero-like (first xs))))) ;; This works as expected: @@ -166,14 +165,14 @@ (defn seq:* [f g] (letfn [(step [f] (lazy-seq - (if (v/zero? (first f)) + (if (g/zero? (first f)) (cons (first f) (step (rest f))) (let [f*g (g/mul (first f) (first g)) f*G1 (c*seq (first f) (rest g)) F1*G (step (rest f))] (cons f*g (seq:+ f*G1 F1*G))))))] (lazy-seq - (if (v/zero? (first g)) + (if (g/zero? (first g)) (cons (first g) (seq:* f (rest g))) (step f))))) @@ -220,13 +219,13 @@ (lazy-seq (let [f0 (first f) fs (rest f) g0 (first g) gs (rest g)] - (cond (and (v/zero? f0) (v/zero? g0)) + (cond (and (g/zero? f0) (g/zero? g0)) (div fs gs) - (v/zero? f0) + (g/zero? f0) (cons f0 (div fs g)) - (v/zero? g0) + (g/zero? g0) (u/arithmetic-ex "ERROR: denominator has a zero constant term") :else (let [q (g/div f0 g0)] @@ -333,7 +332,7 @@ (lazy-seq ;; TODO I don't understand why we get a StackOverflow if I move ;; this assertion out of the `letfn`. - (assert (v/zero? (first g))) + (assert (g/zero? (first g))) (let [[f0 & fs] f gs (rest g) tail (seq:* gs (step fs))] @@ -369,7 +368,7 @@ ;; $R$ thanks to the stream-based approach: (defn revert [f] - {:pre [(v/zero? (first f))]} + {:pre [(g/zero? (first f))]} (letfn [(step [f] (lazy-seq (let [F1 (rest f) @@ -471,8 +470,8 @@ ;; Here it is in Clojure: (defn sqrt [[f1 & [f2 & fs] :as f]] - (if (and (v/zero? f1) - (v/zero? f2)) + (if (and (g/zero? f1) + (g/zero? f2)) (cons f1 (sqrt fs)) (let [const (g/sqrt f1) step (fn step [g] diff --git a/src/emmy/simplify.cljc b/src/emmy/simplify.cljc index 82fdf380..eed14701 100644 --- a/src/emmy/simplify.cljc +++ b/src/emmy/simplify.cljc @@ -3,11 +3,11 @@ (ns emmy.simplify (:require [emmy.expression :as x] [emmy.expression.analyze :as a] + [emmy.generic :as g] [emmy.polynomial :as poly] [emmy.polynomial.factor :as factor] [emmy.rational-function :as rf] [emmy.simplify.rules :as rules] - [emmy.value :as v] [taoensso.timbre :as log]) #?(:clj (:import (java.util.concurrent TimeoutException)))) @@ -74,7 +74,7 @@ expr (let [canonicalized-expr (canonicalize new-expr)] (cond (= canonicalized-expr expr) expr - (v/zero? + (g/numeric-zero? (*poly-simplify* (list '- expr canonicalized-expr))) canonicalized-expr diff --git a/src/emmy/simplify/rules.cljc b/src/emmy/simplify/rules.cljc index 7283943b..be93d463 100644 --- a/src/emmy/simplify/rules.cljc +++ b/src/emmy/simplify/rules.cljc @@ -181,33 +181,33 @@ real part, false otherwise." [z] (and (c/complex? z) - (not (v/zero? z)) - (v/zero? (g/real-part z)))) + (not (g/zero? z)) + (g/zero? (g/real-part z)))) (defn- complex-number? "Returns true if `z` is a complex number with nonzero real AND imaginary parts, false otherwise." [z] (and (c/complex? z) - (not (v/zero? (g/real-part z))) - (not (v/zero? (g/imag-part z))))) + (not (g/zero? (g/real-part z))) + (not (g/zero? (g/imag-part z))))) (defn- imaginary-integer? "Returns true if `z` is an imaginary number with an integral (or VERY close to integral) imaginary part, false otherwise." [z] (and (imaginary-number? z) - (v/almost-integral? + (g/almost-integral? (g/imag-part z)))) (defn not-integral? [x] (not (v/integral? x))) (defn even? [x] - (v/zero? (g/modulo x 2))) + (g/zero? (g/modulo x 2))) (defn odd? [x] - (v/one? (g/modulo x 2))) + (g/one? (g/modulo x 2))) (defn- even-integer? [x] (and (v/integral? x) @@ -758,10 +758,10 @@ (fn [m] (let [s1 (simplify (r/template m (* ??f1 ??f2))) s2 (simplify (r/template m (* ??f3 ??f4)))] - (when (v/exact-zero? + (when (g/exact-zero? (simplify (list '- s1 s2))) - {'??s1 s1}))) - (+ (* (log (* ?x ?y)) ??s1) + {'?s1 s1}))) + (+ (* (log (* ?x ?y)) ?s1) ??x1 ??x2 ??x3)))) (def log-expand @@ -888,7 +888,7 @@ (v/number? xs)) (sym:atan ys xs) (let [s (simplify (list 'gcd ys xs))] - (when-not (v/one? s) + (when-not (g/one? s) (and (ul/assume! (list 'positive? s) 'aggressive-atan-2) @@ -1070,7 +1070,7 @@ (def expand-multiangle (letfn [(exact-integer>3? [x] (and (v/integral? x) - (v/exact? x) + (g/exact? x) (> x 3)))] (rule-simplifier (ruleset @@ -1159,7 +1159,7 @@ (def contract-expt-trig (letfn [(exact-integer>1? [x] (and (v/integral? x) - (v/exact? x) + (g/exact? x) (> x 1)))] (rule-simplifier (ruleset @@ -1238,7 +1238,7 @@ (def split-high-degree-sincos (letfn [(remaining [{n '?n :as m}] (let [n-2 (g/- n 2)] - (if (v/one? n-2) + (if (g/one? n-2) (r/template m (?op ?x)) (r/template m (expt (?op ?x) ~n-2)))))] @@ -1267,7 +1267,7 @@ (letfn [(pred [m] (let [s1 (simplify (r/template m (* ??f1 ??f2))) s2 (simplify (r/template m (* ??f3 ??f4)))] - (when (v/exact-zero? + (when (g/exact-zero? (simplify (list '- s1 s2))) {'?s1 s1})))] (rule-simplifier @@ -1289,7 +1289,7 @@ (flush-obvious-ones simplify))) (defn sincos-random [simplify] - (let [simplifies-to-zero? (comp v/zero? simplify) + (let [simplifies-to-zero? (comp g/numeric-zero? simplify) ops #{'cos 'sin} flip {'cos 'sin, 'sin 'cos}] (rule-simplifier @@ -1352,7 +1352,7 @@ (def exp->sincos (letfn [(positive? [x] (not (or (g/negative? x) - (v/zero? x)))) + (g/zero? x)))) (pos-pred [m] (let [im (g/imag-part (m '?c1))] @@ -1438,7 +1438,7 @@ (def exp-expand (let [exact-integer? (fn [x] (and (v/integral? x) - (v/exact? x)))] + (g/exact? x)))] (rule-simplifier (ruleset (exp (- ?x1)) => (/ 1 (exp ?x1)) diff --git a/src/emmy/special/elliptic.cljc b/src/emmy/special/elliptic.cljc index cf75a40d..9abec454 100644 --- a/src/emmy/special/elliptic.cljc +++ b/src/emmy/special/elliptic.cljc @@ -6,8 +6,7 @@ symmetric form](https://en.wikipedia.org/wiki/Carlson_symmetric_form), as well as the [Jacobi elliptic functions](https://en.wikipedia.org/wiki/Jacobi_elliptic_functions)." - (:require [emmy.util :as u] - [emmy.value :as v])) + (:require [emmy.util :as u])) ;; ## Carlson symmetric forms of elliptic integrals @@ -338,7 +337,7 @@ c k d 0.0 powers-2 1.0] - (if (< (Math/abs c) v/machine-epsilon) + (if (< (Math/abs c) u/machine-epsilon) (let [first-elliptic-integral (/ (/ Math/PI 2) a)] (continue first-elliptic-integral (* first-elliptic-integral @@ -442,7 +441,7 @@ ([u k] (jacobi-elliptic-functions u k vector)) ([u k cont] - (let [eps v/sqrt-machine-epsilon + (let [eps u/sqrt-machine-epsilon emc (- 1. (* k k))] (if (= emc 0.0) (let [cn (/ 1.0 (Math/cosh u))] diff --git a/src/emmy/special/factorial.cljc b/src/emmy/special/factorial.cljc index 1b3a98cc..f43f66bf 100644 --- a/src/emmy/special/factorial.cljc +++ b/src/emmy/special/factorial.cljc @@ -33,7 +33,7 @@ #?(:clj (apply *' elems) :cljs - (if (<= n 20) + (if (< n 19) (apply * elems) (transduce (map u/bigint) g/* elems))))) @@ -79,7 +79,7 @@ (cond (zero? n) 1 (neg? n) (let [denom (rising-factorial (g/add x 1) (g/- n))] - (if (v/zero? denom) + (if (g/zero? denom) ##Inf (g/invert denom))) @@ -100,7 +100,7 @@ (cond (zero? n) 1 (neg? n) (let [denom (rising-factorial (inc x) (- n))] - (if (v/zero? denom) + (if (g/zero? denom) ##Inf (g// 1 denom))) @@ -123,7 +123,7 @@ denominator evaluates to 0.") (def pochhammer - "Alias for [[falling-factorial]]." + "Alias for [[rising-factorial]]." rising-factorial) (defmethod rising-factorial :default [x n] @@ -131,7 +131,7 @@ (cond (zero? n) 1 (neg? n) (let [denom (falling-factorial (g/sub x 1) (g/- n))] - (if (v/zero? denom) + (if (g/zero? denom) ##Inf (g/invert denom))) @@ -146,7 +146,7 @@ (cond (zero? n) 1 (neg? n) (let [denom (falling-factorial (dec x) (- n))] - (if (v/zero? denom) + (if (g/zero? denom) ##Inf (g// 1 denom))) diff --git a/src/emmy/structure.cljc b/src/emmy/structure.cljc index ecdcdea9..189735c0 100644 --- a/src/emmy/structure.cljc +++ b/src/emmy/structure.cljc @@ -74,15 +74,7 @@ (declare s:= mapr) (deftype Structure [orientation v m] - v/Value - (zero? [_] (every? v/zero? v)) - (one? [_] false) - (identity? [_] false) - (zero-like [_] (Structure. orientation (v/zero-like v) m)) - (one-like [_] 1) - (identity-like [_] 1) - (exact? [_] (every? v/exact? v)) - (freeze [_] `(~(orientation orientation->symbol) ~@(map v/freeze v))) + v/IKind (kind [_] orientation) f/IArity @@ -396,6 +388,16 @@ (prefer-method v/= [::up ::down] [v/seqtype ::down]) (prefer-method v/= [::down ::up] [::down v/seqtype]) +(doseq [kind [::up ::down]] + (defmethod g/zero? [kind] [s] (every? g/zero? s)) + (defmethod g/one? [kind] [_] false) + (defmethod g/identity? [kind] [_] false) + (defmethod g/zero-like [kind] [^Structure s] (Structure. (.-orientation s) (g/zero-like (.-v s)) (.-m s))) + (defmethod g/one-like [kind] [_] 1) + (defmethod g/identity-like [kind] [_] 1) + (defmethod g/exact? [kind] [^Structure s] (every? g/exact? (.-v s))) + (defmethod g/freeze [kind] [^Structure s] `(~((.-orientation s) orientation->symbol) ~@(map g/freeze (.-v s))))) + (defn- s:= "Returns true if the supplied structure `this` is equal to the argument on the right, false otherwise. @@ -853,7 +855,7 @@ (defn compatible-zero "Returns a structure compatible for multiplication with `s` down to 0." [s] - (v/zero-like + (g/zero-like (transpose s))) (def dual-zero @@ -956,8 +958,8 @@ (defn- expt "Raise the structure `s` to the nth power." [s n] - (let [one (v/one-like n)] - (cond (v/one? n) s + (let [one (g/one-like n)] + (cond (g/one? n) s (> n one) (g/* s (g/expt s (g/- n one))) :else (u/arithmetic-ex (str "Cannot: " `(expt ~s ~n)))))) diff --git a/src/emmy/util.cljc b/src/emmy/util.cljc index 3a0f110f..76b7320d 100644 --- a/src/emmy/util.cljc +++ b/src/emmy/util.cljc @@ -142,6 +142,7 @@ "Like `defmacro` but when emitting cljs, emits a function with &env and &form prepended to arglists and :sci/macro metadata, so that the macro can be imported into sci using copy-var." + {:clj-kondo/lint-as 'clojure.core/defn} [name & body] (if (:ns &env) (let [[doc body] (if (string? (first body)) @@ -168,3 +169,12 @@ ns-sym sci-ns (merge {:copy-meta [:doc :arglists :macro :sci/macro :imported-from]} opts)))) + +(def machine-epsilon + (loop [e 1.0] + (if (= 1.0 (+ e 1.0)) + (* e 2.0) + (recur (/ e 2.0))))) + +(def sqrt-machine-epsilon + (Math/sqrt machine-epsilon)) diff --git a/src/emmy/util/stream.cljc b/src/emmy/util/stream.cljc index 01754189..3e751a35 100644 --- a/src/emmy/util/stream.cljc +++ b/src/emmy/util/stream.cljc @@ -5,7 +5,7 @@ working with strict and lazy sequences." (:require [clojure.pprint :as pp] [emmy.generic :as g] - [emmy.value :as v])) + [emmy.util :as u])) (defn seq-print "Realizes, simplifies and prints `n` elements from the supplied sequence `xs`." @@ -115,7 +115,7 @@ tolerance convergence-fn] :or {minterms 2 - tolerance v/sqrt-machine-epsilon + tolerance u/sqrt-machine-epsilon convergence-fn (close-enuf? tolerance)}}] (if (empty? xs) {:converged? false diff --git a/src/emmy/value.cljc b/src/emmy/value.cljc index 32e00053..4c02079b 100644 --- a/src/emmy/value.cljc +++ b/src/emmy/value.cljc @@ -11,15 +11,15 @@ (:refer-clojure :exclude [zero? number? = compare]) (:require #?@(:cljs [["complex.js" :as Complex] ["fraction.js/bigfraction.js" :as Fraction] + [emmy.util :as u] [goog.array :as garray] [goog.object :as gobject] [goog.math.Long] [goog.math.Integer]]) - [clojure.core :as core] - [emmy.util :as u]) + [clojure.core :as core]) #?(:clj (:import - (clojure.lang BigInt Sequential Var) + (clojure.lang BigInt Sequential) (org.apache.commons.math3.complex Complex)))) (defprotocol Numerical @@ -29,21 +29,7 @@ #?(:clj Object :cljs default) (numerical? [_] false)) -(defprotocol Value - (^boolean zero? [this]) - (^boolean one? [this]) - (^boolean identity? [this]) - (zero-like [this]) - (one-like [this]) - (identity-like [this]) - (^boolean exact? [this] "Entries that are exact are available for `gcd`, among - other operations.") - (freeze [this] - "Freezing an expression means removing wrappers and other metadata from - subexpressions, so that the result is basically a pure S-expression with the - same structure as the input. Doing this will rob an expression of useful - information for further computation; so this is intended to be done just - before simplification and printing, to simplify those processes.") +(defprotocol IKind (kind [this])) (defn argument-kind [& args] @@ -114,12 +100,6 @@ (instance? goog.math.Long x) (instance? Complex x)))) -(defn numeric-zero? - "Returns `true` if `x` is both a [[number?]] and [[zero?]], false otherwise." - [x] - (and (number? x) - (zero? x))) - ;; `::scalar` is a thing that symbolic expressions AND actual numbers both ;; derive from. @@ -162,98 +142,33 @@ java.lang.Float (numerical? [_] true)])) -(extend-protocol Value +(extend-protocol IKind #?(:clj Number :cljs number) - (zero? [x] (core/zero? x)) - (one? [x] (== 1 x)) - (identity? [x] (== 1 x)) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (freeze [x] x) - (exact? [x] #?(:clj (or (integer? x) (ratio? x)) - :cljs (integer? x))) (kind [x] #?(:clj (type x) - :cljs (if (exact? x) + :cljs (if (and (. js/Number isInteger x) + (< (Math/abs x) (.-MAX_SAFE_INTEGER js/Number))) ::native-integral ::floating-point))) #?(:clj Boolean :cljs boolean) - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [_] 0) - (one-like [_] 1) - (identity-like [_] 1) - (freeze [x] x) - (exact? [_] false) (kind [x] (type x)) #?@(:clj [java.lang.Double - (zero? [x] (core/zero? x)) - (one? [x] (== 1 x)) - (identity? [x] (== 1 x)) - (zero-like [_] 0.0) - (one-like [_] 1.0) - (identity-like [_] 1.0) - (freeze [x] x) - (exact? [_] false) (kind [x] (type x)) java.lang.Float - (zero? [x] (core/zero? x)) - (one? [x] (== 1 x)) - (identity? [x] (== 1 x)) - (zero-like [_] 0.0) - (one-like [_] 1.0) - (identity-like [_] 1.0) - (freeze [x] x) - (exact? [_] false) (kind [x] (type x))]) nil - (zero? [_] true) - (one? [_] false) - (identity? [_] false) - (zero-like [_] (u/unsupported "nil doesn't support zero-like.")) - (one-like [_] (u/unsupported "nil doesn't support one-like.")) - (identity-like [_] (u/unsupported "nil doesn't support identity-like.")) - (freeze [_] nil) - (exact? [_] false) (kind [_] nil) - Var - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [v] (u/unsupported (str "zero-like: " v))) - (one-like [v] (u/unsupported (str "one-like: " v))) - (identity-like [v] (u/unsupported (str "identity-like: " v))) - (freeze [v] (:name (meta v))) - (exact? [_] false) - (kind [v] (type v)) +;; Var +;; (kind [v] (type v)) #?(:clj Object :cljs default) - (zero? [_] false) - (one? [_] false) - (identity? [_] false) - (zero-like [o] (u/unsupported (str "zero-like: " o))) - (one-like [o] (u/unsupported (str "one-like: " o))) - (identity-like [o] (u/unsupported (str "identity-like: " o))) - (exact? [_] false) - (freeze [o] (if (sequential? o) - (map freeze o) - (get @object-name-map o o))) (kind [o] (:type o (type o)))) -(defn exact-zero? - "Returns true if the supplied argument is an exact numerical zero, false - otherwise." - [n] - (and (number? n) - (exact? n) - (zero? n))) ;; Override equiv for numbers. (defmulti = argument-kind) @@ -331,7 +246,7 @@ IPrintWithWriter (-pr-writer [x writer _] - (let [rep (if (<= x (.-MAX_SAFE_INTEGER js/Number)) + (let [rep (if (< (if (< x 0) (- x) x) (.-MAX_SAFE_INTEGER js/Number)) (str x) (str "\"" x "\""))] (write-all writer "#emmy/bigint " rep))))) @@ -387,9 +302,7 @@ #?(:cljs ;; ClojureScript-specific implementations of Value. - (let [big-zero (js/BigInt 0) - big-one (js/BigInt 1)] - + (do (extend-protocol Numerical js/BigInt (numerical? [_] true) @@ -400,43 +313,14 @@ goog.math.Long (numerical? [_] true)) - (extend-protocol Value + (extend-protocol IKind js/BigInt - (zero? [x] (coercive-= big-zero x)) - (one? [x] (coercive-= big-one x)) - (identity? [x] (coercive-= big-one x)) - (zero-like [_] big-zero) - (one-like [_] big-one) - (identity-like [_] big-one) - (freeze [x] - ;; Bigint freezes into a non-bigint if it can be represented as a - ;; number; otherwise, it turns into its own literal. - (if (<= x (.-MAX_SAFE_INTEGER js/Number)) - (js/Number x) - x)) - (exact? [_] true) (kind [_] js/BigInt) goog.math.Integer - (zero? [x] (.isZero x)) - (one? [x] (core/= (.-ONE goog.math.Integer) x)) - (identity? [x] (core/= (.-ONE goog.math.Integer) x)) - (zero-like [_] (.-ZERO goog.math.Integer)) - (one-like [_] (.-ONE goog.math.Integer)) - (identity-like [_] (.-ONE goog.math.Integer)) - (freeze [x] x) - (exact? [_] true) (kind [_] goog.math.Integer) goog.math.Long - (zero? [x] (.isZero x)) - (one? [x] (core/= (goog.math.Long/getOne) x)) - (identity? [x] (core/= (goog.math.Long/getOne) x)) - (zero-like [_] (goog.math.Long/getZero)) - (one-like [_] (goog.math.Long/getOne)) - (identity-like [_] (goog.math.Long/getOne)) - (freeze [x] x) - (exact? [_] true) (kind [_] goog.math.Long)))) #?(:cljs @@ -538,41 +422,12 @@ [o->syms] (swap! object-name-map into o->syms)) -(def machine-epsilon - (loop [e 1.0] - (if (core/= 1.0 (+ e 1.0)) - (* e 2.0) - (recur (/ e 2.0))))) - -(def sqrt-machine-epsilon - (Math/sqrt machine-epsilon)) - (defn within "Returns a function that tests whether two values are within ε of each other." [^double ε] (fn [^double x ^double y] (< (Math/abs (- x y)) ε))) -(def ^:no-doc relative-integer-tolerance (* 100 machine-epsilon)) -(def ^:no-doc absolute-integer-tolerance 1e-20) - -(defn almost-integral? - "Returns true if `x` is either: - - - [[integral?]], - - a floating point number either < [[absolute-integer-tolerance]] (if near - zero) or within [[relative-integer-tolerance]] of the closest integer, - - false otherwise." - [x] - (or (integral? x) - (and (float? x) - (let [x (double x) - z (Math/round x)] - (if (zero? z) - (< (Math/abs x) absolute-integer-tolerance) - (< (Math/abs (/ (- x z) z)) relative-integer-tolerance)))))) - (def twopi (* 2 Math/PI)) (defn principal-value [cuthigh] diff --git a/test/emmy/abstract/function_test.cljc b/test/emmy/abstract/function_test.cljc index 832571c3..448b36df 100644 --- a/test/emmy/abstract/function_test.cljc +++ b/test/emmy/abstract/function_test.cljc @@ -10,6 +10,7 @@ [emmy.generators :as sg] [emmy.generic :as g] [emmy.matrix :as m] + [emmy.numbers] ; for def of g/zero? [emmy.series :as series] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.structure :as s :refer [literal-up @@ -20,11 +21,11 @@ (use-fixtures :each hermetic-simplify-fixture) (deftest value-protocol-tests - (testing "v/zero? returns false for fns" - (is (not (v/zero? (af/literal-function 'f))))) + (testing "g/zero? returns false for fns" + (is (not (g/zero? (af/literal-function 'f))))) - (testing "v/one? returns false for fns" - (is (not (v/one? (af/literal-function 'f))))) + (testing "g/one? returns false for fns" + (is (not (g/one? (af/literal-function 'f))))) (testing "v/numerical? returns false for fns" (is (not (v/numerical? (af/literal-function 'f))))) @@ -32,24 +33,24 @@ (let [f (af/literal-function 'f)] (checking "zero-like, one-like passes through for literal fns" 100 [n sg/real] - (is (v/= (v/zero-like n) - ((v/zero-like f) n))) - (is (v/= (v/one-like n) - ((v/one-like f) n))))) + (is (v/= (g/zero-like n) + ((g/zero-like f) n))) + (is (v/= (g/one-like n) + ((g/one-like f) n))))) (let [f (af/literal-function 'f)] (checking "identity-like returns the identity fn" 100 [n sg/real] - (is (= n ((v/identity-like f) n))))) + (is (= n ((g/identity-like f) n))))) (checking "exact? mirrors input" 100 [n gen/symbol] - (let [f (v/exact? (af/literal-function 'f))] + (let [f (g/exact? (af/literal-function 'f))] (is (not (f n))))) - (checking "v/freeze" 100 [fsym gen/symbol + (checking "g/freeze" 100 [fsym gen/symbol n sg/real] - (is (= (list fsym (v/freeze n)) - (v/freeze ((af/literal-function fsym) n))))) + (is (= (list fsym (g/freeze n)) + (g/freeze ((af/literal-function fsym) n))))) (testing "v/kind returns ::v/function" (let [kind (v/kind (af/literal-function 'f))] @@ -65,11 +66,11 @@ xyt2 (g/square xyt) Uxyt2 (U xyt2)] (is (= '(up x y) - (v/freeze + (g/freeze (g/simplify xy)))) (is (= '(up (x t) (y t)) - (v/freeze + (g/freeze (g/simplify xyt)))) (is (= '(+ (expt (x t) 2) (expt (y t) 2)) (g/simplify xyt2))) @@ -79,7 +80,7 @@ (is (= '(matrix-by-rows [(f x) (g x)] [(h x) (k x)]) - (v/freeze + (g/freeze (g/simplify ((m/by-rows (map af/literal-function '[f g]) (map af/literal-function '[h k])) 'x))))) @@ -88,7 +89,7 @@ (is (= '(matrix-by-rows [(f x y) (g x y)] [(h x y) (k x y)]) - (v/freeze + (g/freeze (g/simplify ((m/by-rows [(R2f 'f) (R2f 'g)] [(R2f 'h) (R2f 'k)]) 'x 'y)))))))) @@ -126,31 +127,31 @@ (deftest trig-tests (testing "tan, sin, cos" (let [f (g/- g/tan (g/div g/sin g/cos))] - (is (v/zero? + (is (g/zero? (g/simplify (f 'x)))))) (testing "cot" (let [f (g/- g/cot (g/invert g/tan))] - (is (v/zero? (g/simplify (f 'x)))))) + (is (g/zero? (g/simplify (f 'x)))))) (testing "tanh" (let [f (g/- (g/div g/sinh g/cosh) g/tanh)] - (is (v/zero? + (is (g/zero? (g/simplify (f 'x)))))) (testing "sec" (let [f (g/- (g/invert g/cos) g/sec)] - (is (v/zero? + (is (g/zero? (g/simplify (f 'x)))))) (testing "csc" (let [f (g/- (g/invert g/sin) g/csc)] - (is (v/zero? + (is (g/zero? (g/simplify (f 'x)))))) (testing "sech" (let [f (g/- (g/invert g/cosh) g/sech)] - (is (v/zero? + (is (g/zero? (g/simplify (f 'x))))))) (defn transpose-defining-relation @@ -182,7 +183,7 @@ a (literal-up 'a 2) g (fn [w] (g/* (literal-down 'g 3) w)) s (up 'x 'y)] - (is (v/zero? (transpose-defining-relation (DTf s) g a)) + (is (g/zero? (transpose-defining-relation (DTf s) g a)) "This function, whatever it is (see scmutils function.scm) satisfies the transpose defining relation.") @@ -211,36 +212,36 @@ k (af/literal-function 'k 0 (up 0 (up 0 0) (down 0 0))) q (af/literal-function 'q 0 (down (up 0 1) (up 2 3)))] (is (= '(up (h↑0 t) (h↑1 t) (h↑2 t)) - (v/freeze + (g/freeze (g/simplify (h 't))))) (is (= '(up (k↑0 t) (up (k↑1↑0 t) (k↑1↑1 t)) (down (k↑2_0 t) (k↑2_1 t))) - (v/freeze + (g/freeze (g/simplify (k 't))))) (is (= '(down (up (q_0↑0 t) (q_0↑1 t)) (up (q_1↑0 t) (q_1↑1 t))) - (v/freeze + (g/freeze (g/simplify (q 't))))) (is (= '(down (up 0 0) (up 0 0)) - (v/freeze - (g/simplify ((v/zero-like q) 't))))))) + (g/freeze + (g/simplify ((g/zero-like q) 't))))))) (testing "R^n -> structured range" (let [h (af/literal-function 'h [0 1] 0)] (is (= '(h x y) (g/simplify (h 'x 'y))))) (let [m (af/literal-function 'm [0 1] (up 1 2 3))] (is (= '(up (m↑0 x y) (m↑1 x y) (m↑2 x y)) - (v/freeze + (g/freeze (g/simplify (m 'x 'y)))))) (let [z (af/literal-function 'm [0 1] (up (down 1 2) (down 3 4)))] (is (= '(up (down (m↑0_0 x y) (m↑0_1 x y)) (down (m↑1_0 x y) (m↑1_1 x y))) - (v/freeze + (g/freeze (g/simplify (z 'x 'y)))))) (let [g (af/literal-function 'm [0 1 2] (down (down 1 2 3) @@ -250,7 +251,7 @@ (down (m_0_0 x y z) (m_0_1 x y z) (m_0_2 x y z)) (down (m_1_0 x y z) (m_1_1 x y z) (m_1_2 x y z)) (down (m_2_0 x y z) (m_2_1 x y z) (m_2_2 x y z))) - (v/freeze + (g/freeze (g/simplify (g 'x 'y 'z))))))) (testing "R -> Rⁿ" diff --git a/test/emmy/abstract/number_test.cljc b/test/emmy/abstract/number_test.cljc index 0b007800..92ce840d 100644 --- a/test/emmy/abstract/number_test.cljc +++ b/test/emmy/abstract/number_test.cljc @@ -25,40 +25,40 @@ gen/symbol])) (deftest value-protocol-tests - (checking "v/zero? returns true for wrapped zero" - 100 [n (gen/one-of [sg/real (gen/fmap v/zero-like sg/real)])] - (if (v/zero? n) - (is (v/zero? (an/literal-number n))) - (is (not (v/zero? (an/literal-number n)))))) - - (checking "v/one? returns true for wrapped zero" - 100 [n (gen/one-of [sg/real (gen/fmap v/one-like sg/real)])] - (if (v/one? n) - (is (v/one? (an/literal-number n))) - (is (not (v/one? (an/literal-number n)))))) - - (checking "v/identity? returns true for wrapped zero" - 100 [n (gen/one-of [sg/real (gen/fmap v/identity-like sg/real)])] - (if (v/identity? n) - (is (v/identity? (an/literal-number n))) - (is (not (v/identity? (an/literal-number n)))))) + (checking "g/zero? returns true for wrapped zero" + 100 [n (gen/one-of [sg/real (gen/fmap g/zero-like sg/real)])] + (if (g/zero? n) + (is (g/zero? (an/literal-number n))) + (is (not (g/zero? (an/literal-number n)))))) + + (checking "g/one? returns true for wrapped zero" + 100 [n (gen/one-of [sg/real (gen/fmap g/one-like sg/real)])] + (if (g/one? n) + (is (g/one? (an/literal-number n))) + (is (not (g/one? (an/literal-number n)))))) + + (checking "g/identity? returns true for wrapped zero" + 100 [n (gen/one-of [sg/real (gen/fmap g/identity-like sg/real)])] + (if (g/identity? n) + (is (g/identity? (an/literal-number n))) + (is (not (g/identity? (an/literal-number n)))))) (checking "v/{zero?,one?,identity?} etc match v/{zero,one,identity}-like" 100 [n gen-literal] - (is (v/zero? (v/zero-like n))) - (is (v/one? (v/one-like n))) - (is (v/identity? (v/identity-like n)))) + (is (g/zero? (g/zero-like n))) + (is (g/one? (g/one-like n))) + (is (g/identity? (g/identity-like n)))) (checking "v/numerical? returns true for all literal-number instances" 100 [n gen-literal] (is (v/numerical? n))) (checking "exact? mirrors input" 100 [n gen-literal-element] - (if (v/exact? n) - (is (v/exact? (an/literal-number n))) - (is (not (v/exact? (an/literal-number n)))))) + (if (g/exact? n) + (is (g/exact? (an/literal-number n))) + (is (not (g/exact? (an/literal-number n)))))) - (checking "v/freeze" 100 [n gen-literal-element] - (is (= (v/freeze n) - (v/freeze (an/literal-number n))))) + (checking "g/freeze" 100 [n gen-literal-element] + (is (= (g/freeze n) + (g/freeze (an/literal-number n))))) (checking "v/kind" 100 [n gen-literal-element] (is (= ::x/numeric (v/kind (an/literal-number n)))))) @@ -150,7 +150,7 @@ (is (= (if (zero? x) 2 `(~'+ 1 (~'cos ~x))) - (v/freeze result)) + (g/freeze result)) "When literal-number wraps an actual number, it attempts to keep the result exact instead of evaluating the fns... UNLESS specific values like (cos 0) can be exactly evaluated."))) @@ -297,10 +297,10 @@ (checking "angle" 100 [z sg/complex] (let [z' (an/literal-number z) result (g/angle z) - expected (cond (and (v/exact? z') (v/exact? result)) + expected (cond (and (g/exact? z') (g/exact? result)) (an/literal-number result) - (v/exact? z') + (g/exact? z') (g/atan (g/imag-part z') (g/real-part z')) @@ -308,7 +308,7 @@ (is (= expected (g/angle z'))))) (checking "magnitude" 100 [z sg/complex] - (let [expected (if (v/exact? z) + (let [expected (if (g/exact? z) (g/sqrt (an/literal-number (g/* z (g/conjugate z)))) @@ -357,15 +357,15 @@ don't work bail out to Math/tan.")))) (checking "asin" 100 [x sg/real] - (is (= (cond (v/zero? x) (v/zero-like x) - (v/exact? x) (list 'asin x) + (is (= (cond (g/zero? x) (g/zero-like x) + (g/exact? x) (list 'asin x) :else (g/asin x)) (x/expression-of (g/asin (an/literal-number x)))))) (checking "acos" 100 [x sg/real] - (is (= (cond (v/one? x) (v/zero-like x) - (v/exact? x) (list 'acos x) + (is (= (cond (g/one? x) (g/zero-like x) + (g/exact? x) (list 'acos x) :else (g/acos x)) (x/expression-of (g/acos (an/literal-number x)))))) @@ -375,18 +375,18 @@ (is (= (g/atan (an/literal-number x)) (g/atan (an/literal-number x) 1))) - (is (= (cond (v/zero? y) (v/zero-like y) - (v/exact? y) (list 'atan y) + (is (= (cond (g/zero? y) (g/zero-like y) + (g/exact? y) (list 'atan y) :else (g/atan y)) (x/expression-of (g/atan (an/literal-number y)))) "single arity") - (let [y-exact? (v/exact? y) - x-exact? (v/exact? x) - y-zero? (v/zero? y) - x-zero? (v/zero? x) - x-one? (v/one? x)] + (let [y-exact? (g/exact? y) + x-exact? (g/exact? x) + y-zero? (g/zero? y) + x-zero? (g/zero? x) + x-one? (g/one? x)] (is (= (cond (and x-one? y-zero?) 0 (and x-one? y-exact?) (list 'atan y) x-one? (g/atan y) @@ -403,37 +403,52 @@ "double arity"))) (checking "cosh" 100 [x sg/real] - (is (= (cond (v/zero? x) 1 - (v/exact? x) (list 'cosh x) + (is (= (cond (g/zero? x) 1 + (g/exact? x) (list 'cosh x) :else (g/cosh x)) (x/expression-of (g/cosh (an/literal-number x)))))) (checking "sinh" 100 [x sg/real] - (is (= (cond (v/zero? x) 0 - (v/exact? x) (list 'sinh x) + (is (= (cond (g/zero? x) 0 + (g/exact? x) (list 'sinh x) :else (g/sinh x)) (x/expression-of (g/sinh (an/literal-number x)))))) (checking "sec" 100 [x sg/real] - (is (= (cond (v/zero? x) 1 - (v/exact? x) (list '/ 1 (list 'cos x)) + (is (= (cond (g/zero? x) 1 + (g/exact? x) (list '/ 1 (list 'cos x)) :else (g/sec x)) (x/expression-of (g/sec (an/literal-number x)))))) (checking "csc" 100 [x sg/real] - (if (v/zero? x) + (if (g/zero? x) (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (g/csc (an/literal-number x)))) - (is (= (if (v/exact? x) + (is (= (if (g/exact? x) (list '/ 1 (list 'sin x)) (g/csc x)) (x/expression-of (g/csc (an/literal-number x)))))))) +(deftest symbolic-value-tests + (testing "zero? and friends work symbolically" + (let [sym:zero? (sym/symbolic-operator 'zero?) + sym:one? (sym/symbolic-operator 'one?) + sym:identity? (sym/symbolic-operator 'identity?)] + (is (= '(= 0 x) (sym:zero? 'x))) + (is (sym:zero? 0)) + (is (not (sym:zero? 1))) + (is (= '(= 1 x) (sym:one? 'x))) + (is (sym:one? 1)) + (is (not (sym:one? 0))) + (is (= '(= 1 x) (sym:identity? 'x))) + (is (sym:identity? 1)) + (is (not (sym:identity? 0)))))) + (deftest symbolic-arithmetic-tests (testing "0-arity cases for symbolic operators" (is (false? ((sym/symbolic-operator 'or)))) @@ -451,11 +466,11 @@ (is (v/= x (g/add x 0)))) (testing "sums fuse together in the constructor" - (is (= '(+ x y z) (v/freeze (g/add 'x (g/add 'y 'z))))) - (is (= '(+ 10 y z) (v/freeze (g/add 10 (g/add 'y 'z))))) - (is (= '(+ y z 10) (v/freeze (g/add (g/add 'y 'z) 10)))) + (is (= '(+ x y z) (g/freeze (g/add 'x (g/add 'y 'z))))) + (is (= '(+ 10 y z) (g/freeze (g/add 10 (g/add 'y 'z))))) + (is (= '(+ y z 10) (g/freeze (g/add (g/add 'y 'z) 10)))) (is (= '(+ y z a b) - (v/freeze (g/add (g/add 'y 'z) + (g/freeze (g/add (g/add 'y 'z) (g/add 'a 'b)))))) (checking "- constructor optimizations" 100 @@ -475,11 +490,11 @@ (is (= (g/- 10 (g/+ 20 'x 3 2 1)) (g/- 10 20 'x 3 2 1)))) (testing "products fuse together in the constructor" - (is (= '(* x y z) (v/freeze (g/mul 'x (g/mul 'y 'z))))) - (is (= '(* 10 y z) (v/freeze (g/mul 10 (g/mul 'y 'z))))) - (is (= '(* y z 10) (v/freeze (g/mul (g/mul 'y 'z) 10)))) + (is (= '(* x y z) (g/freeze (g/mul 'x (g/mul 'y 'z))))) + (is (= '(* 10 y z) (g/freeze (g/mul 10 (g/mul 'y 'z))))) + (is (= '(* y z 10) (g/freeze (g/mul (g/mul 'y 'z) 10)))) (is (= '(* y z a b) - (v/freeze (g/mul (g/mul 'y 'z) + (g/freeze (g/mul (g/mul 'y 'z) (g/mul 'a 'b)))))) (checking "* constructor optimizations" 100 @@ -515,30 +530,30 @@ (is (v/= 0 (g/modulo 0 x))) (is (v/= 0 (g/modulo x x))) (if (= x y) - (is (= 0 (v/freeze (g/modulo x y)))) + (is (= 0 (g/freeze (g/modulo x y)))) (is (= (list 'modulo x y) - (v/freeze (g/modulo x y))))) + (g/freeze (g/modulo x y))))) (is (v/= x (g/modulo x 1)))) (testing "unary ops with symbols" - (is (= '(floor x) (v/freeze (g/floor 'x)))) - (is (= '(ceiling x) (v/freeze (g/ceiling 'x)))) - (is (= '(integer-part x) (v/freeze (g/integer-part 'x)))) - (is (= '(fractional-part x) (v/freeze (g/fractional-part 'x))))) + (is (= '(floor x) (g/freeze (g/floor 'x)))) + (is (= '(ceiling x) (g/freeze (g/ceiling 'x)))) + (is (= '(integer-part x) (g/freeze (g/integer-part 'x)))) + (is (= '(fractional-part x) (g/freeze (g/fractional-part 'x))))) (checking "gcd, lcm annihilation" 100 [pre (gen/vector gen/symbol) post (gen/vector gen/symbol)] - (is (v/one? + (is (g/one? (apply (sym/symbolic-operator 'gcd) (concat pre [1] post)))) - (is (v/zero? + (is (g/zero? (apply (sym/symbolic-operator 'lcm) (concat pre [0] post))))) (let [non-one-zero (gen/fmap (fn [n] - (if (or (v/zero? n) (v/one? n)) + (if (or (g/zero? n) (g/one? n)) 2 n)) sg/any-integral)] @@ -550,16 +565,16 @@ (is (v/= sym (g/gcd sym sym)) "gcd(x,x)==x") - (is (v/= sym (g/gcd (v/zero-like n) sym)) + (is (v/= sym (g/gcd (g/zero-like n) sym)) "gcd(x,0)==x") - (is (v/= sym (g/gcd sym (v/zero-like n))) + (is (v/= sym (g/gcd sym (g/zero-like n))) "gcd(0,x)==x") - (is (v/one? (g/gcd (v/one-like n) sym)) + (is (g/one? (g/gcd (g/one-like n) sym)) "gcd(1,x)==1") - (is (v/one? (g/gcd sym (v/one-like n))) + (is (g/one? (g/gcd sym (g/one-like n))) "gcd(x,1)==1")) (checking "symbolic lcm" 100 [sym gen/symbol @@ -570,16 +585,16 @@ (is (v/= sym (g/lcm sym sym)) "lcm(x,x)==x") - (is (v/zero? (g/lcm (v/zero-like n) sym)) + (is (g/zero? (g/lcm (g/zero-like n) sym)) "lcm(x,0)==0") - (is (v/zero? (g/lcm sym (v/zero-like n))) + (is (g/zero? (g/lcm sym (g/zero-like n))) "lcm(0,x)==0") - (is (v/= sym (g/lcm (v/one-like n) sym)) + (is (v/= sym (g/lcm (g/one-like n) sym)) "lcm(1,x)==x") - (is (v/= sym (g/lcm sym (v/one-like n))) + (is (v/= sym (g/lcm sym (g/one-like n))) "lcm(x,1)==x"))) (testing "/ with symbols" @@ -591,12 +606,12 @@ (testing "negate" (is (= (g/+ 'x (g/- 'x)) (g/+ 'x (g/negate 'x)))) - (is (= '(+ x (- x)) (v/freeze + (is (= '(+ x (- x)) (g/freeze (g/+ 'x (g/negate 'x)))))) (testing "invert" (is (= (g/div 1 'x) (g/invert 'x))) - (is (= '(/ 1 x) (v/freeze + (is (= '(/ 1 x) (g/freeze (g/invert 'x))))) (testing "square" @@ -618,7 +633,7 @@ (checking "sqrt" 100 [x gen/symbol] (is (= (list 'sqrt x) - (v/freeze (g/sqrt x))))) + (g/freeze (g/sqrt x))))) (checking "log" 100 [x gen/symbol] (is (v/= (list 'log x) @@ -635,7 +650,7 @@ (checking "exp" 100 [x gen/symbol] (is (= (list 'exp x) - (v/freeze (g/exp x)))) + (g/freeze (g/exp x)))) (is (= (g/expt 2 x) (g/exp2 x))) (is (= (g/expt 10 x) (g/exp10 x)))) @@ -648,16 +663,16 @@ (testing "conjugate" (is (= '(conjugate (random x)) - (v/freeze + (g/freeze (g/conjugate (an/literal-number '(random x)))))) (doseq [op @#'sym/conjugate-transparent-operators] - (is (= (v/freeze + (is (= (g/freeze (an/literal-number (list op (g/conjugate 'x) (g/conjugate 'y)))) - (v/freeze + (g/freeze (g/conjugate (an/literal-number (list op 'x 'y))))) "This is a little busted, since we don't check for the proper number @@ -692,14 +707,14 @@ "for other cases, the complex number is evaluated.") ;; Case of symbolic radius, angle `n`: - (if (v/exact? n) - (if (v/zero? n) + (if (g/exact? n) + (if (g/zero? n) (is (v/= sym (g/make-polar sym n)) "an exact zero returns the symbolic radius.") - (is (= `(~'* ~sym (~'+ (~'cos ~(v/freeze n)) + (is (= `(~'* ~sym (~'+ (~'cos ~(g/freeze n)) (~'* (~'complex 0.0 1.0) - (~'sin ~(v/freeze n))))) - (v/freeze + (~'sin ~(g/freeze n))))) + (g/freeze (g/make-polar sym n))) "otherwise, an exact numeric angle stays exact and is treated as a literal number.")) @@ -744,7 +759,7 @@ (testing "dot-product" (is (= '(+ (* 0.5 x (conjugate y)) (* 0.5 y (conjugate x))) - (v/freeze + (g/freeze (g/simplify (g/dot-product 'x 'y))))) @@ -818,73 +833,73 @@ (g/tan 'pi-over-2))))) (testing "asin" - (is (= '(asin x) (v/freeze (g/asin 'x))))) + (is (= '(asin x) (g/freeze (g/asin 'x))))) (testing "acos" - (is (= '(acos x) (v/freeze (g/acos 'x))))) + (is (= '(acos x) (g/freeze (g/acos 'x))))) (testing "atan" - (is (= '(atan x) (v/freeze (g/atan 'x))))) + (is (= '(atan x) (g/freeze (g/atan 'x))))) (testing "sinh" - (is (= '(sinh x) (v/freeze (g/sinh 'x))))) + (is (= '(sinh x) (g/freeze (g/sinh 'x))))) (testing "cosh" - (is (= '(cosh x) (v/freeze (g/cosh 'x))))) + (is (= '(cosh x) (g/freeze (g/cosh 'x))))) (testing "tan" - (is (= '(tan x) (v/freeze (g/tan 'x))))) + (is (= '(tan x) (g/freeze (g/tan 'x))))) (testing "cot" (is (= '(/ (cos x) (sin x)) - (v/freeze (g/cot 'x))))) + (g/freeze (g/cot 'x))))) (testing "sec" - (is (= '(/ 1 (cos x)) (v/freeze (g/sec 'x))))) + (is (= '(/ 1 (cos x)) (g/freeze (g/sec 'x))))) (testing "csc" - (is (= '(/ 1 (sin x)) (v/freeze (g/csc 'x))))) + (is (= '(/ 1 (sin x)) (g/freeze (g/csc 'x))))) (testing "acot" (is (= '(- (/ pi 2) (atan x)) - (v/freeze (g/acot 'x))))) + (g/freeze (g/acot 'x))))) (testing "asec" (is (= '(atan (sqrt (- (expt x 2) 1))) - (v/freeze + (g/freeze (g/asec 'x))))) (testing "tanh" (is (= '(/ (sinh x) (cosh x)) - (v/freeze (g/tanh 'x))))) + (g/freeze (g/tanh 'x))))) (testing "coth" (is (= '(/ (cosh x) (sinh x)) - (v/freeze (g/coth 'x))))) + (g/freeze (g/coth 'x))))) (testing "sech" (is (= '(/ 1 (cosh x)) - (v/freeze (g/sech 'x))))) + (g/freeze (g/sech 'x))))) (testing "csch" (is (= '(/ 1 (sinh x)) - (v/freeze (g/csch 'x))))) + (g/freeze (g/csch 'x))))) (testing "acosh" (is (= '(* 2 (log (+ (sqrt (/ (+ x 1) 2)) (sqrt (/ (- x 1) 2))))) - (v/freeze (g/acosh 'x))))) + (g/freeze (g/acosh 'x))))) (testing "asinh" (is (= '(log (+ x (sqrt (+ 1 (expt x 2))))) - (v/freeze (g/asinh 'x))))) + (g/freeze (g/asinh 'x))))) (testing "atanh" (is (= '(/ (- (log (+ 1 x)) (log (- 1 x))) 2) - (v/freeze (g/atanh 'x)))))) + (g/freeze (g/atanh 'x)))))) (deftest boolean-tests ;; These don't QUITE belong in the namespace for abstract number; TODO move @@ -994,7 +1009,7 @@ (deftest incremental-simplifier-tests (testing "incremental simplifier works for unary, binary" (binding [sym/*incremental-simplifier* simpl/simplify-expression] - (is (= 1 (v/freeze + (is (= 1 (g/freeze (g/+ (g/square (g/cos 'x)) (g/square (g/sin 'x))))))) @@ -1003,11 +1018,11 @@ (rule/rule (cos x) => 12))] (binding [sym/*incremental-simplifier* flip] (is (= '(* 2 (cos theta)) - (v/freeze + (g/freeze (g/+ (g/cos 'theta) (g/cos 'theta)))) "The rule applies a single simplification.") - (is (= 24 (v/freeze + (is (= 24 (g/freeze (g/+ (g/cos 'x) (g/cos 'x)))) "rule here maps `(g/cos 'x)` to 12 internally, then `g/+` actually performs the addition.")))) diff --git a/test/emmy/calculus/basis_test.cljc b/test/emmy/calculus/basis_test.cljc index 0f39963f..e53b5755 100644 --- a/test/emmy/calculus/basis_test.cljc +++ b/test/emmy/calculus/basis_test.cljc @@ -42,7 +42,7 @@ (sqrt (+ (expt x 2) (expt y 2)))) (/ (+ (* x (v↑1 (up x y))) (* -1 y (v↑0 (up x y)))) (+ (expt x 2) (expt y 2)))) - (v/freeze + (g/freeze (g/simplify (vjp ((m/point m/R2-rect) (up 'x 'y))))))))) diff --git a/test/emmy/calculus/connection_test.cljc b/test/emmy/calculus/connection_test.cljc index 689ab488..ea996867 100644 --- a/test/emmy/calculus/connection_test.cljc +++ b/test/emmy/calculus/connection_test.cljc @@ -14,13 +14,12 @@ [emmy.function :refer [compose]] [emmy.generic :as g :refer [+ * /]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest basic-tests (testing "Christoffel round-trip" @@ -336,33 +335,33 @@ (/ -1 r) (/ (* -1 (cos theta)) (* r (sin theta))) 0))) - (v/freeze foo))) + (g/freeze foo))) ;; Check answers from MTW p.213 ;; t r theta phi ;; 0 1 2 3 (is (= '(/ (cos theta) (* r (sin theta))) - (v/freeze + (g/freeze (get-in foo [3 2 3])))) (is (= '(/ (* -1 (cos theta)) (* r (sin theta))) - (v/freeze + (g/freeze (get-in foo [3 3 2])))) (is (= '(/ 1 r) - (v/freeze + (g/freeze (get-in foo [2 1 2])))) (is (= '(/ 1 r) - (v/freeze + (g/freeze (get-in foo [3 1 3])))) (is (= '(/ -1 r) - (v/freeze + (g/freeze (get-in foo [2 2 1])))) (is (= '(/ -1 r) - (v/freeze + (g/freeze (get-in foo [3 3 1])))))) ;; TODO: This one takes quite a while, so we only install this test diff --git a/test/emmy/calculus/coordinate_test.cljc b/test/emmy/calculus/coordinate_test.cljc index 69b7b78f..8098329e 100644 --- a/test/emmy/calculus/coordinate_test.cljc +++ b/test/emmy/calculus/coordinate_test.cljc @@ -12,13 +12,12 @@ [emmy.function :refer [compose]] [emmy.generic :as g :refer [+ * /]] [emmy.simplify :as s :refer [hermetic-simplify-fixture]] - [emmy.structure :refer [up]] - [emmy.value :as v])) + [emmy.structure :refer [up]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest smoke (let-coordinates [[x y] R2-rect diff --git a/test/emmy/calculus/covariant_test.cljc b/test/emmy/calculus/covariant_test.cljc index 522d57de..dcc36fa9 100644 --- a/test/emmy/calculus/covariant_test.cljc +++ b/test/emmy/calculus/covariant_test.cljc @@ -24,13 +24,12 @@ [emmy.mechanics.lagrange :as ml] [emmy.polynomial.gcd :as pg] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest Lie-derivative-tests (testing "Lie derivative." @@ -49,7 +48,7 @@ (-> (simplify expr) (x/substitute '(up x0 y0 z0) 'p)))] -(is (= '(+ (* (Y↑0 p) (((partial 0) w_0) p) (X↑0 p)) + (is (= '(+ (* (Y↑0 p) (((partial 0) w_0) p) (X↑0 p)) (* (Y↑0 p) (w_0 p) (((partial 0) X↑0) p)) (* (Y↑0 p) (w_1 p) (((partial 0) X↑1) p)) (* (Y↑0 p) (w_2 p) (((partial 0) X↑2) p)) @@ -70,6 +69,8 @@ (present ((((g/Lie-derivative X) w) Y) R3-rect-point)))) + (is (= '(Lie-derivative X) (g/freeze (g/Lie-derivative X)))) + (is (= '(Lie-D X) (g/freeze (cov/Lie-D X)))) (is (= 0 (simplify ((- ((ff/d ((g/Lie-derivative X) f)) Y) (((g/Lie-derivative X) (ff/d f)) Y)) @@ -378,7 +379,7 @@ Y (vf/literal-vector-field 'Y S2-spherical) m ((point S2-spherical) (up 'theta 'phi))] - (is (v/zero? + (is (g/zero? (simplify (((((cov/covariant-derivative C) V) g) X Y) m))))) @@ -388,7 +389,7 @@ (up (G↑0_10 p) (G↑1_10 p))) (down (up (G↑0_01 p) (G↑1_01 p)) (up (G↑0_11 p) (G↑1_11 p)))) - (v/freeze + (g/freeze (present (G R2-rect-point))))) @@ -399,7 +400,7 @@ (+ (* (G↑1_00 p) (X↑0 p)) (* (G↑1_01 p) (X↑1 p)))) (up (+ (* (G↑0_10 p) (X↑0 p)) (* (G↑0_11 p) (X↑1 p))) (+ (* (G↑1_10 p) (X↑0 p)) (* (G↑1_11 p) (X↑1 p))))) - (v/freeze + (g/freeze (present (((cov/Cartan->forms CF) (vf/literal-vector-field 'X R2-rect)) R2-rect-point))))) @@ -409,7 +410,7 @@ (up (G↑0_10 p) (G↑1_10 p))) (down (up (G↑0_01 p) (G↑1_01 p)) (up (G↑0_11 p) (G↑1_11 p)))) - (v/freeze + (g/freeze (present ((cov/Christoffel->symbols (cov/Cartan->Christoffel (cov/Christoffel->Cartan CG))) @@ -487,7 +488,7 @@ (* (((partial 0) F) p) (X↑1 p) (((partial 1) V↑0) p)) (* (X↑0 p) (((partial 1) F) p) (((partial 0) V↑1) p)) (* (X↑1 p) (((partial 1) F) p) (((partial 1) V↑1) p))) - (v/freeze + (g/freeze (present (((((cov/covariant-derivative CF) X) V) (man/literal-manifold-function 'F R2-rect)) @@ -515,7 +516,7 @@ J (- (* x d:dy) (* y d:dx)) f (man/literal-scalar-field 'f R2-rect)] (is (= '(((partial 1) f) (up x0 y0)) - (v/freeze + (g/freeze (simplify (((((cov/covariant-derivative rect-Cartan) d:dx) @@ -524,7 +525,7 @@ m2))))) (is (= '(((partial 1) f) (up x0 y0)) - (v/freeze + (g/freeze (simplify (((((cov/covariant-derivative polar-Cartan) d:dx) @@ -584,7 +585,7 @@ (* (cos (alpha tau)) (w1 tau) ((D alpha) tau)) (* (sin (alpha tau)) ((D w1) tau))) (sin (alpha tau)))) - (v/freeze + (g/freeze (simplify (s/mapr (fn [omega] @@ -601,7 +602,7 @@ (/ (+ (* 2 (cos (alpha t)) ((D beta) t) ((D alpha) t)) (* (sin (alpha t)) (((expt D 2) beta) t))) (sin (alpha t)))) - (v/freeze + (g/freeze (simplify (s/mapr (fn [omega] @@ -642,7 +643,7 @@ (* (u0 t) ((D beta) t) (G↑1_01 (up (alpha t) (beta t)))) (* (u1 t) ((D beta) t) (G↑1_11 (up (alpha t) (beta t)))) ((D u1) t))) - (v/freeze + (g/freeze (simplify (s/mapr (fn [omega] @@ -663,7 +664,7 @@ (* ((D alpha) t) ((D beta) t) (G↑1_01 (up (alpha t) (beta t)))) (* (expt ((D beta) t) 2) (G↑1_11 (up (alpha t) (beta t)))) (((expt D 2) beta) t))) - (v/freeze + (g/freeze (simplify (s/mapr (fn [omega] @@ -705,7 +706,7 @@ (* (sin (mu-theta tau)) (((expt D 2) mu-phi) tau))) (sin (mu-theta tau)))) - (v/freeze + (g/freeze (simplify (s/mapr (fn [w] @@ -751,7 +752,7 @@ (((expt D 2) theta) t)) (+ (* 2 (cos (theta t)) ((D theta) t) (sin (theta t)) ((D phi) t)) (* (expt (sin (theta t)) 2) (((expt D 2) phi) t)))) - (v/freeze + (g/freeze (simplify (((ml/Lagrange-equations Lsphere) (up (af/literal-function 'theta) @@ -765,21 +766,21 @@ R3-cyl-point ((point R3-cyl) (up 'r0 'theta0 'z0)) mpr (chart R3-rect)] (is (= '(up 0 0 0) - (v/freeze + (g/freeze (simplify (((* d:dr d:dr) mpr) R3-rect-point))))) ;; So \Gamma↑r_{rr} = 0, \Gamma↑\theta_{rr} = 0 (is (= '(up (/ (* -1 y0) (sqrt (+ (expt x0 2) (expt y0 2)))) (/ x0 (sqrt (+ (expt x0 2) (expt y0 2)))) 0) - (v/freeze + (g/freeze (simplify (((* d:dtheta d:dr) mpr) R3-rect-point))))) ;; by hand = -sint d:dx + cost d:dy = 1/r d:dtheta ;; Indeed. (is (= '(up (* -1 (sin theta0)) (cos theta0) 0) - (v/freeze + (g/freeze (simplify (((* d:dtheta d:dr) mpr) R3-cyl-point))))) @@ -788,13 +789,13 @@ (is (= '(up (/ (* -1 y0) (sqrt (+ (expt x0 2) (expt y0 2)))) (/ x0 (sqrt (+ (expt x0 2) (expt y0 2)))) 0) - (v/freeze + (g/freeze (simplify (((* d:dr d:dtheta) mpr) R3-rect-point))))) ;; by hand = -sint d:dx + cost d:dy = 1/r d:dtheta (is (= '(up (* -1 (sin theta0)) (cos theta0) 0) - (v/freeze + (g/freeze (simplify (((* d:dr d:dtheta) mpr) R3-cyl-point))))) @@ -806,7 +807,7 @@ ;; by hand = -r cost d:dx - r sint d:dy = -r d:dr (is (= '(up (* -1 r0 (cos theta0)) (* -1 r0 (sin theta0)) 0) - (v/freeze + (g/freeze (simplify (((* d:dtheta d:dtheta) mpr) R3-cyl-point))))) ;; So \Gammar_{\theta \theta} = -r, \Gamma\theta_{\theta \theta} = 0 @@ -884,12 +885,17 @@ (- (((((CD CF-rect R2-polar) X) Y) F) m_0) (((((cov/covariant-derivative CF-rect) X) Y) F) m_0))))) + ;; throws when given something that doesn't look like a + ;; vector field for Y + (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) + (((cov/covariant-derivative CF-rect) X) 99))) + ;; TODO: Too slow... it works if we bump the timeout, but this is not fast. #_(binding [pg/*poly-gcd-time-limit* [5 :seconds]] - (is (zero? - (simplify - (- (((((CD CF-polar R2-polar) X) Y) F) m_0) - (((((cov/covariant-derivative CF-polar) X) Y) F) m_0)))))) + (is (zero? + (simplify + (- (((((CD CF-polar R2-polar) X) Y) F) m_0) + (((((cov/covariant-derivative CF-polar) X) Y) F) m_0)))))) (testing "Testing on forms." (let [omega (ff/literal-oneform-field 'omega R2-rect) @@ -931,7 +937,7 @@ (* ((D gamma↑0) t) ((D gamma↑1) t) (G_01↑1 (up (gamma↑0 t) (gamma↑1 t)))) (* (expt ((D gamma↑1) t) 2) (G_11↑1 (up (gamma↑0 t) (gamma↑1 t)))) (((expt D 2) gamma↑1) t))) - (v/freeze + (g/freeze (simplify (((cov/geodesic-equation the-real-line R2-rect (conn/literal-Cartan 'G R2-rect)) (cm/literal-manifold-map 'gamma the-real-line R2-rect)) @@ -939,7 +945,7 @@ (let [C (conn/literal-Cartan 'G R2-rect)] (is (= '(up 0 0) - (v/freeze + (g/freeze (simplify (- (((cov/geodesic-equation the-real-line R2-rect C) (cm/literal-manifold-map 'gamma the-real-line R2-rect)) @@ -979,7 +985,7 @@ (* (cos (alpha t)) (u↑1 t) ((D alpha) t)) (* (sin (alpha t)) ((D u↑1) t))) (sin (alpha t)))) - (v/freeze + (g/freeze (simplify ((((cov/parallel-transport-equation the-real-line S2-spherical sphere-Cartan) diff --git a/test/emmy/calculus/curvature_test.cljc b/test/emmy/calculus/curvature_test.cljc index e81246df..59b44608 100644 --- a/test/emmy/calculus/curvature_test.cljc +++ b/test/emmy/calculus/curvature_test.cljc @@ -19,13 +19,12 @@ [emmy.mechanics.lagrange :refer [osculating-path]] [emmy.operator :as o] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (def M (m/make-manifold m/Rn 2)) (def M-rect (m/coordinate-system-at M :rectangular :origin)) diff --git a/test/emmy/calculus/derivative_test.cljc b/test/emmy/calculus/derivative_test.cljc index 49fe7757..51941dc1 100644 --- a/test/emmy/calculus/derivative_test.cljc +++ b/test/emmy/calculus/derivative_test.cljc @@ -26,7 +26,7 @@ (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest fn-iperturbed-tests (testing "tag-active? responds appropriately" @@ -152,7 +152,7 @@ (is (o/operator? ((D f) 'x)) "if f returns an operator, (D f) does too.") (is (= '(sin y) - (v/freeze + (g/freeze (((D f) 'x) 'y))) "derivative pushes into the operator's fn..")))) @@ -363,8 +363,8 @@ (simplify ((D g) 'x 'y))))) (testing "D of zero-like" - (is (= 0 ((v/zero-like f) 'x))) - (is (= 0 ((D (v/zero-like f)) 'x)))))) + (is (= 0 ((g/zero-like f) 'x))) + (is (= 0 ((D (g/zero-like f)) 'x)))))) (deftest complex-derivatives (let [f (fn [z] (* c/I (sin (* c/I z))))] @@ -491,14 +491,14 @@ (testing "D can handle functions of varying arities" (let [f100dd (fn [x n acc] - (if (v/zero? n) + (if (g/zero? n) acc (recur x (dec n) (sin (+ x acc))))) f100d (fn [x] (f100dd x 100 x)) f100e (fn f100e ([x] (f100e x 100 x)) ([x n acc] - (if (v/zero? n) + (if (g/zero? n) acc (recur x (dec n) (sin (+ x acc)))))) f100ea (f/with-arity f100e [:exactly 1]) @@ -555,7 +555,7 @@ (let [S (s/up 't (s/up 'x 'y) (s/down 'p_x 'p_y)) present (fn [expr] (-> (simplify expr) - (x/substitute (v/freeze S) 'p)))] + (x/substitute (g/freeze S) 'p)))] (is (= '(matrix-by-rows [(((partial 0) H) p) (((partial 1 0) H) p) @@ -576,7 +576,7 @@ S (s/up 't (s/up 'x 'y) (s/down 'px 'py)) present (fn [expr] (-> (simplify expr) - (x/substitute (v/freeze S) 'p)))] + (x/substitute (g/freeze S) 'p)))] (is (= '(matrix-by-rows [(((partial 0) C↑0) p) (((partial 1 0) C↑0) p) (((partial 1 1) C↑0) p) @@ -713,7 +713,7 @@ (* (/ 7 256) (expt dx 5))) (simplify (take 6 ((d/taylor-series - (fn [x] (g/sqrt (+ (v/one-like x) x))) + (fn [x] (g/sqrt (+ (g/one-like x) x))) 0) 'dx)))))) (deftest derivative-of-matrix @@ -1222,13 +1222,13 @@ ;; Calling f1 or f2 separately work as expected: (is (= '(((partial 0) a) t t) - (v/freeze + (g/freeze (((D f) 't) (fn [f1 _] (f1 't))))) "`a` received `x` as its first arg, so we see `(partial 0)`") (let [g (af/literal-function 'g)] (is (= '((D g) t) - (v/freeze + (g/freeze (((D f) 't) (fn [_ f2] (f2 g))))) "derivative of (g x) == ((D g) x).")) @@ -1508,7 +1508,7 @@ (* -8 (sin (* 3 a)) (cos (* 4 b)))))] (->> (d/symbolic-taylor-series f (s/up 'a 'b)) (take 3) - (v/freeze))) + (g/freeze))) "coefficients with structural input")) (let [G (af/literal-function 'G '(-> (UP Real Real) Real))] @@ -1523,7 +1523,7 @@ (* (/ 1 2) (((expt (partial 1) 2) G) (up a b)))))] (->> (d/symbolic-taylor-series G (s/up 'a 'b)) (take 3) - (v/freeze))) + (g/freeze))) "abstract function, coefficients with structural input")) (let [H (af/literal-function 'H '(-> (X Real Real) Real))] @@ -1560,7 +1560,7 @@ (* (/ 1 6) (((expt (partial 1) 3) H) a b)))))) (->> (d/symbolic-taylor-series H 'a 'b) (take 4) - (v/freeze))) + (g/freeze))) "coefficients with a multi-arg function")) (is (v/= [0 1 0 0] diff --git a/test/emmy/calculus/form_field_test.cljc b/test/emmy/calculus/form_field_test.cljc index 4f752655..2385de00 100644 --- a/test/emmy/calculus/form_field_test.cljc +++ b/test/emmy/calculus/form_field_test.cljc @@ -11,13 +11,12 @@ [emmy.expression :as x] [emmy.generic :as g :refer [+ - *]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :refer [up down]] - [emmy.value :as v])) + [emmy.structure :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest manifold-tests (testing "first block of test material from manifold.scm" @@ -72,13 +71,13 @@ (is (= 0 (simplify ((residual vr vr) mp)))))))) (deftest form-field-tests - (testing "v/zero-like" + (testing "g/zero-like" (let [oneform-field (ff/literal-oneform-field 'b R2-rect)] - (is (v/zero? (v/zero-like oneform-field))) + (is (g/zero? (g/zero-like oneform-field))) (is (ff/form-field? - (v/zero-like oneform-field))) - (is (= 'ff:zero (v/freeze - (v/zero-like oneform-field)))))) + (g/zero-like oneform-field))) + (is (= 'ff:zero (g/freeze + (g/zero-like oneform-field)))))) (testing "oneform-field->components" (let-coordinates [[x y] R2-rect] @@ -89,7 +88,7 @@ (is (= '(down (f_0 (up x0 y0)) (f_1 (up x0 y0))) - (v/freeze + (g/freeze ((ff/oneform-field->components f R2-rect) (up 'x0 'y0)))) "retrieve the components")))) @@ -276,7 +275,7 @@ (is (= '(+ (* (w_0 (up x0 y0 z0)) (X↑0 (up x0 y0 z0))) (* (w_1 (up x0 y0 z0)) (X↑1 (up x0 y0 z0))) (* (w_2 (up x0 y0 z0)) (X↑2 (up x0 y0 z0)))) - (v/freeze ((w X) R3-point)))) + (g/freeze ((w X) R3-point)))) ;; A few theorems diff --git a/test/emmy/calculus/hodge_star_test.cljc b/test/emmy/calculus/hodge_star_test.cljc index 11962d7e..97dc8531 100644 --- a/test/emmy/calculus/hodge_star_test.cljc +++ b/test/emmy/calculus/hodge_star_test.cljc @@ -15,13 +15,12 @@ [emmy.function :refer [compose]] [emmy.generic :as g :refer [+ - * /]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest gram-schmidt-tests (testing "Orthonormalizing with respect to the Lorentz metric in 2 dimensions." @@ -135,7 +134,7 @@ (testing "What is a rank 0 form?" (is (= '(V↑1 (up x y)) - (v/freeze + (g/freeze (((E2-star dx) (vf/literal-vector-field 'V R2-rect)) ((m/point R2-rect) (up 'x 'y)))))) diff --git a/test/emmy/calculus/indexed_test.cljc b/test/emmy/calculus/indexed_test.cljc index aafbcd7d..46bb8162 100644 --- a/test/emmy/calculus/indexed_test.cljc +++ b/test/emmy/calculus/indexed_test.cljc @@ -12,13 +12,12 @@ [emmy.function :as f] [emmy.generic :as g :refer [+ *]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest indexed-tests (let-coordinates [[x y] m/R2-rect] @@ -36,7 +35,7 @@ (is (= '(+ (* c (v1↑1 (up x y)) (w2_1 (up x y)) (w1_1 (up x y))) (* b (v1↑1 (up x y)) (w2_0 (up x y)) (w1_1 (up x y))) (* a (v1↑0 (up x y)) (w2_1 (up x y)) (w1_0 (up x y)))) - (v/freeze + (g/freeze (((ci/indexed->typed (ci/typed->indexed T (b/coordinate-system->basis R2-rect)) (b/coordinate-system->basis R2-rect)) diff --git a/test/emmy/calculus/manifold_test.cljc b/test/emmy/calculus/manifold_test.cljc index 4f14bf45..ec8e9635 100644 --- a/test/emmy/calculus/manifold_test.cljc +++ b/test/emmy/calculus/manifold_test.cljc @@ -14,13 +14,12 @@ [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.structure :refer [up]] [emmy.util :as u] - [emmy.value :as v] [same.core :refer [ish?]])) (use-fixtures :each hermetic-simplify-fixture) (def s-freeze - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (defn rt "Round trip the supplied coordinates through the supplied `coordinate-system`." @@ -176,7 +175,7 @@ (checking "R2-rect->polar" 100 [x (gen/fmap #(g/modulo % 1000) sg/real) y (gen/fmap #(g/modulo % 1000) sg/real)] - (when-not (and (v/zero? x) (v/zero? y)) + (when-not (and (g/zero? x) (g/zero? y)) (let [r (g/abs (up x y)) theta (g/atan y x)] (is (= (up r theta) @@ -192,6 +191,18 @@ (m/coords->point m/R2-polar) (m/point->coords m/R2-rect))))) + (testing "Spherical singular coordinates throw" + (is (thrown? #?(:clj IllegalStateException :cljs js/Error) + (->> (up 0 0 0) + (m/coords->point m/R3-rect) + (m/point->coords m/R3-spherical))))) + + (testing "Spacetime singular coordinates throw" + (is (thrown? #?(:clj clojure.lang.ExceptionInfo :cljs js/Error) + (->> (up 0 0 0 0) + (m/coords->point m/spacetime-rect) + (m/point->coords m/spacetime-sphere))))) + (checking "R3-rect" 100 [coords (sg/structure1 sg/real 3)] (check-coord-system m/R3-rect coords) (roundtrips? m/R3-rect coords)) @@ -405,6 +416,7 @@ (roundtrips? m/S3-spherical (up 'a 'b 'c)) (roundtrips? m/S3-tilted (up 'a 'b 'c)) + (testing "S3-{spherical,tilted}" (is (= '(up (atan (sqrt @@ -463,23 +475,27 @@ (check-manifold-family m/SO3-type) (check-manifold m/SO3) - (testing "SO(3)" - (roundtrips? m/alternate-angles (up 'theta 'phi 'psi)) - (roundtrips? m/Euler-angles (up 'theta 'phi 'psi)) - - (is (= '(up theta phi psi) - (s-freeze - ((f/compose (m/chart m/Euler-angles) - (m/point m/alternate-angles) - (m/chart m/alternate-angles) - (m/point m/Euler-angles)) - (up 'theta 'phi 'psi))))) - - (is (= '(up (asin (* (sin theta) (cos psi))) - (atan (+ (* (sin phi) (cos theta) (cos psi)) (* (cos phi) (sin psi))) - (+ (* (cos theta) (cos phi) (cos psi)) (* -1 (sin phi) (sin psi)))) - (atan (* -1 (sin theta) (sin psi)) (cos theta))) - (s-freeze - ((f/compose (m/chart m/alternate-angles) - (m/point m/Euler-angles)) - (up 'theta 'phi 'psi))))))) +(testing "SO(3)" + (roundtrips? m/alternate-angles (up 'theta 'phi 'psi)) + (roundtrips? m/Euler-angles (up 'theta 'phi 'psi)) + + (is (= '(up theta phi psi) + (s-freeze + ((f/compose (m/chart m/Euler-angles) + (m/point m/alternate-angles) + (m/chart m/alternate-angles) + (m/point m/Euler-angles)) + (up 'theta 'phi 'psi))))) + + (is (= '(up (asin (* (sin theta) (cos psi))) + (atan (+ (* (sin phi) (cos theta) (cos psi)) (* (cos phi) (sin psi))) + (+ (* (cos theta) (cos phi) (cos psi)) (* -1 (sin phi) (sin psi)))) + (atan (* -1 (sin theta) (sin psi)) (cos theta))) + (s-freeze + ((f/compose (m/chart m/alternate-angles) + (m/point m/Euler-angles)) + (up 'theta 'phi 'psi))))) + + (is (thrown? #?(:clj java.lang.AssertionError :cljs js/Error) + (->> (up 0 0 0) + (m/coords->point m/Euler-angles)))))) diff --git a/test/emmy/calculus/map_test.cljc b/test/emmy/calculus/map_test.cljc index a85e849c..c12113f7 100644 --- a/test/emmy/calculus/map_test.cljc +++ b/test/emmy/calculus/map_test.cljc @@ -14,11 +14,10 @@ [emmy.expression :as x] [emmy.function :as f] [emmy.generic :as g :refer [+ - *]] - [emmy.structure :refer [up down]] - [emmy.value :as v])) + [emmy.structure :refer [up down]])) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest map-tests (testing "the basics: explanation of the connection between the basis forms @@ -177,12 +176,12 @@ and the differentials of coordinate functions." (testing "pullback" (is (= '(f (up (θ t) (φ t))) - (v/freeze + (g/freeze (((m/pullback μ) f) ((man/point R1-rect) 't))))) (is (= '((D θ) t) - (v/freeze + (g/freeze ((((m/pullback μ) dθ) d:dt) ((man/point R1-rect) 't))))) @@ -203,22 +202,22 @@ and the differentials of coordinate functions." (man/chart R3-rect)) R3-rect-point ((man/point R3-rect) (up 'x 'y 'z))] (is (= '(((partial 0) mu↑theta) (up x y z)) - (v/freeze + (g/freeze ((((m/pullback mu) dtheta) d:dx) R3-rect-point)))) (is (= '(((partial 1) mu↑theta) (up x y z)) - (v/freeze + (g/freeze ((((m/pullback mu) dtheta) d:dy) R3-rect-point)))) (is (= '(((partial 0) mu↑r) (up x y z)) - (v/freeze + (g/freeze ((((m/pullback mu) dr) d:dx) R3-rect-point)))) (is (= '(((partial 1) mu↑r) (up x y z)) - (v/freeze + (g/freeze ((((m/pullback mu) dr) d:dy) R3-rect-point)))) @@ -282,7 +281,7 @@ and the differentials of coordinate functions." ;; first pullback a function (let [f (f/compose (af/literal-function 'f R3-rect->R) R3-rect-chi)] - (is (= 0 (v/freeze + (is (= 0 (g/freeze (((- ((m/pullback mu) (ff/d f)) (ff/d ((m/pullback mu) f))) X2) @@ -292,7 +291,7 @@ and the differentials of coordinate functions." (is (= '(up (mu↑x (up u0 v0)) (mu↑y (up u0 v0)) (mu↑z (up u0 v0))) - (v/freeze + (g/freeze (R3-rect-chi (mu m2))))) (let [present (fn [expr] diff --git a/test/emmy/calculus/metric_test.cljc b/test/emmy/calculus/metric_test.cljc index f24e4257..5e6ee850 100644 --- a/test/emmy/calculus/metric_test.cljc +++ b/test/emmy/calculus/metric_test.cljc @@ -15,13 +15,12 @@ [emmy.matrix :as matrix] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.structure :as s :refer [up]] - [emmy.util.aggregate :as ua] - [emmy.value :as v])) + [emmy.util.aggregate :as ua])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest metric-tests (testing "Example: natural metric on a sphere of radius R" diff --git a/test/emmy/calculus/tensor_test.cljc b/test/emmy/calculus/tensor_test.cljc index d0954095..52350006 100644 --- a/test/emmy/calculus/tensor_test.cljc +++ b/test/emmy/calculus/tensor_test.cljc @@ -11,13 +11,12 @@ [emmy.calculus.vector-field :as vf] [emmy.generic :as g :refer [+ - *]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.util :as u] - [emmy.value :as v])) + [emmy.util :as u])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) ;; ## Testing a function for being a tensor field. diff --git a/test/emmy/calculus/vector_calculus_test.cljc b/test/emmy/calculus/vector_calculus_test.cljc index 3a6adbba..3c7435d2 100644 --- a/test/emmy/calculus/vector_calculus_test.cljc +++ b/test/emmy/calculus/vector_calculus_test.cljc @@ -11,13 +11,12 @@ [emmy.calculus.vector-calculus :as vc] [emmy.generic :as g :refer [+ - * / sin cos]] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest old-vector-calculus-tests (let [f (fn [[x y z]] diff --git a/test/emmy/calculus/vector_field_test.cljc b/test/emmy/calculus/vector_field_test.cljc index badad06d..8916e088 100644 --- a/test/emmy/calculus/vector_field_test.cljc +++ b/test/emmy/calculus/vector_field_test.cljc @@ -18,20 +18,20 @@ (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest vector-field-tests - (testing "v/zero-like, v/one-like, v/identity-like" + (testing "g/zero-like, g/one-like, g/identity-like" (let [vf (vf/literal-vector-field 'b R2-rect)] - (is (v/zero? (v/zero-like vf))) - (is (vf/vector-field? (v/zero-like vf))) - (is (= 'vf:zero (v/freeze - (v/zero-like vf)))) + (is (g/zero? (g/zero-like vf))) + (is (vf/vector-field? (g/zero-like vf))) + (is (= 'vf:zero (g/freeze + (g/zero-like vf)))) (testing "the returned identity keeps its context and `::vf/vector-field` status." - (is (vf/vector-field? (v/one-like vf))) - (is (vf/vector-field? (v/identity-like vf)))))) + (is (vf/vector-field? (g/one-like vf))) + (is (vf/vector-field? (g/identity-like vf)))))) (testing "with-coordinate-prototype" (let [A R2-rect @@ -59,13 +59,13 @@ (is (vf/vector-field? f)) (is (= '(up (- y0) x0) - (v/freeze + (g/freeze ((vf/vector-field->components circular R2-rect) (up 'x0 'y0))))) (is (= '(up (f↑0 (up x0 y0)) (f↑1 (up x0 y0))) - (v/freeze + (g/freeze ((vf/vector-field->components f R2-rect) (up 'x0 'y0)))))))) @@ -116,7 +116,7 @@ (is (= '(+ (* x d:dx) (* y d:dy)) - (v/freeze + (g/freeze (vf/basis-components->vector-field (up x y) vb)))) (let [vf (vf/basis-components->vector-field (up x y) vb) diff --git a/test/emmy/collection_test.cljc b/test/emmy/collection_test.cljc index 56ee23d4..a553e0c7 100644 --- a/test/emmy/collection_test.cljc +++ b/test/emmy/collection_test.cljc @@ -22,65 +22,65 @@ (is (= [:between 1 2] (f/arity v)) "vectors respond to f/arity correctly")) - (checking "v/zero-like" 100 + (checking "g/zero-like" 100 [v (gen/vector sg/number)] - (let [zero-v (v/zero-like v)] + (let [zero-v (g/zero-like v)] (is (vector? zero-v) "still a vector!") - (is (v/zero? zero-v) + (is (g/zero? zero-v) "zero? works") - (is (every? v/zero? zero-v) + (is (every? g/zero? zero-v) "zero-like zeros out all values."))) (checking "v/kind, one?, identity?" 100 [v (gen/vector sg/any-integral)] - (is (not (v/one? v)) + (is (not (g/one? v)) "no vector is a multiplicative identity.") - (is (not (v/identity? v)) + (is (not (g/identity? v)) "no vector is a multiplicative identity!") (is (= (v/kind v) (type v)) "Kind reflects type back out.")) - (testing "v/one-like, v/identity-like return 1, the multiplicative identity for vectors" - (is (= 1 (v/one-like [1 2 3]))) - (is (= 1 (v/identity-like [1 2 3]))) + (testing "g/one-like, g/identity-like return 1, the multiplicative identity for vectors" + (is (= 1 (g/one-like [1 2 3]))) + (is (= 1 (g/identity-like [1 2 3]))) - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/identity-like {:k "v"})))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/identity-like {:k "v"})))) - (checking "v/exact?" 100 + (checking "g/exact?" 100 [v (gen/vector sg/any-integral)] - (is (v/exact? v) + (is (g/exact? v) "all integral values == exact vector") - (is (not (v/exact? (conj v 1.5))) + (is (not (g/exact? (conj v 1.5))) "conj-ing an inexact value removes the exact? designation")) - (testing "v/freeze" + (testing "g/freeze" (is (= '(up (/ 1 2)) - (v/freeze [#emmy/ratio 1/2])) - "v/freeze freezes entries")))) + (g/freeze [#emmy/ratio 1/2])) + "g/freeze freezes entries")))) (deftest sequence-tests (testing "sequence protocol impls" - (let [zeros (v/zero-like (range 10))] + (let [zeros (g/zero-like (range 10))] (is (seq? zeros) "The output is indeed a seq, not a vector.") - (is (every? v/zero? zeros) - "v/zero-like lazily zeroes all entries") + (is (every? g/zero? zeros) + "g/zero-like lazily zeroes all entries") - (is (not (v/zero? zeros)) + (is (not (g/zero? zeros)) "to return true, this predicate would have to realize the full sequence... so instead it returns false.") - (is (every? v/zero? (v/zero-like (map inc (range 10)))) + (is (every? g/zero? (g/zero-like (map inc (range 10)))) "works with a non-Range type") - (is (every? v/zero? (v/zero-like (list 1 2 3))) + (is (every? g/zero? (g/zero-like (list 1 2 3))) "works with lists")))) (defrecord MyRecord []) @@ -109,7 +109,7 @@ (g/* x m1)) "mult pushes into values") - (when-not (v/zero? x) + (when-not (g/zero? x) (is (ish? (u/map-vals #(g// % x) m1) (g/divide m1 x)) "division by scalar pushes into values")) @@ -135,7 +135,7 @@ (is (= m (g/make-polar m {})) "make-polar with no angles is identity.") - (is (ish? (v/zero-like m) + (is (ish? (g/zero-like m) (g/make-polar {} m)) "if all angles comes from m, but every radius is 0, then the resulting entries will be zero.") @@ -143,7 +143,7 @@ (is (= m (g/real-part m)) "real-part on all real is id.") - (is (ish? (v/zero-like m) + (is (ish? (g/zero-like m) (g/imag-part m)) "imag-part on all real is zeor-like.") @@ -178,7 +178,7 @@ (testing "sorted map" (let [m (sorted-map 1 2 3 4)] (is (= (type m) (v/kind m)) - "This would fail without special handling when the `Value` + "This would fail without special handling when the `IKind` implementation attempts to pass a `:type` keyword to a sorted map containing numbers, since keywords and numbers don't compare."))) @@ -186,48 +186,48 @@ (is (= [:between 1 2] (f/arity m)) "maps respond to f/arity correctly")) - (checking "v/zero-like" 100 + (checking "g/zero-like" 100 [m (gen/map gen/keyword sg/number)] - (let [zero-m (v/zero-like m)] - (is (v/zero? zero-m) + (let [zero-m (g/zero-like m)] + (is (g/zero? zero-m) "zero? works") - (is (every? v/zero? (vals zero-m)) + (is (every? g/zero? (vals zero-m)) "zero-like zeros out all values.") (is (= (u/keyset m) (u/keyset zero-m)) "The keyset is identical after zeroing."))) (checking "v/kind, one?, identity?" 100 [m (gen/map gen/keyword sg/any-integral)] - (is (not (v/one? m)) + (is (not (g/one? m)) "no map is a multiplicative identity.") - (is (not (v/identity? m)) + (is (not (g/identity? m)) "no map is a multiplicative identity.") (is (isa? (v/kind m) ::collection/map) "All maps inherit from this new keyword. TODO should this in value, with ::v/function and friends?")) - (testing "v/one-like, v/identity-like throw" - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/one-like {:k "v"}))) + (testing "g/one-like, g/identity-like throw" + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/one-like {:k "v"}))) - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/identity-like {:k "v"})))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/identity-like {:k "v"})))) - (checking "v/exact?" 100 + (checking "g/exact?" 100 [m (gen/map gen/keyword sg/any-integral)] - (is (v/exact? m) + (is (g/exact? m) "all integral values == exact map") - (is (not (v/exact? (assoc m :key 1.5))) + (is (not (g/exact? (assoc m :key 1.5))) "adding an inexact key removes the exact? designation")) - (testing "v/freeze" + (testing "g/freeze" (is (= {:ratio '(/ 1 2)} - (v/freeze {:ratio #emmy/ratio 1/2})) - "v/freeze freezes values")) + (g/freeze {:ratio #emmy/ratio 1/2})) + "g/freeze freezes values")) (testing "v/= on collections" #?(:cljs @@ -281,39 +281,48 @@ (laws/additive-monoid 100 set-gen "Set" :commutative? true)) - (testing "Set protocol implementations" - (checking "f/arity" 100 [s (gen/set gen/any-equatable)] - (is (= [:between 1 2] (f/arity s)) - "sets respond to f/arity correctly")) - - (checking "v/zero-like works" 100 - [s (gen/set sg/number)] - (let [zero-s (v/zero-like s)] - (is (v/zero? zero-s)))) - - (checking "v/kind, v/one?, v/identity?" 100 [s (gen/set sg/any-integral)] - (is (not (v/one? s)) - "no map is a multiplicative identity.") - - (is (not (v/identity? s)) - "no map is a multiplicative identity.") - - (is (isa? (v/kind s) ::collection/set) - "All sets inherit from this new keyword.")) - - (testing "v/one-like, v/identity-like throw" - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/one-like #{"v"}))) + (checking "f/arity" 100 [s (gen/set gen/any-equatable)] + (is (= [:between 1 2] (f/arity s)) + "sets respond to f/arity correctly")) - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/identity-like #{"V"})))) - (checking "v/exact?" 100 - [m (gen/set sg/any-integral)] - (is (not (v/exact? m)) - "sets aren't exact.")) - - (testing "v/freeze currently throws, since we don't have a way of rendering + (testing "Set protocol implementations" + (doseq [generator [(gen/set gen/any-equatable) + (gen/sorted-set gen/small-integer)]] + + (checking "g/zero-like works" 100 + [s generator] + (let [zero-s (g/zero-like s)] + (is (g/zero? zero-s)))) + + (checking "v/kind, g/one?, g/identity?" 100 + [s generator] + (is (not (g/one? s)) + "no map is a multiplicative identity.") + + (is (not (g/identity? s)) + "no map is a multiplicative identity.") + + (is (isa? (v/kind s) ::collection/set) + "All sets inherit from this new keyword.")) + + (testing "g/one-like, g/identity-like throw" + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/one-like #{"v"}))) + + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/identity-like #{"V"}))))) + + (doseq [generator [(gen/set gen/small-integer) + (gen/sorted-set gen/small-integer)]] + (checking "g/exact?" 100 + [s generator] + (is (g/exact? s) + "all exact values == exact set") + (is (not (g/exact? (conj s 1.5))) + "adding an inexact key removes the exact? designation"))) + + (testing "g/freeze currently throws, since we don't have a way of rendering it or simplifying." - (is (thrown? #?(:clj UnsupportedOperationException :cljs js/Error) - (v/freeze #{"v"})))))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (g/freeze #{"v"})))))) diff --git a/test/emmy/complex_test.cljc b/test/emmy/complex_test.cljc index b2360bf4..30c51311 100644 --- a/test/emmy/complex_test.cljc +++ b/test/emmy/complex_test.cljc @@ -51,54 +51,56 @@ (with-comparator (v/within 1e-3) (l/field 100 sg/complex "Complex"))) -(deftest value-protocol - (testing "v/Value protocol implementation" +(deftest generic-functions + (testing "generic function implementation (formerly Value)" (is (every? - v/zero? + g/zero? [(c/complex -0.0 -0.0) (c/complex 0.0 -0.0) (c/complex -0.0 0.0) (c/complex 0.0 0.0) - (v/zero-like c/ONE) - (v/zero-like (c/complex 100)) + (g/zero-like c/ONE) + (g/zero-like (c/complex 100)) c/ZERO #emmy/complex "0"]) "negative zero doesn't affect zero") - (is (not (v/zero? c/ONE))) - (is (not (v/zero? (c/complex 1.0)))) - (is (= c/ZERO (v/zero-like (c/complex 2)))) - (is (= c/ZERO (v/zero-like #emmy/complex "0 + 3.14i"))) + (is (not (g/zero? c/ONE))) + (is (not (g/zero? (c/complex 1.0)))) + (is (= c/ZERO (g/zero-like (c/complex 2)))) + (is (= c/ZERO (g/zero-like #emmy/complex "0 + 3.14i"))) (let [ones [c/ONE (c/complex 1.0) - (v/one-like c/ZERO) + (g/one-like c/ZERO) (c/complex 1.0 0.0) (c/complex 1.0 -0.0)]] - (is (every? v/one? ones) + (is (every? g/one? ones) "-0 in imaginary does not affect one?") - (is (every? v/identity? ones) + (is (every? g/identity? ones) "-0 in imaginary does not affect identity?")) - (is (not (v/one? (c/complex 2)))) - (is (not (v/one? (c/complex 0.0)))) + (is (not (g/one? (c/complex 2)))) + (is (not (g/one? (c/complex 0.0)))) - (is (= 10.0 (v/freeze (c/complex 10))) + (is (= 10.0 (g/freeze (c/complex 10))) "If the imaginary piece is 0, freeze will return only the real part.") (is (v/numerical? (c/complex 10))) (testing "exact?" - (is (not (v/exact? (c/complex 0 10.1)))) + (is (not (g/exact? (c/complex 0 10.1)))) ;; cljs is able to maintain exact numbers here. #?@(:clj - [(is (not (v/exact? (c/complex 10)))) - (is (not (v/exact? (c/complex 10 12))))] + [(is (not (g/exact? (c/complex 10)))) + (is (not (g/exact? (c/complex 10 12))))] :cljs - [(is (v/exact? (c/complex 10))) - (is (v/exact? (c/complex 10 12)))])))) + [(is (g/exact? (c/complex 10))) + (is (g/exact? (c/complex 10 12))) + (is (not (g/exact? (c/complex 10.1)))) + (is (not (g/exact? (c/complex 10 12.1))))])))) (let [pi Math/PI] (deftest complex-numbers @@ -284,7 +286,7 @@ (g/negate (c/complex 10 -2))))) (testing "invert" - (is (v/zero? (g/add c/I (g/invert c/I))))) + (is (g/zero? (g/add c/I (g/invert c/I))))) (testing "abs" (is (= 5.0 (g/abs (c/complex 3 4))))) @@ -353,9 +355,9 @@ (letfn [(check [l r] (let [z (g/gcd l r)] - (if (v/zero? z) - (is (and (v/zero? l) - (v/zero? r))) + (if (g/zero? z) + (is (and (g/zero? l) + (g/zero? r))) (is (fourth-power-is-one? (g/gcd (g// l z) (g// r z)))))))] @@ -364,8 +366,8 @@ (let [gaussian-l (c/round l) gaussian-r (c/round r) z (g/gcd gaussian-l gaussian-r)] - (when-not (or (v/zero? gaussian-l) - (v/zero? gaussian-r)) + (when-not (or (g/zero? gaussian-l) + (g/zero? gaussian-r)) (is (not (neg? (g/real-part z))) "real part of the GCD is always positive, unless either side to gcd is 0.")) diff --git a/test/emmy/differential_test.cljc b/test/emmy/differential_test.cljc index 26905a10..991ebbf5 100644 --- a/test/emmy/differential_test.cljc +++ b/test/emmy/differential_test.cljc @@ -28,7 +28,7 @@ (defn nonzero [gen] (gen/fmap (fn [x] (if (= x 0) - (v/one-like x) + (g/one-like x) x)) gen)) @@ -79,6 +79,7 @@ (is (= [] (#'d/terms:* l r)))))) (deftest differential-type-tests + (defmethod g/zero? [#?(:clj String :cljs js/String)] [_] false) (testing "v/numerical? special cases" (is (not (v/numerical? (d/from-terms {[] "face"})))) (is (v/numerical? (d/->Differential [])) @@ -118,35 +119,35 @@ (testing "value protocol implementation" (let [zero (d/->Differential []) dy (d/from-terms {[] 0, [1] 1})] - (is (v/zero? zero) + (is (g/zero? zero) "zero? returns true for an empty term list") - (is (v/zero? (d/from-terms [(d/make-term [] 0)])) + (is (g/zero? (d/from-terms [(d/make-term [] 0)])) "zero? returns true for an explicit zero") - (is (not (v/zero? dy)) - "the finite term is 0, but `v/zero?` fails if any perturbation is + (is (not (g/zero? dy)) + "the finite term is 0, but `g/zero?` fails if any perturbation is non-zero.") (is (= dy 0) "subtly, `dy` IS in fact equal to zero; this can be used for control flow.") - (testing "v/one? only responds true to a one primal if all tangents are zero." - (is (v/one? (d/from-terms {[] 1}))) - (is (v/one? (d/from-terms {[] 1 [1] 0}))) - (is (not (v/one? (d/from-terms {[] 1 [1] 1}))))) + (testing "g/one? only responds true to a one primal if all tangents are zero." + (is (g/one? (d/from-terms {[] 1}))) + (is (g/one? (d/from-terms {[] 1 [1] 0}))) + (is (not (g/one? (d/from-terms {[] 1 [1] 1}))))) - (testing "v/identity? only responds true to an `identity` primal if all + (testing "g/identity? only responds true to an `identity` primal if all tangents are zero." - (is (v/identity? (d/from-terms {[] 1}))) - (is (v/identity? (d/from-terms {[] 1 [1] 0}))) - (is (not (v/identity? (d/from-terms {[] 1 [1] 1}))))) + (is (g/identity? (d/from-terms {[] 1}))) + (is (g/identity? (d/from-terms {[] 1 [1] 0}))) + (is (not (g/identity? (d/from-terms {[] 1 [1] 1}))))) (checking "*-like works" 100 [diff real-diff-gen] - (is (v/zero? (v/zero-like diff))) - (is (v/one? (v/one-like diff))) - (is (v/identity? (v/identity-like diff)))) + (is (g/zero? (g/zero-like diff))) + (is (g/one? (g/one-like diff))) + (is (g/identity? (g/identity-like diff)))) (testing "equality, comparison" (checking "g/negative?, g/infinite?" 100 [x sg/real] @@ -234,13 +235,13 @@ (is (= '[Differential [[] (expt x 4)] [[0] (* 2 (expt x 2) 2 x)]] - (v/freeze not-simple)) + (g/freeze not-simple)) "A frozen differential freezes each entry") (is (= '[Differential [[] (expt x 4)] [[0] (* 4 (expt x 3))]] - (v/freeze + (g/freeze (g/simplify not-simple))) "simplify simplifies each tangent term") @@ -367,11 +368,11 @@ (is (d/eq (d/from-terms {[] 'k [0] 1}) (d/d:+ dx 'k)))) (testing "various ways to get to zero" - (is (v/zero? (d/d:+ dx -dx))) - (is (v/zero? (d/d:+ -dx dx))) - (is (v/zero? (d/d:* dx 0))) - (is (v/zero? (d/d:* 0 dx))) - (is (v/zero? (g/* dx dx)))) + (is (g/zero? (d/d:+ dx -dx))) + (is (g/zero? (d/d:+ -dx dx))) + (is (g/zero? (d/d:* dx 0))) + (is (g/zero? (d/d:* 0 dx))) + (is (g/zero? (g/* dx dx)))) (testing "associative, commutative multiplication" (is (d/eq dxdy (d/d:* dx dy))) @@ -380,9 +381,9 @@ (is (d/eq dxdydz (d/d:* (d/d:* dy dz) dx)))) (testing "infinitesimals go to zero when multiplied!" - (is (v/zero? (d/d:* dx dx)) + (is (g/zero? (d/d:* dx dx)) "dx^2==0") - (is (v/zero? (d/d:* dz (d/d:* dy dz))) + (is (g/zero? (d/d:* dz (d/d:* dy dz))) "dy*dz^2==0")))) (checking "(a/b)*b == a, (a*b)/b == a" 100 @@ -549,7 +550,7 @@ (do (is (zero? ((derivative g/floor) x))) (is (zero? ((derivative g/ceiling) x))) (is (zero? ((derivative g/integer-part) x))) - (is (v/one? ((derivative g/fractional-part) x))))))) + (is (g/one? ((derivative g/fractional-part) x))))))) (testing "lift-n" (let [* (d/lift-n g/* (fn [_] 1) (fn [_ y] y) (fn [x _] x)) diff --git a/test/emmy/examples/double_pendulum_test.cljc b/test/emmy/examples/double_pendulum_test.cljc index c741df92..f2378f94 100644 --- a/test/emmy/examples/double_pendulum_test.cljc +++ b/test/emmy/examples/double_pendulum_test.cljc @@ -8,8 +8,8 @@ [emmy.examples.double-pendulum :as double] [emmy.expression.analyze :as a] [emmy.expression.compile :as c] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) @@ -21,7 +21,7 @@ (is (= '(+ (* -1 g l1 m1 (cos θ)) (* -1 g l1 m2 (cos θ)) (* -1 g l2 m2 (cos φ))) - (v/freeze + (g/freeze (e/simplify (V state))))) (is (= '(+ (* l1 l2 m2 θdot φdot (cos (+ θ (* -1 φ)))) @@ -29,7 +29,7 @@ (* (/ 1 2) (expt l1 2) m1 (expt θdot 2)) (* (/ 1 2) (expt l1 2) m2 (expt θdot 2)) (* (/ 1 2) (expt l2 2) m2 (expt φdot 2))) - (v/freeze + (g/freeze (e/simplify (T state))))) (is (= '(+ (* l1 l2 m2 θdot φdot (cos (+ θ (* -1 φ)))) (* (/ 1 2) (expt l1 2) m1 (expt θdot 2)) @@ -38,7 +38,7 @@ (* g l1 m1 (cos θ)) (* g l1 m2 (cos θ)) (* g l2 m2 (cos φ))) - (v/freeze + (g/freeze (e/simplify (L state))))) (e/with-literal-functions [θ φ] @@ -53,7 +53,7 @@ (* l1 l2 m2 (cos (+ (θ t) (* -1 (φ t)))) (((expt D 2) θ) t)) (* g l2 m2 (sin (φ t))) (* (expt l2 2) m2 (((expt D 2) φ) t)))) - (v/freeze + (g/freeze (e/simplify (((e/Lagrange-equations (double/L 'm1 'm2 'l1 'l2 'g)) (up θ φ)) diff --git a/test/emmy/examples/pendulum_test.cljc b/test/emmy/examples/pendulum_test.cljc index be1cc9b1..3d221b16 100644 --- a/test/emmy/examples/pendulum_test.cljc +++ b/test/emmy/examples/pendulum_test.cljc @@ -4,15 +4,15 @@ (:require [clojure.test :refer [is deftest use-fixtures]] [emmy.env :refer [up simplify]] [emmy.examples.pendulum :as p] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (deftest simple-pendulum (is (= '(+ (* (/ 1 2) (expt l 2) m (expt thetadot 2)) (* g l m (cos theta))) - (v/freeze + (g/freeze (simplify ((p/L 'm 'l 'g (fn [_t] (up 0 0))) (up 't 'theta 'thetadot))))))) diff --git a/test/emmy/expression_test.cljc b/test/emmy/expression_test.cljc index 550a29f7..f2179330 100644 --- a/test/emmy/expression_test.cljc +++ b/test/emmy/expression_test.cljc @@ -6,30 +6,51 @@ [com.gfredericks.test.chuck.clojure-test :refer [checking]] [emmy.abstract.number :as an] [emmy.expression :as e] - [emmy.generic :as g] - [emmy.value :as v])) + [emmy.generic :as g])) + +(defn- unimplemented? + "Returns true if applying method to value results in an exception + indicating the absence of that method." + [method value] + (try + (method value) + false + (catch #?(:clj IllegalArgumentException :cljs js/Error) e + (re-find #"No method in multimethod" (str e))))) (deftest expressions (testing "value protocol impl" - (is (v/zero? (e/make-literal ::blah 0))) - (is (v/one? (e/make-literal ::blah 1))) - (is (v/identity? (e/make-literal ::blah 1))) - - (is (not (v/zero? (e/make-literal ::blah 10)))) - (is (v/zero? (v/zero-like (e/make-literal ::blah 10)))) - - (is (not (v/one? (e/make-literal ::blah 10)))) - (is (v/one? (v/one-like (e/make-literal ::blah 10)))) - - (is (not (v/identity? (e/make-literal ::blah 10)))) - (is (v/identity? (v/identity-like (e/make-literal ::blah 10)))) - - (is (not (v/exact? (e/make-literal ::blah 10.5)))) - (is (v/exact? (e/make-literal ::blah 10))) + ;; Things produced by make-literal have the `kind` supplied at creation + ;; time. Outside of the builtin kind(s), nothing is known about the zero-nature + ;; (etc.) of such objects. To inherit the default behavior, derive from :e/numeric + (derive ::blah-derived ::e/numeric) + (is (isa? ::blah-derived ::e/numeric)) + (is (unimplemented? g/zero? (e/make-literal ::blah 0))) + (is (g/zero? (e/make-literal ::blah-derived 0))) + (is (g/one? (e/make-literal ::blah-derived 1))) + (is (g/identity? (e/make-literal ::blah-derived 1))) + + (is (unimplemented? g/one? (e/make-literal ::blah 1))) + (is (unimplemented? g/identity? (e/make-literal ::blah 1))) + (is (unimplemented? g/zero? (e/make-literal ::blah 10))) + (is (not (g/zero? (e/make-literal ::blah-derived 10)))) + (is (g/zero? (g/zero-like (e/make-literal ::blah-derived 10)))) + + (is (not (g/one? (e/make-literal ::blah-derived 10)))) + (is (g/one? (g/one-like (e/make-literal ::blah-derived 10)))) + (is (unimplemented? g/one-like (e/make-literal ::blah 10))) + + (is (not (g/identity? (e/make-literal ::blah-derived 10)))) + (is (g/identity? (g/identity-like (e/make-literal ::blah-derived 10)))) + (is (unimplemented? g/identity-like (e/make-literal ::blah 10))) + + (is (unimplemented? g/exact? (e/make-literal ::blah 10))) + (is (not (g/exact? (e/make-literal ::blah-derived 10.5)))) + (is (g/exact? (e/make-literal ::blah-derived 10))) (is (= '(sin 1 2 3) - (v/freeze - (e/literal-apply ::blah 'sin [1 2 3])))) + (g/freeze + (e/literal-apply ::blah-derived 'sin [1 2 3])))) (is (e/literal? (e/literal-apply ::blah 'sin [1 2 3]))) @@ -140,10 +161,10 @@ (testing "for types that don't play nice we resort to hashing." (is (= -1 (e/compare '(+ x y) #emmy/complex "1+2i"))) - (is (= 1 (e/compare #emmy/complex "1+2i" '(+ x y))))) + (is (= 1 (e/compare #emmy/complex "1+2i" '(+ x y))))))) ;; TODO add more tests as we start to explore this function. -)) + (deftest string-form-test (let [expr (g/+ 'x 'x)] diff --git a/test/emmy/fdg/bianchi_test.cljc b/test/emmy/fdg/bianchi_test.cljc index 833485be..77b2649a 100644 --- a/test/emmy/fdg/bianchi_test.cljc +++ b/test/emmy/fdg/bianchi_test.cljc @@ -4,13 +4,13 @@ (:refer-clojure :exclude [+ - * /]) (:require [clojure.test :refer [is deftest testing use-fixtures]] [emmy.env :as e :refer [+ -]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (defn cyclic-sum [f] (fn [x y z] diff --git a/test/emmy/fdg/ch10_test.cljc b/test/emmy/fdg/ch10_test.cljc index 474ca2a2..70749c04 100644 --- a/test/emmy/fdg/ch10_test.cljc +++ b/test/emmy/fdg/ch10_test.cljc @@ -10,13 +10,13 @@ down up point define-coordinates]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (def spherical e/R3-rect) (define-coordinates [r theta phi] spherical) diff --git a/test/emmy/fdg/ch11_test.cljc b/test/emmy/fdg/ch11_test.cljc index d6edc262..4ed9ad55 100644 --- a/test/emmy/fdg/ch11_test.cljc +++ b/test/emmy/fdg/ch11_test.cljc @@ -9,13 +9,13 @@ up rotate-x rotate-y rotate-z point chart]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (deftest ch11-tests (testing "Implementation, p175" diff --git a/test/emmy/fdg/ch1_test.cljc b/test/emmy/fdg/ch1_test.cljc index efca75a4..600f3171 100644 --- a/test/emmy/fdg/ch1_test.cljc +++ b/test/emmy/fdg/ch1_test.cljc @@ -10,13 +10,13 @@ R2-rect chart point define-coordinates]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (defn Lfree [mass] diff --git a/test/emmy/fdg/ch3_test.cljc b/test/emmy/fdg/ch3_test.cljc index fae02615..48505c84 100644 --- a/test/emmy/fdg/ch3_test.cljc +++ b/test/emmy/fdg/ch3_test.cljc @@ -11,8 +11,8 @@ square exp point chart define-coordinates]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) @@ -96,7 +96,7 @@ omega2 (e/literal-oneform-field 'a R2-rect) circular (- (* x d:dy) (* y d:dx))] (is (= '(oneform-field (down a_0 a_1)) - (v/freeze omega)) + (g/freeze omega)) "TODO - why does this freeze into this form?") (testing "page 35, with and without literal-oneform-field shorthand" diff --git a/test/emmy/fdg/ch7_test.cljc b/test/emmy/fdg/ch7_test.cljc index 16afef01..4ddd0d0a 100644 --- a/test/emmy/fdg/ch7_test.cljc +++ b/test/emmy/fdg/ch7_test.cljc @@ -11,6 +11,7 @@ R2-rect R2-polar R3-rect R1-rect S2-spherical let-coordinates]] + [emmy.generic :as g] [emmy.operator :as o] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.value :as v] @@ -19,7 +20,7 @@ (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (defn F->directional-derivative [F] @@ -171,7 +172,7 @@ (fn [f] (fn [m] (let [m0 (((phi v) (- delta)) m) - Aij (+ (v/one-like ((omega v) m0)) + Aij (+ (g/one-like ((omega v) m0)) (* delta (- ((omega v) m0)))) ui ((etilde u) m0)] (* ((e f) m) diff --git a/test/emmy/fdg/ch8_test.cljc b/test/emmy/fdg/ch8_test.cljc index d9aec1d9..dea2c907 100644 --- a/test/emmy/fdg/ch8_test.cljc +++ b/test/emmy/fdg/ch8_test.cljc @@ -5,19 +5,19 @@ (:require [clojure.test :refer [is deftest testing use-fixtures]] [emmy.calculus.curvature-test :refer [S2-Christoffel]] [emmy.env :as e :refer [+ - * / sin zero? - D partial - up - point chart - R2-rect R2-polar - define-coordinates]] + D partial + up + point chart + R2-rect R2-polar + define-coordinates]] + [emmy.generic :as g] [emmy.operator :as o] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (define-coordinates [theta phi] e/S2-spherical) @@ -171,7 +171,7 @@ (fn [_ theta Delta-phi] (* R (sin theta) Delta-phi)))] (is (= '(* Delta-phi R (cos theta0)) - (v/freeze + (g/freeze (((partial 1) (delta 'R)) 'phi0 'theta0 'Delta-phi)))) (let [phi-hat (* (/ 1 (sin theta)) d:dphi)] diff --git a/test/emmy/fdg/ch9_test.cljc b/test/emmy/fdg/ch9_test.cljc index b914d2bc..040c9132 100644 --- a/test/emmy/fdg/ch9_test.cljc +++ b/test/emmy/fdg/ch9_test.cljc @@ -11,13 +11,13 @@ point R2-rect R3-rect define-coordinates]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) (define-coordinates [theta phi] e/S2-spherical) (define-coordinates [t x y z] e/spacetime-rect) diff --git a/test/emmy/fdg/einstein_test.cljc b/test/emmy/fdg/einstein_test.cljc index 13069d1d..b2c1a26f 100644 --- a/test/emmy/fdg/einstein_test.cljc +++ b/test/emmy/fdg/einstein_test.cljc @@ -11,13 +11,13 @@ spacetime-rect spacetime-sphere compose square point up let-coordinates]] - [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.simplify :refer [hermetic-simplify-fixture]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze e/simplify)) + (comp g/freeze e/simplify)) ;; Einstein Field Equations diff --git a/test/emmy/function_test.cljc b/test/emmy/function_test.cljc index 4bf11ecd..704719d6 100644 --- a/test/emmy/function_test.cljc +++ b/test/emmy/function_test.cljc @@ -11,23 +11,26 @@ [emmy.value :as v] [same.core :refer [ish? with-comparator]])) + +(defmulti unknown-multifn-for-test identity) + (deftest value-protocol-tests - (testing "v/zero? returns false for fns" - (is (not (v/zero? neg?))) - (is (not (v/zero? #'neg?))) - (is (not (v/zero? g/add)))) - - (testing "v/one? returns false for fns" - (is (not (v/one? neg?))) - (is (not (v/one? #'neg?))) - (is (not (v/one? g/add))) - (is (not (v/one? identity)))) - - (testing "v/identity? returns false for fns" - (is (not (v/identity? neg?))) - (is (not (v/identity? #'neg?))) - (is (not (v/identity? g/add))) - (is (not (v/identity? identity)) + (testing "g/zero? returns false for fns" + (is (not (g/zero? neg?))) + (is (not (g/zero? #'neg?))) + (is (not (g/zero? g/add)))) + + (testing "g/one? returns false for fns" + (is (not (g/one? neg?))) + (is (not (g/one? #'neg?))) + (is (not (g/one? g/add))) + (is (not (g/one? identity)))) + + (testing "g/identity? returns false for fns" + (is (not (g/identity? neg?))) + (is (not (g/identity? #'neg?))) + (is (not (g/identity? g/add))) + (is (not (g/identity? identity)) "We go conservative and say that EVEN the actual identity function is not identity.")) (testing "v/numerical? returns false for fns" @@ -37,40 +40,52 @@ (is (not (v/numerical? identity)))) (checking "zero-like, one-like returns 0, 1 for fns, vars" 100 - [f (gen/elements [g/negative? g/abs g/sin g/cos - #'g/negative? #'g/abs #'g/sin #'g/cos]) + [f (gen/elements [g/abs g/sin g/cos + #'g/abs #'g/sin #'g/cos]) n sg/real] - (is (== 0 ((v/zero-like f) n))) - (is (== 1 ((v/one-like f) n)))) + (is (== 0 ((g/zero-like f) n))) + (is (== 1 ((g/one-like f) n)))) + + (checking "zero-like, one-like returns false, true for boolean-valued fns, vars" 100 + [f (gen/elements [g/negative? g/zero? g/one? + #'g/negative? #'g/zero? #'g/one?]) + n sg/real] + (is (= false ((g/zero-like f) n))) + (is (= true ((g/one-like f) n)))) (checking "identity-like returns the identity fn" 100 [f (gen/elements [g/negative? g/abs g/sin g/cos #'g/negative? #'g/abs #'g/sin #'g/cos]) n sg/real] - (is (= n ((v/identity-like f) n)))) + (is (= n ((g/identity-like f) n)))) (checking "exact? mirrors input" 100 [n sg/real] - (if (v/exact? n) - (is ((v/exact? identity) n)) - (is (not ((v/exact? identity) n))))) + (if (g/exact? n) + (is ((g/exact? identity) n)) + (is (not ((g/exact? identity) n))))) - (testing "v/freeze" + (testing "g/freeze" (is (= ['+ '- '* '/ 'modulo 'quotient 'remainder 'negative? '< '<= '> '>= '= 'partial-derivative] - (map v/freeze [+ - * / mod quot rem + (map g/freeze [+ - * / mod quot rem neg? < <= > >= = g/partial-derivative])) "Certain functions freeze to symbols") - (is (= (map v/freeze [g/+ g/- g/* g// + (is (= (map g/freeze [g/+ g/- g/* g// g/modulo g/quotient g/remainder g/negative?]) - (map v/freeze [+ - * / mod quot rem neg?])) + (map g/freeze [+ - * / mod quot rem neg?])) "These freeze to the same symbols as their generic counterparts.") + (is (= (map g/freeze [#'g/+ #'g/- #'g/* #'g//]) '(+ - * /)) + "vars freeze to their referents") + (let [f (fn [x] (* x x))] - (is (= f (v/freeze f)) - "Unknown functions freeze to themselves"))) + (is (= f (g/freeze f)) + "Unknown functions freeze to themselves") + (is (= unknown-multifn-for-test (g/freeze unknown-multifn-for-test)) + "Unknown multifns (without :name keyword method) freeze to themselves"))) (testing "v/kind returns ::v/function" (is (= ::v/function (v/kind neg?))) @@ -312,12 +327,12 @@ (with-comparator (v/within 1e-10) (checking "tan, sin, cos" 100 [n sg/real] (let [f (g/- g/tan (g/div g/sin g/cos))] - (when-not (v/zero? n) + (when-not (g/zero? n) (is (ish? 0 (f n)))))) (checking "cot" 100 [n sg/real] (let [f (g/- g/cot (g/invert g/tan))] - (when-not (v/zero? n) + (when-not (g/zero? n) (is (ish? 0 (f n))))))) (checking "tanh" 100 [n (sg/reasonable-double {:min -100 :max 100})] @@ -330,7 +345,7 @@ (checking "csc" 100 [n sg/real] (let [f (g/- (g/invert g/sin) g/csc)] - (when-not (v/zero? n) + (when-not (g/zero? n) (is (ish? 0 (f n)))))) (checking "sech" 100 [n sg/real] @@ -358,7 +373,7 @@ (checking "atanh" 100 [n (sg/reasonable-double {:min -10 :max 10})] - (when-not (v/one? (g/abs n)) + (when-not (g/one? (g/abs n)) (let [f (f/compose g/tanh g/atanh)] (is (ish? n (f n)))))))) @@ -406,13 +421,13 @@ (is (= 20 ((g/determinant *) 4 5)))) (checking "invert" 100 [n sg/real] - (when-not (v/zero? n) + (when-not (g/zero? n) (is (= ((g/+ 1 g/invert) n) (g/+ 1 (g/invert n)))))) (checking "negative?" 100 [n sg/real] (is (not ((g/negative? g/abs) n))) - (when-not (v/zero? n) + (when-not (g/zero? n) (is ((g/negative? (f/compose g/negate g/abs)) n)))) (checking "abs" 100 [n sg/real] @@ -421,13 +436,13 @@ (checking "quotient" 100 [l sg/any-integral r sg/any-integral] - (when-not (v/zero? r) + (when-not (g/zero? r) (is (= ((g/+ 1 g/quotient) l r) (g/+ 1 (g/quotient l r)))))) (checking "exact-divide" 100 [n (gen/choose -200 200) m (gen/choose -20 20)] - (when-not (v/zero? m) + (when-not (g/zero? m) (is (= n ((g/exact-divide g/* m) n m)) "The f position here is a function that takes 2 elements, passes them to g/*, the calls exact-divide on the result and @@ -440,13 +455,13 @@ (checking "remainder" 100 [l sg/any-integral r sg/any-integral] - (when-not (v/zero? r) + (when-not (g/zero? r) (is (= ((g/+ 1 g/remainder) l r) (g/+ 1 (g/remainder l r)))))) (checking "modulo" 100 [l sg/any-integral r sg/any-integral] - (when-not (v/zero? r) + (when-not (g/zero? r) (is (= ((g/+ 1 g/modulo) l r) (g/+ 1 (g/modulo l r)))))) @@ -472,7 +487,7 @@ (checking "solve-linear, div pass through correctly" 100 [l sg/real r sg/real] - (when-not (v/zero? r) + (when-not (g/zero? r) (is (= (g// l r) ((passthrough g//) l r))) diff --git a/test/emmy/generators.cljc b/test/emmy/generators.cljc index 8523935f..3963f2d5 100644 --- a/test/emmy/generators.cljc +++ b/test/emmy/generators.cljc @@ -75,7 +75,7 @@ ([opts] (->> (reasonable-double opts) (gen/fmap (fn [x] - (if (v/exact? x) + (if (g/exact? x) (+ x 0.5) x)))))) @@ -97,7 +97,7 @@ (def big-ratio (gen/let [n bigint d bigint] - (let [d (if (v/zero? d) + (let [d (if (g/zero? d) (u/bigint 1) d)] (r/rationalize n d)))) @@ -291,7 +291,7 @@ (let [term-gen (gen/let [tags (vector-set gen/nat) coef coeff-gen] (let [tags (if (empty? tags) [0] tags) - coef (if (v/zero? coef) 1 coef)] + coef (if (g/zero? coef) 1 coef)] (#'d/make-term tags coef)))] (gen/let [terms (gen/vector term-gen 1 5) primal coeff-gen] @@ -333,7 +333,7 @@ (poly/constant arity p)))) terms)] (if nonzero? - (gen/such-that (complement v/zero?) pgen) + (gen/such-that (complement g/zero?) pgen) pgen)))] (let [arity (if (integer? arity) (gen/return arity) diff --git a/test/emmy/generic_test.cljc b/test/emmy/generic_test.cljc index 1b383e45..5734d823 100644 --- a/test/emmy/generic_test.cljc +++ b/test/emmy/generic_test.cljc @@ -45,16 +45,28 @@ ;; Install methods on a new, custom defrecord to test default implementations. (defrecord Wrap [s] - v/Value - (one? [this] (= this (v/one-like this))) - (zero? [this] (= this (v/zero-like this))) - (identity? [this] (= this (v/identity-like this))) - (zero-like [_] (Wrap. "0")) - (one-like [_] (Wrap. "1")) - (identity-like [_] (Wrap. "1")) - (freeze [_] (list 'wrap s)) - (exact? [_] false) - (kind [_] ::wrap)) + v/IKind + (kind [_] ::wrap) + + #?@(:clj [Comparable + (compareTo [_ b] + (if (instance? Wrap b) + (compare s (.-s ^Wrap b)) + (compare s b)))] + + :cljs [IComparable + (-compare [_ b] + (if (instance? Wrap b) + (compare s (.-s ^Wrap b)) + (compare s b)))])) + +(defmethod g/zero? [::wrap] [a] (= a (g/zero-like a))) +(defmethod g/one? [::wrap] [a] (= a (g/one-like a))) +(defmethod g/identity? [::wrap] [a] (= a (g/identity-like a))) +(defmethod g/zero-like [::wrap] [_] (Wrap. "0")) +(defmethod g/one-like [::wrap] [_] (Wrap. "1")) +(defmethod g/identity-like [::wrap] [_] (Wrap. "1")) +(defmethod g/freeze [::wrap] [^Wrap a] (list 'wrap (.-s a))) (defmethod g/add [::wrap ::wrap] [l r] (->Wrap (str (:s l) "+" (:s r)))) @@ -82,25 +94,33 @@ (is (= (->Wrap "l*r") (g/mul l r))) (is (= (->Wrap "l*l*l*l*l*l") (g/expt l 6))) (is (= (->Wrap "l*l*l*l") (g/expt l 4))) - (is (= l (g/expt l 1)))) + (is (= l (g/expt l 1))) + (is (= (g/one-like l) (g/expt l 0)))) (testing "div comes for free from mul and invert" (is (= (->Wrap "1/l") (g/invert l))) (is (= (->Wrap "l*1/r") (g/div l r)))) + (testing "negative? comes from zero-like and Comparable" + (is (g/negative? (->Wrap "!"))) + (is (not (g/negative? (->Wrap "@"))))) + + (testing "sinc comes from zero? and one-like" + (is (= (->Wrap "1") (g/sinc (->Wrap "0"))))) + (testing "unimplemented predicate behavior" (is (not (g/infinite? l)) "instead of an error, infinite? returns false for random types.")))) (deftest generic-freeze-behavior (testing "freeze should return symbols" - (is (= 'abs (v/freeze g/abs)) + (is (= 'abs (g/freeze g/abs)) "fn where we don't override the name.") (is (= ['+ '- '- '* '/ '/] - (map v/freeze [g/add g/sub g/negate g/mul g/div g/invert]) - (map v/freeze [g/+ g/- g/- g/* g// g/divide])) - "v/freeze returns symbols for our generic multimethods. The hidden g/add + (map g/freeze [g/add g/sub g/negate g/mul g/div g/invert]) + (map g/freeze [g/+ g/- g/- g/* g// g/divide])) + "g/freeze returns symbols for our generic multimethods. The hidden g/add etc return proper higher-level symbols."))) (deftest type-assigner @@ -115,14 +135,14 @@ (checking "g/+" 100 [x gen/any-equatable] (is (= x (g/+ x)) "single arg should return itself, for any type.") - (is (= (if (v/numeric-zero? x) 0 x) + (is (= (if (g/numeric-zero? x) 0 x) (g/+ x 0)) "adding a 0 works for any input. The first zero element gets returned.") (is (= x (g/+ 0 x)) "adding a leading 0 acts as identity.") - (is (= (if (v/numeric-zero? x) 0 x) + (is (= (if (g/numeric-zero? x) 0 x) (g/+ 0 x 0.0 0 0)) "multi-arg works as long as zeros appear."))) @@ -136,7 +156,7 @@ (is (= 1 (g/*)) "No args returns the multiplicative identity.") (checking "g/*" 100 [x gen/any-equatable] (is (v/= x (g/* x)) "single arg returns itself.") - (is (v/= (if (v/one? x) 1 x) + (is (v/= (if (and (v/numerical? x) (g/one? x)) 1 x) (g/* x 1)) "First unity gets returned.") (is (v/= x (g/* 1 x)) "Anything times a 1 returns itself."))) diff --git a/test/emmy/laws.cljc b/test/emmy/laws.cljc index ef9897a8..69ed7463 100644 --- a/test/emmy/laws.cljc +++ b/test/emmy/laws.cljc @@ -2,41 +2,39 @@ (ns emmy.laws "test.check laws useful for checking the algebraic properties of different types - that implement the emmy.generic operations, and the additive and - multiplicative options in emmy.value.Value." + that implement the emmy.generic operations." (:require [clojure.test :refer [is]] [com.gfredericks.test.chuck.clojure-test :refer [checking]] [emmy.generic :as g] - [emmy.value :as v] [same.core :refer [ish?]])) (defn nullity [options generator type-name] - (checking (str type-name " v/zero? agrees with v/zero-like.") + (checking (str type-name " g/zero? agrees with g/zero-like.") options [a generator] - (is (v/zero? (v/zero-like a))))) + (is (g/zero? (g/zero-like a))))) (defn unity [options generator type-name] - (checking (str type-name " v/one? agrees with v/one-like.") + (checking (str type-name " g/one? agrees with g/one-like.") options [a generator] - (is (v/one? (v/one-like a))))) + (is (g/one? (g/one-like a))))) (defn zero-like [options generator type-name] (nullity options generator type-name) (checking (str type-name " has a valid zero-like implementation.") options [a generator] - (is (ish? a (g/add a (v/zero-like a)))) - (is (ish? a (g/add (v/zero-like a) a))))) + (is (ish? a (g/add a (g/zero-like a)))) + (is (ish? a (g/add (g/zero-like a) a))))) (defn one-like [options generator type-name] (unity options generator type-name) (checking (str type-name " has a valid one-like implementation.") options [a generator] - (is (ish? a (g/mul a (v/one-like a)))) - (is (ish? a (g/mul (v/one-like a) a))))) + (is (ish? a (g/mul a (g/one-like a)))) + (is (ish? a (g/mul (g/one-like a) a))))) (defn associative-add [options generator type-name] (checking (str type-name " implements associative g/add.") @@ -76,18 +74,18 @@ (checking (str type-name " has additive inverses via g/negate and g/sub") options [a generator] - (is (ish? (v/zero-like a) (g/add a (g/negate a)))) - (is (v/zero? (g/add a (g/negate a)))) - (is (ish? (v/zero-like a) (g/add (g/negate a) a))) - (is (ish? (v/zero-like a) (g/sub a a))))) + (is (ish? (g/zero-like a) (g/add a (g/negate a)))) + (is (g/zero? (g/add a (g/negate a)))) + (is (ish? (g/zero-like a) (g/add (g/negate a) a))) + (is (ish? (g/zero-like a) (g/sub a a))))) (defn multiplicative-inverse [options generator type-name] (checking (str type-name " has multiplicative inverses via g/div and g/invert (excluding zero.)") options - [a generator :when (not (v/zero? a))] - (is (ish? (v/one-like a) (g/mul a (g/invert a)))) - (is (ish? (v/one-like a) (g/mul (g/invert a) a))) - (is (ish? (v/one-like a) (g/div a a))))) + [a generator :when (not (g/zero? a))] + (is (ish? (g/one-like a) (g/mul a (g/invert a)))) + (is (ish? (g/one-like a) (g/mul (g/invert a) a))) + (is (ish? (g/one-like a) (g/div a a))))) (defn mul-distributes-over-add [options generator type-name] (checking (str type-name " g/mul distributes over g/add, left and right") @@ -124,8 +122,8 @@ 0 + a == a + 0 == a - `(v/zero-like a)` should always return this element, - and `(v/zero? (v/zero-like))` should always be true.`" + `(g/zero-like a)` should always return this element, + and `(g/zero? (g/zero-like))` should always be true.`" [opts generator type-name & {:keys [commutative?]}] (additive-semigroup opts generator type-name :commutative? commutative?) (zero-like opts generator type-name)) @@ -153,8 +151,8 @@ 0 * a == a * 0 == a - `(v/one-like a)` should always return this element, - and `(v/one? (v/one-like))` should always be true.`" + `(g/one-like a)` should always return this element, + and `(g/one? (g/one-like))` should always be true.`" [opts generator type-name & {:keys [commutative?]}] (multiplicative-semigroup opts generator type-name :commutative? commutative?) (one-like opts generator type-name)) diff --git a/test/emmy/matrix_test.cljc b/test/emmy/matrix_test.cljc index 458f1a71..45e89553 100644 --- a/test/emmy/matrix_test.cljc +++ b/test/emmy/matrix_test.cljc @@ -19,47 +19,47 @@ (deftest value-protocol-tests (testing "zero?" - (is (v/zero? (m/by-rows [0]))) - (is (v/zero? + (is (g/zero? (m/by-rows [0]))) + (is (g/zero? (m/by-rows [0 0 0] [0 0 0] [0 0 0]))) - (is (not (v/zero? (m/by-rows [1 2 3]))))) + (is (not (g/zero? (m/by-rows [1 2 3]))))) (testing "zero-like" (is (= (m/by-rows [0 0 0]) - (v/zero-like (m/by-rows [1 2 3])))) + (g/zero-like (m/by-rows [1 2 3])))) (is (= (m/by-rows [0]) - (v/zero-like (m/by-rows [1])))) + (g/zero-like (m/by-rows [1])))) (is (= (m/by-rows [0.0] [0.0]) - (v/zero-like (m/by-rows [1.5] [2.5]))) + (g/zero-like (m/by-rows [1.5] [2.5]))) "zero-like preserves types")) (testing "one? vs identity?" (let [I10 (m/I 10)] - (is (not (v/one? I10)) + (is (not (g/one? I10)) "one? implies that multiplying by this acts as identity, which is only true for matrices of the correct shape (not for scalars!) so one? will always return false for a matrix.") - (is (v/identity? I10) + (is (g/identity? I10) "identity? exists to check for an identity matrix."))) (testing "one-like" (is (= (m/I 3) - (v/one-like + (g/one-like (m/by-rows [1 2 3] [4 5 6] [7 8 9])))) (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) - (v/one-like (m/by-rows [1 2 3 4]))) + (g/one-like (m/by-rows [1 2 3 4]))) "one-like is only supported on square matrices.")) (testing "identity-like" (is (= (m/I 3) - (v/identity-like + (g/identity-like (m/by-rows [1 2 3] [4 5 6] [7 8 9])))) (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) - (v/identity-like (m/by-rows [1 2 3 4]))) + (g/identity-like (m/by-rows [1 2 3 4]))) "identity-like is only supported on square matrices.")) (testing "numerical? returns false, always" @@ -69,10 +69,10 @@ (is (not (v/numerical? (m/by-rows [0 1 (g// 3 2)]))))) (testing "exact?" - (is (v/exact? (m/by-rows [1] [2]))) - (is (not (v/exact? (m/by-rows [1.2] [3] [4])))) - (is (not (v/exact? (m/by-rows [0] [0] [0.00001])))) - (is (v/exact? (m/by-rows [0 1 (g// 3 2)])))) + (is (g/exact? (m/by-rows [1] [2]))) + (is (not (g/exact? (m/by-rows [1.2] [3] [4])))) + (is (not (g/exact? (m/by-rows [0] [0] [0.00001])))) + (is (g/exact? (m/by-rows [0 1 (g// 3 2)])))) (testing "kind" (is (= ::m/row-matrix (v/kind (m/by-rows [1 2])))) @@ -177,15 +177,15 @@ (M 2))) (is (= (m/I 2) - ((v/identity-like M) 2)) + ((g/identity-like M) 2)) "identity-like on a matrix of functions returns a new matrix of fns.") (is (= (m/I 2) - ((v/one-like M) 2)) + ((g/one-like M) 2)) "one-like on a matrix of functions returns a new matrix of fns.") (is (= (m/make-zero 2) - ((v/zero-like M) 2)) + ((g/zero-like M) 2)) "one-like on a matrix of functions returns a new matrix of fns."))) (checking "by-rows == (comp transpose by-cols), vice versas" 100 @@ -259,13 +259,20 @@ (checking "make-zero" 100 [m (gen/choose 0 10) n (gen/choose 0 10)] (let [M (m/make-zero m n)] - (is (v/zero? M)) + (is (g/zero? M)) (is (= m (m/num-rows M))) (is (= n (m/num-cols M))))) (testing "make-diagonal" (is (= (m/I 10) (m/make-diagonal 10 1)))) + (testing "symmetric?" + (is (m/symmetric? (m/by-rows [1 2] [2 1]))) + (is (not (m/symmetric? (m/by-rows [1 2] [-2 1]))))) + + (testing "empty" + (is (g/zero? (empty (m/by-rows [1 2] [2 1]))))) + (checking "make-diagonal" 100 [vs (gen/vector sg/real 1 20)] (let [M (m/make-diagonal vs)] @@ -277,17 +284,17 @@ (is (= vs (m/diagonal M))) (is (= vs (m/diagonal M))))) - (checking "make-diagonal, v/identity? v/one?" 100 + (checking "make-diagonal, g/identity? g/one?" 100 [v (gen/vector (gen/return 1) 1 20)] (let [M (m/make-diagonal v)] - (is (v/identity? M)) - (is (not (v/identity? (g/* 2 M)))) + (is (g/identity? M)) + (is (not (g/identity? (g/* 2 M)))) - (is (not (v/one? M)) + (is (not (g/one? M)) "matrices don't act as one; they need to maintain their structure when multiplied by constants.") - (is (not (v/one? (g/* 2 M)))))) + (is (not (g/one? (g/* 2 M)))))) (let [M (m/by-rows (list 1 2 3) (list 4 5 6)) @@ -299,11 +306,11 @@ (is (= '(matrix-by-rows [1 2 3] [4 5 6]) - (v/freeze M))) + (g/freeze M))) (is (= (m/by-rows [1 4] [2 5] [3 6]) (g/transpose M))) - (is (= (m/by-rows [0 0 0] [0 0 0]) (v/zero-like M))) - (is (= (m/by-rows [1 0 0] [0 1 0] [0 0 1]) (v/one-like A))) - (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (v/one-like M))) + (is (= (m/by-rows [0 0 0] [0 0 0]) (g/zero-like M))) + (is (= (m/by-rows [1 0 0] [0 1 0] [0 0 1]) (g/one-like A))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (g/one-like M))) (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (m/by-rows [1 2 3] [4 5]))) (is (thrown? #?(:clj AssertionError :cljs js/Error) (m/by-rows))) (is (= 5 (m/get-in M [1 1]))) @@ -357,7 +364,7 @@ (is (not (m/diagonal? (m/generate 3 3 +)))) - (is (v/zero? (m/by-rows [0 0] + (is (g/zero? (m/by-rows [0 0] [0 0]))))) (deftest literal-matrix-creation @@ -458,7 +465,7 @@ 100 [[l inner r] (gen/let [rows (gen/choose 1 5) cols (gen/choose 1 5)] ( rows cols))] - (is (v/zero? + (is (g/zero? (g/- (m/s:transpose l inner r) (s/transpose-outer inner))))) @@ -474,9 +481,9 @@ (if (empty? r) (testing "in this case, the right side is fully collapsed and empty and the left side contains a single empty structure." - (do (is (v/zero? (m/s:transpose l inner r))) + (do (is (g/zero? (m/s:transpose l inner r))) (is (empty? (s/transpose-outer inner))))) - (is (v/zero? + (is (g/zero? (g/- (m/s:transpose l inner r) (s/transpose-outer inner))) "left side empty generates a compatible, zero entry")))) @@ -830,7 +837,7 @@ (defspec a*ainv=i (gen/let [n (gen/choose 1 5)] (prop/for-all [A (sg/square-matrix n)] - (or (v/zero? (g/determinant A)) + (or (g/zero? (g/determinant A)) (= (m/I n) (g/* (g/invert A) A) (g/* A (g/invert A))))))) @@ -852,7 +859,7 @@ (deftest naive-vs-determinant-tests (let [M (m/literal-matrix 'x 6)] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (m/determinant M) (naive-determinant M)))) @@ -874,7 +881,7 @@ (deftest naive-vs-invert-tests (let [M (m/literal-matrix 'x 4)] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (m/invert M) (naive-invert M)))) @@ -992,7 +999,7 @@ with the denominator." 100 [s (sg/up1 sg/any-integral 5) x (gen/fmap (fn [x] - (if (v/zero? x) 1 x)) + (if (g/zero? x) 1 x)) sg/any-integral)] (is (= s (g/* x (g// s x))))) @@ -1198,7 +1205,7 @@ (g/* d M)) "multiplying by down directly matches conversion to matrix first.") - (is (v/zero? + (is (g/zero? (g/simplify (g/- (g/transpose (g/* M v)) (g/* (g/transpose v) diff --git a/test/emmy/mechanics/hamilton_test.cljc b/test/emmy/mechanics/hamilton_test.cljc index 0e0de14e..d004c355 100644 --- a/test/emmy/mechanics/hamilton_test.cljc +++ b/test/emmy/mechanics/hamilton_test.cljc @@ -14,13 +14,12 @@ [emmy.mechanics.lagrange :as L] [emmy.operator :as o] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [component up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [component up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest basic-tests (checking "basic accessors and state creation" 100 @@ -50,8 +49,8 @@ (testing "Hamiltonian type sig" (let [state (H/literal-Hamiltonian-state 4)] - (is (= (list 'f (v/freeze state)) - (v/freeze + (is (= (list 'f (g/freeze state)) + (g/freeze ((f/literal-function 'f (H/Hamiltonian 4)) state))) "Applying the state passes the type check."))) @@ -146,15 +145,27 @@ (simplify ((H/Legendre-transform (fn [x] (* 'c x x))) 'y)))) + (is (= '(/ (* (/ 1 4) (expt y 2)) c) + (binding [H/*validate-Legendre-transform?* true] + (simplify + ((H/Legendre-transform (fn [x] (* 'c x x))) 'y)))) + "works with validation") + + (is (thrown-with-msg? #?(:clj clojure.lang.ExceptionInfo :cljs js/Error) + #"Legendre Transform Failure" + (binding [H/*validate-Legendre-transform?* true] + ((H/Legendre-transform (fn [x] (* 'c x))) 'y))) + "throws for singular arguments") + (is (= '(* (/ 1 4) (expt p 2)) - (v/freeze + (g/freeze (simplify ((H/Legendre-transform g/square) 'p))))) (is (= '(+ (* (/ 1 2) m (expt v_x 2)) (* (/ 1 2) m (expt v_y 2)) (* -1 (V x y))) - (v/freeze + (g/freeze (simplify ((L/L-rectangular 'm V) (up 't (up 'x 'y) (up 'v_x 'v_y))))))) diff --git a/test/emmy/mechanics/lagrange_test.cljc b/test/emmy/mechanics/lagrange_test.cljc index 8ce21bc6..c779ce1c 100644 --- a/test/emmy/mechanics/lagrange_test.cljc +++ b/test/emmy/mechanics/lagrange_test.cljc @@ -19,7 +19,7 @@ (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest basic-tests (checking "basic accessors and state creation" 100 diff --git a/test/emmy/mechanics/noether_test.cljc b/test/emmy/mechanics/noether_test.cljc index d9e41d2d..75788304 100644 --- a/test/emmy/mechanics/noether_test.cljc +++ b/test/emmy/mechanics/noether_test.cljc @@ -7,8 +7,7 @@ [emmy.mechanics.lagrange :as l] [emmy.mechanics.noether :as n] [emmy.mechanics.rotation :as r] - [emmy.structure :as s :refer [up]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up]])) (defn F-tilde [theta phi psi] (comp (r/Rx theta) @@ -20,7 +19,7 @@ (is (= '(down (+ (* -1 m vy z) (* m vz y)) (+ (* m vx z) (* -1 m vz x)) (+ (* -1 m vx y) (* m vy x))) - (v/freeze + (g/freeze (g/simplify ((n/Noether-integral (l/L-central-rectangular 'm (f/literal-function 'Vr)) diff --git a/test/emmy/mechanics/rigid_test.cljc b/test/emmy/mechanics/rigid_test.cljc index 11e2429c..6c1a7785 100644 --- a/test/emmy/mechanics/rigid_test.cljc +++ b/test/emmy/mechanics/rigid_test.cljc @@ -19,7 +19,7 @@ (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest rigid-tests (f/with-literal-functions [theta phi psi] @@ -71,7 +71,7 @@ an-Euler-state) (get 1))))) - (is (v/zero? + (is (g/zero? (simplify ;; this first is the fucked up one (- (-> ((rig/L-space-Euler 'A 'B 'C) an-Euler-state) diff --git a/test/emmy/mechanics/rotation_test.cljc b/test/emmy/mechanics/rotation_test.cljc index 5d2ea605..3387ba07 100644 --- a/test/emmy/mechanics/rotation_test.cljc +++ b/test/emmy/mechanics/rotation_test.cljc @@ -8,13 +8,12 @@ [emmy.matrix :as matrix] [emmy.mechanics.rotation :as r] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :refer [up]] - [emmy.value :as v])) + [emmy.structure :refer [up]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest rotation-tests (let [P (up 'x 'y 'z)] diff --git a/test/emmy/mechanics/routhian_test.cljc b/test/emmy/mechanics/routhian_test.cljc index 42807e26..f474484e 100644 --- a/test/emmy/mechanics/routhian_test.cljc +++ b/test/emmy/mechanics/routhian_test.cljc @@ -7,13 +7,12 @@ [emmy.generic :as g :refer [+ - * / square]] [emmy.mechanics.routhian :as rn] [emmy.simplify :refer [hermetic-simplify-fixture]] - [emmy.structure :as s :refer [up down]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up down]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (defn Lag [mx kx my ky] (fn [[_ [x y] [vx vy]]] diff --git a/test/emmy/mechanics/time_evolution_test.cljc b/test/emmy/mechanics/time_evolution_test.cljc index 4c95c2ca..16b10049 100644 --- a/test/emmy/mechanics/time_evolution_test.cljc +++ b/test/emmy/mechanics/time_evolution_test.cljc @@ -6,8 +6,7 @@ [emmy.generic :as g :refer [+]] [emmy.mechanics.hamilton :as h] [emmy.mechanics.time-evolution :as te] - [emmy.structure :as s :refer [up]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up]])) (deftest time-evolution-tests (is (= (h/->H-state (+ 't 'dt) (up 'x) (up 'p_x)) @@ -18,7 +17,7 @@ Hp ((te/H->Hp 'dt) H) state (h/->H-state 't (up 'x) (up 'p_x))] (is (= '(/ (* (/ 1 2) (expt p_x 2)) m) - (v/freeze + (g/freeze (g/simplify (Hp state)))) "this Hamiltonian is not time dependent!"))) diff --git a/test/emmy/modint_test.cljc b/test/emmy/modint_test.cljc index 4cdd84e4..d9ccf328 100644 --- a/test/emmy/modint_test.cljc +++ b/test/emmy/modint_test.cljc @@ -23,7 +23,7 @@ (testing "value implementation" (is (= '(modint 1 2) - (v/freeze (m/make 1 2))))) + (g/freeze (m/make 1 2))))) (checking "v/= can handle non-modint instances" 100 [m (sg/modint) @@ -96,16 +96,16 @@ (g/modulo modulus)))))) (testing "zero?" - (is (v/zero? m0_7)) - (is (v/zero? (v/zero-like m5_7)))) + (is (g/zero? m0_7)) + (is (g/zero? (g/zero-like m5_7)))) (testing "one?" - (is (v/one? m1_7)) - (is (v/one? (v/one-like m5_7)))) + (is (g/one? m1_7)) + (is (g/one? (g/one-like m5_7)))) (testing "identity?" - (is (v/identity? m1_7)) - (is (v/identity? (v/identity-like m5_7)))) + (is (g/identity? m1_7)) + (is (g/identity? (g/identity-like m5_7)))) (testing "compatibility" (is 4 (g/integer-part m4_7)) diff --git a/test/emmy/numbers_test.cljc b/test/emmy/numbers_test.cljc index 091a28af..9285c07e 100644 --- a/test/emmy/numbers_test.cljc +++ b/test/emmy/numbers_test.cljc @@ -111,7 +111,7 @@ (is (= (c/complex 0 9) (g/sqrt -81)))) (testing "sqrt of one preserves type" - (is (v/one-like (g/sqrt c/ONE))) + (is (g/one-like (g/sqrt c/ONE))) (is (c/complex? (g/sqrt c/ONE)))) (checking "transpose, determinant, trace act as id" 100 [x sg/real] @@ -209,7 +209,7 @@ (is (= (c/complex 0 9) (g/sqrt -81)))) (testing "sqrt of one preserves type" - (is (v/one-like (g/sqrt c/ONE))) + (is (g/one-like (g/sqrt c/ONE))) (is (c/complex? (g/sqrt c/ONE)))) (testing "log" @@ -293,7 +293,7 @@ (checking "negating arg switches floor and ceiling, changes sign" 100 [n sg/any-integral] - (is (v/zero? + (is (g/zero? (g/+ (g/floor n) (g/ceiling (g/- n)))) "floor(x) + ceil(-x) == 0") @@ -349,8 +349,8 @@ ;; TODO unit test with real side for anything that bakes in integer. (letfn [(nonzero [g] (gen/fmap (fn [x] - (if (v/zero? x) - (v/one-like x) + (if (g/zero? x) + (g/one-like x) x)) g))] (checking "mod, rem identity" 100 @@ -387,7 +387,7 @@ (g/quotient 4 1.2)))) (checking "quotient, remainder with floats" 100 - [x (gen/such-that (complement v/zero?) sg/real)] + [x (gen/such-that (complement g/zero?) sg/real)] (is (= (g/quotient x x) (g/exact-divide x x)) "exact-divide is fine if passed identical inputs") @@ -405,10 +405,10 @@ "x/-x == -1") (testing "remainder" - (is (v/zero? (g/remainder x x))) - (is (v/zero? (g/remainder x (- x)))) - (is (v/zero? (g/remainder (- x) (- x)))) - (is (v/zero? (g/remainder (- x) x))))) + (is (g/zero? (g/remainder x x))) + (is (g/zero? (g/remainder x (- x)))) + (is (g/zero? (g/remainder (- x) (- x)))) + (is (g/zero? (g/remainder (- x) x))))) (checking "x == y*quot(x,y) + rem(x,y)" 100 [x (gen-integer 1e4) @@ -417,7 +417,7 @@ (is (= x (g/+ (g/* y (g/quotient x y)) rem))) - (when-not (v/zero? rem) + (when-not (g/zero? rem) (is (= (v/compare 0 x) (v/compare 0 rem)) "`g/remainder` returns a result of either 0 or the same @@ -430,7 +430,7 @@ (is (= x (g/+ (g/* y (g/floor (g// x y))) mod))) - (when-not (v/zero? mod) + (when-not (g/zero? mod) (is (= (v/compare 0 y) (v/compare 0 mod)) "`g/modulo` returns a result of either 0 or the same sign @@ -450,7 +450,7 @@ (letfn [(nonzero [g] (gen/fmap (fn [x] (let [small (g/remainder x 10000)] - (if (v/zero? small) 1 small))) + (if (g/zero? small) 1 small))) g))] (checking "gcd" 100 [x (nonzero sg/small-integral) y (nonzero sg/small-integral) @@ -523,7 +523,7 @@ (is (ish? (Math/tan n) (g/tan n)))) (checking "tan" 100 [n sg/real] - (when-not (v/zero? (g/cos n)) + (when-not (g/zero? (g/cos n)) (is (ish? (g/div (g/sin n) (g/cos n)) (g/tan n))))) @@ -548,8 +548,8 @@ (g/tan (g/atan 0.5 0.2))))) (checking "cot" 100 [n sg/real] - (when-not (or (v/zero? (g/sin n)) - (v/zero? (g/cos n))) + (when-not (or (g/zero? (g/sin n)) + (g/zero? (g/cos n))) (is (ish? (g/cot n) (g/invert (g/tan n)))))) @@ -560,23 +560,23 @@ (is (ish? (Math/sinh n) (g/sinh n)))) (checking "tanh" 100 [n (sg/reasonable-double {:min -100 :max 100})] - (when-not (v/zero? (g/cosh n)) + (when-not (g/zero? (g/cosh n)) (is (ish? (g/tanh n) (g/div (g/sinh n) (g/cosh n)))))) (checking "sec" 100 [n sg/real] - (when-not (v/zero? (g/cosh n)) + (when-not (g/zero? (g/cosh n)) (is (ish? (g/sec n) (g/invert (g/cos n)))))) (checking "csc" 100 [n sg/real] - (when-not (v/zero? (g/sin n)) + (when-not (g/zero? (g/sin n)) (is (ish? (g/csc n) (g/invert (g/sin n)))))) (checking "sech" 100 [n sg/real] (let [cosh-n (g/cosh n)] - (when-not (v/zero? cosh-n) + (when-not (g/zero? cosh-n) (is (ish? (g/sech n) (g/invert cosh-n)))))) @@ -596,7 +596,7 @@ (checking "atanh" 100 [n (sg/reasonable-double {:min -10 :max 10})] - (when-not (v/one? (g/abs n)) + (when-not (g/one? (g/abs n)) (is (ish? n (g/tanh (g/atanh n)))))))) (deftest complex-constructor-tests @@ -653,4 +653,4 @@ (if (neg? x) (is (ish? Math/PI (g/angle x)) "the angle of a negative number is pi in the complex plane.") - (is (v/zero? (g/angle x)))))) + (is (g/zero? (g/angle x)))))) diff --git a/test/emmy/numerical/derivative_test.cljc b/test/emmy/numerical/derivative_test.cljc index a638ceae..61bf9c80 100644 --- a/test/emmy/numerical/derivative_test.cljc +++ b/test/emmy/numerical/derivative_test.cljc @@ -42,7 +42,7 @@ (us/seq-limit {:tolerance 1e-13}))))))) (deftest D-numeric-tests - (with-comparator (v/within (g/sqrt v/machine-epsilon)) + (with-comparator (v/within (g/sqrt u/machine-epsilon)) (testing "D-numeric packages all of this up." (let [f (d/D-numeric g/sqrt)] (is (ish? (u/double @@ -133,7 +133,7 @@ (deftest central-d2-tests (testing "central-d2 mode generates a second derivative." - (with-comparator (v/within (g/sqrt v/machine-epsilon)) + (with-comparator (v/within (g/sqrt u/machine-epsilon)) (let [f (fn [x] (g/* (g// (g/expt x 3) 3))) f'' (d/D-numeric f {:method :central-d2})] diff --git a/test/emmy/numerical/quadrature/boole_test.cljc b/test/emmy/numerical/quadrature/boole_test.cljc index 7324dec9..aef88fa0 100644 --- a/test/emmy/numerical/quadrature/boole_test.cljc +++ b/test/emmy/numerical/quadrature/boole_test.cljc @@ -9,7 +9,6 @@ [emmy.numerical.quadrature.trapezoid :as qt] [emmy.numsymb] [emmy.simplify :as s :refer [hermetic-simplify-fixture]] - [emmy.value :as v] [same.core :refer [ish?]])) (use-fixtures :each hermetic-simplify-fixture) @@ -53,7 +52,7 @@ (let [t**p (g/expt 2 p)] (/ (- (* t**p b) a) (- t**p 1))))] - (is (v/zero? + (is (g/zero? (g/simplify (- (richardson-step 4 (richardson-step 2 t1 t2) diff --git a/test/emmy/numerical/quadrature/common_test.cljc b/test/emmy/numerical/quadrature/common_test.cljc index 94506bab..11865fb6 100644 --- a/test/emmy/numerical/quadrature/common_test.cljc +++ b/test/emmy/numerical/quadrature/common_test.cljc @@ -3,7 +3,7 @@ (ns emmy.numerical.quadrature.common-test (:require [clojure.test :refer [is deftest testing]] [emmy.numerical.quadrature.common :as qc] - [emmy.value :as v])) + [emmy.util :as u])) (deftest interval-tests (testing "an interval is open unless it's fully closed" @@ -50,7 +50,7 @@ (integrate f 0 10)) "our fake sequence converges after four steps!") - (let [tiny-r (fn [l] (+ l (* 10 v/machine-epsilon)))] + (let [tiny-r (fn [l] (+ l (* 10 u/machine-epsilon)))] (is (= {:converged? true :terms-checked 1 :result slim-estimate} diff --git a/test/emmy/numerical/quadrature/milne_test.cljc b/test/emmy/numerical/quadrature/milne_test.cljc index b88411d5..e56bcf33 100644 --- a/test/emmy/numerical/quadrature/milne_test.cljc +++ b/test/emmy/numerical/quadrature/milne_test.cljc @@ -10,7 +10,6 @@ [emmy.numsymb] [emmy.simplify :as s :refer [hermetic-simplify-fixture]] [emmy.util :as u] - [emmy.value :as v] [same.core :refer [ish?]])) (use-fixtures :each hermetic-simplify-fixture) @@ -46,7 +45,7 @@ (let [t**p (g/expt 2 p)] (/ (- (* t**p b) a) (- t**p 1))))] - (is (v/zero? + (is (g/zero? (g/simplify (- (richardson-step 2 m1 m2) (milne-step f a b)))) diff --git a/test/emmy/numerical/quadrature/riemann_test.cljc b/test/emmy/numerical/quadrature/riemann_test.cljc index a7619e4d..e413da3d 100644 --- a/test/emmy/numerical/quadrature/riemann_test.cljc +++ b/test/emmy/numerical/quadrature/riemann_test.cljc @@ -9,7 +9,6 @@ [emmy.util :as u] [emmy.util.aggregate :as ua] [emmy.util.stream :as us] - [emmy.value :as v] [same.core :refer [ish?]])) (deftest windowed-sum-tests @@ -119,7 +118,7 @@ :result 2} (qr/left-integral g/sin 0 Math/PI {:accelerate? true - :tolerance v/machine-epsilon})) + :tolerance u/machine-epsilon})) "left-integral converges for sin over 0 => pi.") (is (ish? @@ -136,7 +135,7 @@ :result 2} (qr/right-integral g/sin 0 Math/PI {:accelerate? true - :tolerance v/machine-epsilon})) + :tolerance u/machine-epsilon})) "right-integral converges for sin over 0 => pi.") (is (ish? @@ -165,7 +164,7 @@ (qr/lower-integral g/sin 0 Math/PI {:accelerate? true :minterms 3 - :tolerance v/machine-epsilon})) + :tolerance u/machine-epsilon})) "lower-integral converges for sin over 0 => pi when you force it to consider more than 3 terms.")) @@ -176,5 +175,5 @@ :result 2} (qr/upper-integral g/sin 0 Math/PI {:accelerate? true - :tolerance v/machine-epsilon})) + :tolerance u/machine-epsilon})) "upper-integral converges (at machine epsilon!)"))) diff --git a/test/emmy/numerical/quadrature/simpson38_test.cljc b/test/emmy/numerical/quadrature/simpson38_test.cljc index a9dc0886..54264d2f 100644 --- a/test/emmy/numerical/quadrature/simpson38_test.cljc +++ b/test/emmy/numerical/quadrature/simpson38_test.cljc @@ -9,7 +9,6 @@ [emmy.numerical.quadrature.trapezoid :as qt] [emmy.numsymb] [emmy.simplify :as s :refer [hermetic-simplify-fixture]] - [emmy.value :as v] [same.core :refer [ish?]])) (use-fixtures :each hermetic-simplify-fixture) @@ -48,7 +47,7 @@ richardson-step (let [t**2 (g/square 3)] (/ (- (* t**2 t3) t1) (- t**2 1)))] - (is (v/zero? + (is (g/zero? (g/simplify (g/- richardson-step (simpson38-step f a b)))) diff --git a/test/emmy/numerical/quadrature/simpson_test.cljc b/test/emmy/numerical/quadrature/simpson_test.cljc index a6319284..b8fda63d 100644 --- a/test/emmy/numerical/quadrature/simpson_test.cljc +++ b/test/emmy/numerical/quadrature/simpson_test.cljc @@ -11,7 +11,6 @@ [emmy.numsymb] [emmy.simplify :as s :refer [hermetic-simplify-fixture]] [emmy.util.stream :as us] - [emmy.value :as v] [same.core :refer [ish?]])) (use-fixtures :each hermetic-simplify-fixture) @@ -43,7 +42,7 @@ richardson-step (let [t**2 (g/square 2)] (/ (- (* t**2 t2) t1) (- t**2 1)))] - (is (v/zero? + (is (g/zero? (g/simplify (- richardson-step (simpson-step f 'a 'b)))) diff --git a/test/emmy/numerical/quadrature/trapezoid_test.cljc b/test/emmy/numerical/quadrature/trapezoid_test.cljc index 5f21bf3a..af2d58f8 100644 --- a/test/emmy/numerical/quadrature/trapezoid_test.cljc +++ b/test/emmy/numerical/quadrature/trapezoid_test.cljc @@ -9,7 +9,6 @@ [emmy.polynomial.richardson :as pr] [emmy.util :as u] [emmy.util.stream :as us] - [emmy.value :as v] [same.core :refer [ish?]])) ;; The tests on Pi estimation come from Sussman's ["Abstraction in Numerical @@ -72,7 +71,7 @@ :terms-checked 9 :result Math/PI} (qt/integral f 0 1 {:accelerate? true - :tolerance v/machine-epsilon})) + :tolerance u/machine-epsilon})) "With acceleration we hit machine epsilon in 9 iterations.") (testing "the incremental trapezoid method takes 2^n+1 evaluations" diff --git a/test/emmy/operator_test.cljc b/test/emmy/operator_test.cljc index c705990f..392b1a66 100644 --- a/test/emmy/operator_test.cljc +++ b/test/emmy/operator_test.cljc @@ -26,29 +26,34 @@ (deftest value-protocol-tests (let [x2 (-> (fn [f] (fn [x] (* 2 (f x)))) (o/make-operator 'double))] - (let [f ((v/zero-like x2) g/sin)] + (let [f ((g/zero-like x2) g/sin)] (checking " zero-like" 100 [n sg/real] - (is (v/zero? (f n))))) + (is (g/zero? (f n))))) - (let [f ((v/one-like x2) g/sin)] + (let [f ((g/one-like x2) g/sin)] (checking " one-like" 100 [n sg/real] (is (= (g/sin n) (f n)) "operator one-like is identity"))) - (let [f ((v/identity-like x2) g/sin)] + (let [f ((g/identity-like x2) g/sin)] (checking " identity-like" 100 [n sg/real] (is (= (g/sin n) (f n))))) (testing "one? zero? identity? return true appropriately" - (is (v/zero? (v/zero-like x2))) - (is (not (v/one? (v/one-like x2)))) - (is (v/identity? (v/identity-like x2)))) + (is (g/zero? (g/zero-like x2))) + (is (not (g/one? (g/one-like x2)))) + (is (g/identity? (g/identity-like x2)))) (testing "v/numerical?" (is (not (v/numerical? x2)))) - (testing "v/freeze" - (is (= 'double (v/freeze x2)))) + (testing "g/freeze" + (is (= 'double (g/freeze x2))) + (is (= '(- double) (g/freeze (g/negate x2)))) + (is (= '(/ double 3) (g/freeze (g/div x2 3))))) + + (testing "toString" + (is (= "double" (str x2)))) (testing "v/kind" (is (= ::o/operator (v/kind x2)))) @@ -72,56 +77,56 @@ (deftest simplifier-tests (testing "identity gets stripped from products" (is (= 'D - (v/freeze (g/* D o/identity)) - (v/freeze (g/* o/identity D))))) + (g/freeze (g/* D o/identity)) + (g/freeze (g/* o/identity D))))) (testing "identity does NOT get stripped from sums" (is (= '(+ identity D) - (v/freeze + (g/freeze (g/+ o/identity D)))) (is (= '(+ D identity) - (v/freeze + (g/freeze (g/+ D o/identity))))) (let [x2 (-> (fn [f] (fn [x] (* 2 (f x)))) (o/make-operator 'double))] (is (= '(+ D (* D double (expt D 2))) - (v/freeze + (g/freeze (g/+ D (g/* D x2 o/identity D D)))) "operators next to each other are gathered into exponents, and `identity` gets removed (since it's the multiplicative identity)") (is (= '(expt D 2) - (v/freeze (g/* D D)))) + (g/freeze (g/* D D)))) (is (= '(* D double D) - (v/freeze (g/* (* D x2) D))) + (g/freeze (g/* (* D x2) D))) "multiplication is commutative but NOT associative, so we gather these together.")) (testing "internal multiplication on both sides" (is (= '(expt D 6) - (v/freeze (g/* (g/* D D D) (g/* D D D))) - (v/freeze (g/* (g/* D (g/expt D 2) D) (g/* D D))) - (v/freeze (g/* (g/* (g/expt D 2) D) (g/* D D D))) - (v/freeze (g/* (g/* D D D) (g/* D (g/expt D 2)))) - (v/freeze (g/* (g/* D D D) (g/* (g/expt D 2) D))) - (v/freeze (g/* (g/* D D D) (g/* D (g/expt D 2)))) - (v/freeze (g/* (g/* D D D) (g/* (g/expt D 2) D)))))) + (g/freeze (g/* (g/* D D D) (g/* D D D))) + (g/freeze (g/* (g/* D (g/expt D 2) D) (g/* D D))) + (g/freeze (g/* (g/* (g/expt D 2) D) (g/* D D D))) + (g/freeze (g/* (g/* D D D) (g/* D (g/expt D 2)))) + (g/freeze (g/* (g/* D D D) (g/* (g/expt D 2) D))) + (g/freeze (g/* (g/* D D D) (g/* D (g/expt D 2)))) + (g/freeze (g/* (g/* D D D) (g/* (g/expt D 2) D)))))) (testing "internal multiplication on right" (is (= '(expt D 4) - (v/freeze (g/* D (g/* D D D))) - (v/freeze (g/* (g/expt D 2) (g/* D D))) - (v/freeze (g/* D (g/* D (g/expt D 2)))) - (v/freeze (g/* D (g/* (g/expt D 2) D))) - (v/freeze (g/* D (g/* D (g/expt D 2)))) - (v/freeze (g/* D (g/* (g/expt D 2) D)))))) + (g/freeze (g/* D (g/* D D D))) + (g/freeze (g/* (g/expt D 2) (g/* D D))) + (g/freeze (g/* D (g/* D (g/expt D 2)))) + (g/freeze (g/* D (g/* (g/expt D 2) D))) + (g/freeze (g/* D (g/* D (g/expt D 2)))) + (g/freeze (g/* D (g/* (g/expt D 2) D)))))) (testing "sums collapse too via the associative rule" (is (= '(+ D (partial 1) (expt D 3)) - (v/freeze + (g/freeze (g/+ D (g/+ (partial 1) (g/* D (g/expt D 2))))))))) (deftest custom-getter-tests @@ -135,7 +140,7 @@ (testing "get names" (is (= '(compose (component x) identity) - (v/freeze (get o/identity 'x))) + (g/freeze (get o/identity 'x))) "The name of the operator returned by `get` reflects the (sort of awkward) composition that `get` induces.")) @@ -192,6 +197,7 @@ (is (= 12 ((double f) 1))) (is (= 24 ((double (double f)) 1))) (is (= 12 ((double-op f) 1))) + (is (= -12 ((g/negate (double-op f)) 1))) (is (= 24 ((double-op (double-op f)) 1))) (is (= 24 (((g/* double-op double-op) f) 1))) ;; * for operators is composition (is (= 144 (((g/* double double) f) 1))) ;; * for functions is pointwise multiply @@ -242,14 +248,14 @@ (* -1 ((D f) x) ((D g) (+ (f x) ((D f) x)))) (((expt D 2) f) x) (((expt D 3) f) x)) - (v/freeze + (g/freeze (g/simplify ((D ((* (- D g) (+ D I)) f)) 'x)))))) (testing "that basic arithmetic operations work on multivariate literal functions" (is (= '(down (* 2 (((partial 0) ff) x y)) (* 2 (((partial 1) ff) x y))) - (v/freeze + (g/freeze (g/simplify (((+ D D) ff) 'x 'y))))) @@ -283,7 +289,7 @@ (* (/ 1 6) (expt ε 3) (((expt D 3) f) t)) (* (/ 1 24) (expt ε 4) (((expt D 4) f) t)) (* (/ 1 120) (expt ε 5) (((expt D 5) f) t))) - (v/freeze + (g/freeze (g/simplify (take 6 (seq (((g/exp (* 'ε D)) (f/literal-function 'f)) 't))))))) (is (ish? '(0 @@ -298,7 +304,7 @@ (* (/ 1 362880) (expt ε 9)) 0 (* (/ -1 39916800) (expt ε 11))) - (v/freeze + (g/freeze (g/simplify (take 12 (seq (((g/exp (* 'ε D)) g/sin) 0))))))) (is (ish? '(1 @@ -313,7 +319,7 @@ 0 (* (/ -1 3628800) (expt ε 10)) 0) - (v/freeze + (g/freeze (g/simplify (take 12 (seq (((g/exp (* 'ε D)) g/cos) 0))))))) (is (= '(1 @@ -322,7 +328,7 @@ (* (/ 1 16) (expt ε 3)) (* (/ -5 128) (expt ε 4)) (* (/ 7 256) (expt ε 5))) - (v/freeze + (g/freeze (g/simplify (take 6 (seq (((g/exp (* 'ε D)) #(g/sqrt (+ % 1))) 0))))))) (is (= '(+ @@ -333,7 +339,7 @@ (* (/ 29 90) (expt n 3) (expt ε 7)) (* (/ -7 20) (expt n 2) (expt ε 7)) (* (/ 1 7) n (expt ε 7))) - (v/freeze + (g/freeze (g/simplify (nth (seq (((g/exp (* 'ε D)) #(g/expt (+ 1 %) 'n)) 0)) 7)))))) (testing "mixed types don't combine" @@ -379,6 +385,22 @@ (o/context (* o p)) (o/context (* p o)))))) + (testing "bring your own zero/one/identity functions" + (let [wrap (fn [tag] + (fn [o] (* o (o/make-operator #(comp (f/literal-function tag) %) tag)))) + o (o/make-operator identity 'o {:zero-like (wrap 'Zero-like) + :one-like (wrap 'One-like) + :identity-like (wrap 'Identity-like) + :one? (wrap 'One?) + :zero? (wrap 'Zero?)})] + (is (= '(f x) (g/freeze (f 'x)))) + (is (= '(f x) (g/freeze ((o f) 'x)))) + (is (= '(Zero-like (f x)) (g/freeze (((g/zero-like o) f) 'x)) )) + (is (= '(One-like (f x)) (g/freeze (((g/one-like o) f) 'x)))) + (is (= '(Identity-like (f x)) (g/freeze (((g/identity-like o) f) 'x)))) + (is (= '(One? (f x)) (g/freeze (((g/one? o) f) 'x)))) + (is (= '(Zero? (f x)) (g/freeze (((g/zero? o) f) 'x)))))) + (testing "*, -, + between operators simplifies" (is (= (o/procedure D) (o/procedure (* o/identity D)) @@ -386,16 +408,16 @@ "* ignores identity") (is (= (o/procedure D) - (o/procedure (+ D (v/zero-like D))) - (o/procedure (+ (v/zero-like D) D))) + (o/procedure (+ D (g/zero-like D))) + (o/procedure (+ (g/zero-like D) D))) "+ ignores zeros") (is (= (o/procedure D) - (o/procedure (- D (v/zero-like D)))) + (o/procedure (- D (g/zero-like D)))) "- ignores zeros on right") (is (not= (o/procedure D) - (o/procedure (- (v/zero-like D) D))) + (o/procedure (- (g/zero-like D) D))) "- does NOT ignore zero on left"))) ;;; more testing to come as we implement multivariate literal functions that diff --git a/test/emmy/polynomial/factor_test.cljc b/test/emmy/polynomial/factor_test.cljc index ed6adf07..18511725 100644 --- a/test/emmy/polynomial/factor_test.cljc +++ b/test/emmy/polynomial/factor_test.cljc @@ -9,9 +9,7 @@ [emmy.numbers] [emmy.polynomial :as p] [emmy.polynomial.factor :as pf] - [emmy.simplify - :refer [hermetic-simplify-fixture simplify-expression]] - [emmy.value :as v])) + [emmy.simplify :refer [hermetic-simplify-fixture simplify-expression]])) (use-fixtures :each hermetic-simplify-fixture) @@ -58,25 +56,25 @@ (expt (+ y (cos (expt (+ (* x (expt y 2)) x) 2))) 2) (expt (+ y (* -1 (cos (expt (+ (* x (expt y 2)) x) 2)))) 3)) (pf/factor - (v/freeze test-poly)))))) + (g/freeze test-poly)))))) (testing "factoring works on literals" (let [expr (g// (g/square (g/+ 'x 'y)) (g/square (g/+ 'x 'z)))] (is (= '(/ (expt (+ x y) 2) (expt (+ x z) 2)) - (v/freeze expr)) + (g/freeze expr)) "unfactored before simplification.") (is (= '(/ (+ (expt x 2) (* 2 x y) (expt y 2)) (+ (expt x 2) (* 2 x z) (expt z 2))) - (v/freeze + (g/freeze (g/simplify expr))) "simplification expands by default.") (is (= '(/ (expt (+ x y) 2) (expt (+ x z) 2)) - (v/freeze + (g/freeze (pf/factor (g/simplify expr)))) "calling factor re-factors the expression!")))) diff --git a/test/emmy/polynomial/gcd_test.cljc b/test/emmy/polynomial/gcd_test.cljc index 2c08e32f..56ce4735 100644 --- a/test/emmy/polynomial/gcd_test.cljc +++ b/test/emmy/polynomial/gcd_test.cljc @@ -452,9 +452,9 @@ (gen/tuple (sg/polynomial :arity arity) (sg/polynomial :arity arity)))] (let [g (g/gcd u v)] - (is (or (and (v/zero? u) - (v/zero? v) - (v/zero? g)) + (is (or (and (g/zero? u) + (g/zero? v) + (g/zero? g)) (and (g/exact-divide u g) (g/exact-divide v g)))))) diff --git a/test/emmy/polynomial/interpolate_test.cljc b/test/emmy/polynomial/interpolate_test.cljc index e7ed4fa2..d024aa4c 100644 --- a/test/emmy/polynomial/interpolate_test.cljc +++ b/test/emmy/polynomial/interpolate_test.cljc @@ -23,18 +23,18 @@ (testing "Neville and Lagrange interpolation are equivalent" (let [points [['x_1 'y_1] ['x_2 'y_2]]] - (is (v/zero? + (is (g/zero? (diff (pi/lagrange points 'x) (pi/neville-recursive points 'x)))))) (testing "points ordering doesn't matter for the final value. (Should test all permutations...)" - (is (v/zero? + (is (g/zero? (diff (pi/lagrange [['x_1 'y_1] ['x_2 'y_2] ['x_3 'y_3]] 'x) (pi/lagrange [['x_2 'y_2] ['x_1 'y_1] ['x_3 'y_3]] 'x)))) - (is (v/zero? + (is (g/zero? (diff (pi/lagrange [['x_2 'y_2] ['x_1 'y_1] ['x_3 'y_3]] 'x) (pi/lagrange [['x_3 'y_3] ['x_2 'y_2] ['x_1 'y_1]] 'x))))) diff --git a/test/emmy/polynomial/richardson_test.cljc b/test/emmy/polynomial/richardson_test.cljc index 69ed0fed..c2b95d4b 100644 --- a/test/emmy/polynomial/richardson_test.cljc +++ b/test/emmy/polynomial/richardson_test.cljc @@ -5,8 +5,8 @@ [emmy.numbers] [emmy.polynomial.interpolate :as pi] [emmy.polynomial.richardson :as pr] + [emmy.util :as u] [emmy.util.stream :as us] - [emmy.value :as v] [same.core :refer [ish?]])) (deftest richardson-limit-tests @@ -16,21 +16,21 @@ (is (ish? {:converged? true :terms-checked 26 :result 3.1415926535897944} - (us/seq-limit pi-seq {:tolerance v/machine-epsilon})))) + (us/seq-limit pi-seq {:tolerance u/machine-epsilon})))) (testing "with richardson, we go faster." (is (ish? {:converged? true :terms-checked 7 :result 3.1415926535897936} (-> (pr/richardson-sequence pi-seq 2 2 2) - (us/seq-limit {:tolerance v/machine-epsilon})))) + (us/seq-limit {:tolerance u/machine-epsilon})))) (is (ish? {:converged? false :terms-checked 3 :result 3.1415903931299374} (-> (take 3 pi-seq) (pr/richardson-sequence 2 2 2) - (us/seq-limit {:tolerance v/machine-epsilon}))) + (us/seq-limit {:tolerance u/machine-epsilon}))) "richardson-sequence bails if the input sequence runs out of terms.") (is (ish? [2.8284271247461903 diff --git a/test/emmy/polynomial_test.cljc b/test/emmy/polynomial_test.cljc index a3adf1ae..388d1b27 100644 --- a/test/emmy/polynomial_test.cljc +++ b/test/emmy/polynomial_test.cljc @@ -62,7 +62,7 @@ [p (sg/polynomial)] (is (p/polynomial? p)) (is (not (p/coeff? p))) - (is (not (v/exact? p))) + (is (not (g/exact? p))) (is (= ::p/polynomial (v/kind p)) "kind works")) @@ -96,71 +96,71 @@ "term limitation in printing") (is (= '(polynomial 1 [[{} 1] [{0 1} 2] [{0 2} 3]]) - (v/freeze + (g/freeze (p/make [1 2 3]))) "freeze representation isn't THAT great yet...")) (checking "zero-like" 100 [p (sg/polynomial)] - (is (v/zero? - (v/zero-like p)))) + (is (g/zero? + (g/zero-like p)))) (testing "one" - (is (not (v/one? (p/make [])))) - (is (v/one? (p/make [1]))) - (is (v/one? (p/make 2 {[0 0] 1}))) - (is (v/one? (p/make 3 {[0 0 0] 1}))) - (is (not (v/one? (p/make 3 {[0 0 0] 1 [0 0 1] 2})))) - (is (not (v/one? (p/make [1.1])))) - (is (v/one? (p/make [1.0]))) - (is (v/one? (p/make [(p/make [1])]))) - (is (not (v/one? (p/make [(p/make [2])]))))) + (is (not (g/one? (p/make [])))) + (is (g/one? (p/make [1]))) + (is (g/one? (p/make 2 {[0 0] 1}))) + (is (g/one? (p/make 3 {[0 0 0] 1}))) + (is (not (g/one? (p/make 3 {[0 0 0] 1 [0 0 1] 2})))) + (is (not (g/one? (p/make [1.1])))) + (is (g/one? (p/make [1.0]))) + (is (g/one? (p/make [(p/make [1])]))) + (is (not (g/one? (p/make [(p/make [2])]))))) (checking "one-like" 100 [p (sg/polynomial)] - (is (v/one? - (v/one-like p)))) + (is (g/one? + (g/one-like p)))) (testing "one-like unit tests" (is (= (p/constant 1 1) - (v/one-like (p/make [1 2 3])))) + (g/one-like (p/make [1 2 3])))) (is (= (p/constant 2 1) - (v/one-like + (g/one-like (p/make 2 {[1 0] 1 [2 1] 3})))) (is (= (p/constant 3 1) - (v/one-like + (g/one-like (p/make 3 {[1 2 1] 4 [0 1 0] 5})))) (is (= (p/make 2 {[0 0] 1}) - (v/one-like (p/make 2 []))) + (g/one-like (p/make 2 []))) "If we can't deduce the unit element from the zero polynomial over an unknown ring, assume it's 1")) (checking "identity-like (only on monomials)" 100 [p (sg/polynomial :arity 1)] - (is (v/identity? - (v/identity-like p)))) + (is (g/identity? + (g/identity-like p)))) (testing "identity unit tests" - (is (v/identity? (p/make [0 1]))) - (is (not (v/identity? (p/make [])))) - (is (not (v/identity? (p/make [0])))) + (is (g/identity? (p/make [0 1]))) + (is (not (g/identity? (p/make [])))) + (is (not (g/identity? (p/make [0])))) (testing "identity? only returns true for monomials." - (is (v/identity? (p/identity 1))) - (is (not (v/identity? (p/identity 2 1)))))) + (is (g/identity? (p/identity 1))) + (is (not (g/identity? (p/identity 2 1)))))) (testing "identity-like unit tests" (is (= (p/make [0 1]) - (v/identity-like (p/make [0 0 0 1])))) + (g/identity-like (p/make [0 0 0 1])))) (is (= (p/make [0 1]) - (v/identity-like (p/make [1 2 3])))) + (g/identity-like (p/make [1 2 3])))) (is (thrown? #?(:clj AssertionError :cljs js/Error) - (v/identity-like (p/constant 10 1))) + (g/identity-like (p/constant 10 1))) "identity-like is only supported on monomials.")))) (deftest constructor-accessor-tests @@ -262,9 +262,9 @@ "unless both sides are coeffs!"))) (testing "dense make returns 0 for no entries or a zero first entry" - (is (v/zero? (p/make []))) - (is (v/zero? (p/make [0]))) - (is (not (v/zero? (p/make [1]))))) + (is (g/zero? (p/make []))) + (is (g/zero? (p/make [0]))) + (is (not (g/zero? (p/make [1]))))) (checking "dense construction round-trips with univariate->dense" 100 [prefix (gen/vector gen/nat 1 20) @@ -303,19 +303,19 @@ (* (/ 1 2) (expt x 2)) x 1) - (v/freeze + (g/freeze (g/simplify ((p/from-power-series ss/exp-series 4) 'x)))))) (checking "p/make returns zero only if first entry is zero" 100 [arity gen/nat x sg/number] - (if (v/zero? x) - (is (v/zero? (p/make [x]))) + (if (g/zero? x) + (is (g/zero? (p/make [x]))) (is (= x (p/make [x])))) - (if (v/zero? x) - (is (v/zero? (p/constant arity x))) + (if (g/zero? x) + (is (g/zero? (p/constant arity x))) (is (v/= x (p/constant arity x))))) (checking "terms, lead term" 100 @@ -381,10 +381,10 @@ (is (not (p/monic? 2)))) (checking "scale, scale-l" 100 [p (sg/polynomial)] - (is (v/zero? (p/scale-l 0 p))) - (is (v/zero? (p/scale-l p 0))) - (is (v/zero? (p/scale 0 p))) - (is (v/zero? (p/scale p 0)))) + (is (g/zero? (p/scale-l 0 p))) + (is (g/zero? (p/scale-l p 0))) + (is (g/zero? (p/scale 0 p))) + (is (g/zero? (p/scale p 0)))) (checking "map-exponents works on scalars" 100 [c gen/nat @@ -457,7 +457,7 @@ (checking "drop-leading-term" 100 [xs (gen/vector (gen/fmap inc gen/nat) 2 20) c (gen/fmap inc gen/nat)] - (is (v/zero? (p/drop-leading-term c)) + (is (g/zero? (p/drop-leading-term c)) "dropping the leading term from a constant returns 0.") (is (= (p/make xs) @@ -476,7 +476,7 @@ (+ (expt x 5) (* 10 (expt x 4)) (* 25 (expt x 3)) (* 15 (expt x 2)) x)] (map #(-> (p/touchard %) (p/->expression ['x]) - (v/freeze)) + (g/freeze)) (range -1 6))) "Touchard matches examples from https://mathworld.wolfram.com/BellPolynomial.html")) @@ -520,7 +520,7 @@ (g/negate (p/make l))))) (testing "add/sub unit tests" - (is (v/zero? + (is (g/zero? (g/add (p/make [0 0 2]) (p/make [0 0 -2])))) @@ -536,7 +536,7 @@ (g/add (p/make [0 1]) (p/make [-1])))) - (is (v/zero? + (is (g/zero? (g/sub (p/make [0 0 2]) (p/make [0 0 2])))) @@ -580,7 +580,7 @@ (let [p*q (g/mul p q) [Q R] (p/divide p*q q)] (is (p/divisible? p*q q)) - (is (v/zero? R)) + (is (g/zero? R)) (is (= p Q)))) (testing "mul" @@ -810,7 +810,7 @@ [p (sg/polynomial :arity arity) xs (gen/vector sg/symbol arity)] (is (every? - v/zero? + g/zero? (for [idx (range (inc arity))] (let [sub-xs (subvec xs 0 idx) padded (into sub-xs (repeat (- arity idx) 0))] @@ -857,13 +857,13 @@ [term-count (gen/choose 2 10) factor pos p (gen/fmap p/make (gen/vector pos term-count))] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (p (g/* 'x factor)) ((p/arg-scale p [factor]) 'x)))) "arg-scale") - (is (v/zero? + (is (g/zero? (g/simplify (g/- (p (g/+ 'x factor)) ((p/arg-shift p [factor]) 'x)))) @@ -908,13 +908,13 @@ U (p/make 2 [[[1 1] 3] [[2 2] 4] [[0 0] 5] [[0 3] 7] [[4 0] -2]])] (testing "univariate and multivariate polynomials work with D operator" (is (= '(+ (* 12 (expt x 2)) (* 6 x) 2) - (v/freeze + (g/freeze (g/simplify ((D V) 'x))))) (is (= '(down (+ (* -8 (expt x 3)) (* 8 x (expt y 2)) (* 3 y)) (+ (* 8 (expt x 2) y) (* 21 (expt y 2)) (* 3 x))) - (v/freeze + (g/freeze (g/simplify ((D U) 'x 'y)))))) diff --git a/test/emmy/quaternion_test.cljc b/test/emmy/quaternion_test.cljc index dc41f2ca..8f5fb8de 100644 --- a/test/emmy/quaternion_test.cljc +++ b/test/emmy/quaternion_test.cljc @@ -152,35 +152,35 @@ (testing "value protocol" (testing "zero?" - (is (q/zero? q/ZERO)) - (is (v/zero? (q/make 0 0 0 0))) - (is (not (v/zero? (q/make 1 0 0 0))))) + (is (g/zero? q/ZERO)) + (is (g/zero? (q/make 0 0 0 0))) + (is (not (g/zero? (q/make 1 0 0 0))))) (checking "zero-like" 100 [x (sg/quaternion)] - (if (v/zero? x) + (if (g/zero? x) (is (= x (q/make 0 0 0 0))) - (do (is (v/zero? (v/zero-like x))) - (is (v/zero? (empty x)) + (do (is (g/zero? (g/zero-like x))) + (is (g/zero? (empty x)) "empty also returns the zero")))) (testing "one?" - (is (q/one? q/ONE)) - (is (v/identity? q/ONE))) + (is (g/one? q/ONE)) + (is (g/identity? q/ONE))) (checking "one-like, identity-like" 100 [x (sg/quaternion)] - (if (v/one? x) + (if (g/one? x) (is (= x (q/make 1 0 0 0))) - (is (v/one? (v/one-like x)))) + (is (g/one? (g/one-like x)))) - (if (v/identity? x) + (if (g/identity? x) (is (= x (q/make 1 0 0 0))) - (is (v/identity? (v/identity-like x))))) + (is (g/identity? (g/identity-like x))))) (testing "exact?" - (is (v/exact? (q/make 1 2 3 4))) - (is (not (v/exact? (q/make 1.2 3 4 5)))) - (is (v/exact? (q/make 1 2 3 #emmy/ratio 3/2))) - (is (not (v/exact? (q/make 0 0 0 0.00001))))) + (is (g/exact? (q/make 1 2 3 4))) + (is (not (g/exact? (q/make 1.2 3 4 5)))) + (is (g/exact? (q/make 1 2 3 #emmy/ratio 3/2))) + (is (not (g/exact? (q/make 0 0 0 0.00001))))) (testing "numerical?" (is (not (v/numerical? (s/up 1 2 3 4))) @@ -188,7 +188,7 @@ (testing "freeze" (is (= '(quaternion (/ 1 2) 2 3 x) - (v/freeze (q/make #emmy/ratio 1/2 + (g/freeze (q/make #emmy/ratio 1/2 2 3 'x))))) (checking "kind" 100 [x (sg/quaternion)] @@ -531,20 +531,20 @@ (g/dot-product x-complex x)) "quaternion dots with complex") - (is (= (g/dot-product x x-complex) - (g/dot-product x-complex x-complex)) + (is (== (g/dot-product x x-complex) + (g/dot-product x-complex x-complex)) "quaternion dots with complex") - (is (= (g/dot-product x x-real) - (g/dot-product x-real x) - (g/dot-product x-real x-real)) + (is (== (g/dot-product x x-real) + (g/dot-product x-real x) + (g/dot-product x-real x-real)) "quaternion dots with real")) (let [m (q/magnitude x) normal (q/normalize x)] (is (ish? normal (q/normalize normal))) - (if (v/zero? m) - (is (q/zero? x) + (if (g/zero? m) + (is (g/zero? x) "can't normalize if the quaternion is zero.") (is (q/unit? normal :epsilon 1e-8) @@ -560,7 +560,7 @@ (is (q/pure? q1xq2) "quaternion cross product has no real component") - (is (v/zero? (g/dot-product q1 q1xq2)) + (is (g/zero? (g/dot-product q1 q1xq2)) "dot of quaternion with an orthogonal quaternion == 0") (testing "cross with scalar" @@ -589,12 +589,12 @@ (testing "commutator" (let [p (q/make 'r1 'i1 'j1 'k1) q (q/make 'r2 'i2 'j2 'k2)] - (is (q/zero? + (is (g/zero? (g/simplify (q/commutator p p))) "the commutator of a vector with itself is zero") - (is (q/zero? + (is (g/zero? (g/simplify (g/- (q/commutator p q) (g/cross-product (g/* 2 p) q)))) @@ -620,20 +620,20 @@ (checking "q/commutator" 100 [q1 (sg/quaternion sg/small-integral) q2 (sg/quaternion sg/small-integral)] - (is (v/zero? + (is (g/zero? (q/commutator (q/make (first (q/->complex-pair q1))) (q/make (first (q/->complex-pair q2))))) "complex multiplication commutes, so the commutator of the complex part (r,i) is always zero.") - (is (v/zero? + (is (g/zero? (q/commutator (q/make (q/real-part q1)) q2)) "real quaternions commute with all other quaternions") - (is (v/zero? + (is (g/zero? (q/commutator q1 (q/make (q/real-part q2)))) @@ -671,14 +671,14 @@ (testing "q/log unit tests" (is (= '(quaternion (log y) (* (/ 1 2) pi) 0 0) - (v/freeze + (g/freeze (g/simplify (q/log (q/make 0 'y 0 0))))) "this test failed before a fix in `emmy.numsymb` forced atan to return an exact value of `pi/2` instead of a floating point number.") (is (= '(quaternion (log y) 0 0 0) - (v/freeze + (g/freeze (g/simplify (q/log (q/make 'y 0 0 0))))) "note that symbolic log on a real quaternion generates a symbolic real @@ -723,7 +723,7 @@ (q/expt x 2)) "q*q == q^2, expt impl matches manual exponentiation") - (is (q/one? (g/expt x q/ZERO)) + (is (g/one? (g/expt x q/ZERO)) "x to the quaternion 0 power == 1") (is (ish? x (g/expt x q/ONE)) @@ -865,7 +865,7 @@ (up x y (sqrt (+ (* -1 (expt x 2)) (* -1 (expt y 2)) 1)))) - (v/freeze + (g/freeze (g/simplify (q/->angle-axis (q/from-angle-axis @@ -915,6 +915,26 @@ "Build up the tensor, check that it matches the matrix version.")))) +(deftest symbolic-rotation-matrices + (is (= (q/make (g/sqrt (g/+ (g/* (/ 3 4) 'x) (/ 4))) 0 0 0) + (g/simplify + (q/from-rotation-matrix + (m/by-rows ['x 0 0] + [0 'x 0] + [0 0 'x]))))) + (is (= (q/make 0 (g/sqrt (g/+ (g/* (/ 2) 'x) (/ 2))) 0 0) + (g/simplify + (q/from-rotation-matrix + (m/by-rows ['x 0 0] + [0 -1 0] + [0 0 (g/- 'x)]))))) + (is (= (q/make 0 0 (g/sqrt (g/+ (g/* (/ 2) 'x) (/ 2))) 0) + (g/simplify + (q/from-rotation-matrix + (m/by-rows [-1 0 0] + [0 'x 0] + [0 0 (g/- 'x)])))))) + (deftest rotation-matrix-tests (checking "to and from 3x3 rotation matrices" 100 [x (sg/quaternion)] diff --git a/test/emmy/ratio_test.cljc b/test/emmy/ratio_test.cljc index 98efb79f..86e605bb 100644 --- a/test/emmy/ratio_test.cljc +++ b/test/emmy/ratio_test.cljc @@ -15,16 +15,16 @@ [same.core :refer [ish?]])) (deftest ratio-value-implementation - (testing "v/freeze" - (is (= '(/ 1 2) (v/freeze #emmy/ratio 1/2))) - (is (= 2 (v/freeze #emmy/ratio 10/5)) + (testing "g/freeze" + (is (= '(/ 1 2) (g/freeze #emmy/ratio 1/2))) + (is (= 2 (g/freeze #emmy/ratio 10/5)) "Numbers pass through") - (is (= 2 (v/freeze #emmy/ratio "10/5")))) + (is (= 2 (g/freeze #emmy/ratio "10/5")))) - (checking "v/exact? is always true for ratios, v/kind works" + (checking "g/exact? is always true for ratios, v/kind works" 100 [r sg/big-ratio] - (is (v/exact? r)) + (is (g/exact? r)) (let [k (v/kind r)] (is (or (= k r/ratiotype) (= k u/biginttype)) @@ -72,8 +72,8 @@ 100 [n sg/any-integral d sg/bigint - :when (and (not (v/zero? d)) - (not (v/one? d)))] + :when (and (not (g/zero? d)) + (not (g/one? d)))] (is (= n (g/mul d (r/rationalize n d))) "multiplying by denominator recovers numerator") (let [r (r/rationalize n d) diff --git a/test/emmy/rational_function_test.cljc b/test/emmy/rational_function_test.cljc index 462cf68c..e1656e64 100644 --- a/test/emmy/rational_function_test.cljc +++ b/test/emmy/rational_function_test.cljc @@ -56,24 +56,24 @@ x-1 (p/make [-1 1]) x+1:x-1 (rf/make x+1 x-1)] (testing "zero?, one-like" - (is (v/zero? (v/zero-like x+1:x-1))) - (is (v/zero? (g/* x+1:x-1 (v/zero-like x+1:x-1))))) + (is (g/zero? (g/zero-like x+1:x-1))) + (is (g/zero? (g/* x+1:x-1 (g/zero-like x+1:x-1))))) (testing "one?, one-like" - (is (v/one? (v/one-like x+1:x-1))) - (is (= x+1:x-1 (g/* x+1:x-1 (v/one-like x+1:x-1))))) + (is (g/one? (g/one-like x+1:x-1))) + (is (= x+1:x-1 (g/* x+1:x-1 (g/one-like x+1:x-1))))) (testing "identity?, identity-like" - (is (v/identity? (v/identity-like x+1:x-1))) + (is (g/identity? (g/identity-like x+1:x-1))) (is (= (g/* (p/make [0 1]) x+1:x-1) - (g/* x+1:x-1 (v/identity-like x+1:x-1))) + (g/* x+1:x-1 (g/identity-like x+1:x-1))) "identity is `x`, multiplying should be equivalent to multiplying by x.")) - (testing "v/freeze" + (testing "g/freeze" (is (= '(/ (polynomial 1 [[{} 1] [{0 1} 1]]) (polynomial 1 [[{} -1] [{0 1} 1]])) - (v/freeze x+1:x-1)))) + (g/freeze x+1:x-1)))) (testing "v/numerical?" (is (not (v/numerical? x+1:x-1)))) @@ -250,7 +250,7 @@ xs (gen/vector sg/symbol arity)] (let [rf (rf/->RationalFunction arity n 12 nil)] (is (every? - v/zero? + g/zero? (for [idx (range (inc arity))] (let [sub-xs (subvec xs 0 idx) padded (into sub-xs (repeat (- arity idx) 0))] @@ -266,13 +266,13 @@ [r (sg/rational-function) factor (gen/fmap inc gen/nat)] (let [scaled (rf/arg-scale r [factor])] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (r (g/* 'x factor)) (rf/evaluate scaled ['x])))))) (let [shifted (rf/arg-shift r [factor])] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (r (g/+ 'x factor)) (rf/evaluate shifted ['x]))))))) @@ -336,7 +336,7 @@ (is (= 3 (rf-simp '(gcd 9 (* x 6 y))))) (is (= '(* 7 y) (rf-simp '(gcd (* 14 x y) (* 21 y z))))) (is (= '(* (/ 1 6) y) - (v/freeze + (g/freeze (rf-simp '(gcd (* (/ 5 2) x y) (* (/ 7 3) y z))))) "Can handle rational gcd!")) @@ -358,7 +358,7 @@ x-1 (p/make [-1 1]) x+1:x-1 (rf/make x+1 x-1)] (is (= '(/ -2 (+ (expt x 2) (* -2 x) 1)) - (v/freeze + (g/freeze (g/simplify ((D x+1:x-1) 'x))))) diff --git a/test/emmy/series/impl_test.cljc b/test/emmy/series/impl_test.cljc index d63d386d..d1d05f9d 100644 --- a/test/emmy/series/impl_test.cljc +++ b/test/emmy/series/impl_test.cljc @@ -2,8 +2,8 @@ (ns emmy.series.impl-test (:require [clojure.test :refer [is deftest testing]] - [emmy.series.impl :as i] - [emmy.value :as v])) + [emmy.generic :as g] + [emmy.series.impl :as i])) (deftest sequence-series-tests (testing "make a sequence" @@ -161,7 +161,7 @@ (/ 1 5040) (/ 1 40320) (/ 1 362880)) - (v/freeze (take 10 i/expx))))) + (g/freeze (take 10 i/expx))))) (testing "sine expansion" (is (= '(0 @@ -174,7 +174,7 @@ (/ -1 5040) 0 (/ 1 362880)) - (v/freeze (take 10 i/sinx))))) + (g/freeze (take 10 i/sinx))))) (testing "cosine expansion" (is (= '(1 @@ -187,7 +187,7 @@ 0 (/ 1 40320) 0) - (v/freeze (take 10 i/cosx))))) + (g/freeze (take 10 i/cosx))))) (testing "catalan numbers" (is (= [1 1 2 5 14 42 132 429 1430 4862] @@ -197,17 +197,17 @@ (is (->> (i/seq:- i/sinx (i/sqrt (i/c-seq 1 (i/expt i/cosx 2)))) (take 30) - (every? v/zero?)) + (every? g/zero?)) "sin(x) = sqrt(1-cos(x)^2) to 30 terms") (is (->> (i/seq:- i/tanx (i/revert i/atanx)) (take 30) - (every? v/zero?)) + (every? g/zero?)) "tan(x) = revert(arctan(x))") (is (->> (i/seq:- i/atanx (i/integral (i/invert (i/->series [1 0 1])))) (take 30) - (every? v/zero?)) + (every? g/zero?)) "atan(x) = integral(1/(1+x^2))")) diff --git a/test/emmy/series_test.cljc b/test/emmy/series_test.cljc index e66a1004..110b4226 100644 --- a/test/emmy/series_test.cljc +++ b/test/emmy/series_test.cljc @@ -24,31 +24,31 @@ (testing "v/numerical?" (is (not (v/numerical? series)))) - (testing "v/exact?" - (is (not (v/exact? series)))) + (testing "g/exact?" + (is (not (g/exact? series)))) (testing "zero-like" (is (= (take 10 s/zero) - (take 10 (g/* series (v/zero-like series)))))) + (take 10 (g/* series (g/zero-like series)))))) (testing "one-like" (is (= (take 10 series) - (take 10 (g/* series (v/one-like series)))))) + (take 10 (g/* series (g/one-like series)))))) (testing "identity-like" (let [id (if (s/power-series? series) (s/power-series* [0 1]) (s/series* [0 1]))] (is (= (take 10 (g/* id series)) - (take 10 (g/* series (v/identity-like series)))) + (take 10 (g/* series (g/identity-like series)))) "the identity-series is an identity on APPLICATION, not for multiplication with other series."))) (testing "meta / with-meta work") (testing "one? zero? identity? always return false (for now!)" - (is (not (v/zero? (v/zero-like series)))) - (is (not (v/one? (v/one-like series)))) - (is (not (v/identity? (v/identity-like series))))) + (is (not (g/zero? (g/zero-like series)))) + (is (not (g/one? (g/one-like series)))) + (is (not (g/identity? (g/identity-like series))))) (checking "f/arity" 100 [v (sg/power-series sg/real)] (is (= [:exactly 1] @@ -83,7 +83,7 @@ (check-series (s/series 1 2 3 4))) (checking "identity-like power-series application" 100 [n sg/real] - (is (= n (-> ((v/identity-like s/sin-series) n) + (is (= n (-> ((g/identity-like s/sin-series) n) (s/sum 50))) "evaluating the identity series at `n` will return a series that sums to `n`."))) @@ -259,7 +259,33 @@ (testing "series derivative" (is (= [1 2 3 4 5 6] ;; 1 + 2x + 3x^2 + ... (take 6 (-> (s/generate (constantly 1)) - (g/partial-derivative [])))))))) + (g/partial-derivative []))))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (-> (s/generate (constantly 1)) + (g/partial-derivative [1]))))) + + (testing "series value" + (is (= '(1 x (expt x 2) (expt x 3)) + (take 4 (-> (s/generate (constantly 1)) + (s/value 'x) + g/simplify + g/freeze)))) + (is (= (take 4 (-> (s/series* [g/sin g/cos g/tan]) + (s/value '[x]) + g/simplify + g/freeze)) + '((sin x) (cos x) (tan x) 0)))) + + (testing "series string form" + (let [segment [4 5 6 7 8] + s (s/series* segment) + p (s/power-series* segment)] + + (is (= "(+ 4 5 6 7 ...)" (str s))) + (is (= "(+ (* 4 (expt _ 0)) (* 5 (expt _ 1)) (* 6 (expt _ 2)) (* 7 (expt _ 3)) ...)" + (str p))) + (is (= (str s) (str (g/freeze s)))) + (is (= (str p) (str (g/freeze p)))))))) (deftest series-as-fn-tests (let [f (fn [i] #(g/* %1 %2 i)) @@ -269,6 +295,35 @@ (is (= [0 6 12 18 24 30] (take 6 (square-series 2 3))))) + (testing "series of fns with high arity" + (let [sum #(reduce + 0 %&) + product #(reduce * 1 %&) + S (s/series* [sum product]) + expect (fn [n] + (let [ns (range 1 (inc n))] + (list (apply sum ns) (apply product ns) 0)))] + (is (= (expect 0) (take 3 (S)))) + (is (= (expect 1) (take 3 (S 1)))) + (is (= (expect 2) (take 3 (S 1 2)))) + (is (= (expect 3) (take 3 (S 1 2 3)))) + (is (= (expect 4) (take 3 (S 1 2 3 4)))) + (is (= (expect 5) (take 3 (S 1 2 3 4 5)))) + (is (= (expect 6) (take 3 (S 1 2 3 4 5 6)))) + (is (= (expect 7) (take 3 (S 1 2 3 4 5 6 7)))) + (is (= (expect 8) (take 3 (S 1 2 3 4 5 6 7 8)))) + (is (= (expect 9) (take 3 (S 1 2 3 4 5 6 7 8 9)))) + (is (= (expect 10) (take 3 (S 1 2 3 4 5 6 7 8 9 10)))) + (is (= (expect 11) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11)))) + (is (= (expect 12) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12)))) + (is (= (expect 13) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13)))) + (is (= (expect 14) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14)))) + (is (= (expect 15) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))) + (is (= (expect 16) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)))) + (is (= (expect 17) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)))) + (is (= (expect 18) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)))) + (is (= (expect 19) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)))) + (is (= (expect 20) (take 3 (S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)))))) + (testing "a series of fns is a fn too" (let [nats*index-series (-> (fn [i] (g/* i nats)) (s/generate ::s/series))] @@ -278,7 +333,7 @@ (+ (* 3 (expt x 2)) (* 4 x) 3) (+ (* 4 (expt x 3)) (* 6 (expt x 2)) (* 6 x) 4) (+ (* 5 (expt x 4)) (* 8 (expt x 3)) (* 9 (expt x 2)) (* 8 x) 5)) - (v/freeze + (g/freeze (g/simplify (take 6 (nats*index-series 'x)))))))))) @@ -302,7 +357,7 @@ (let [ps (s/->function (s/series 1 2 3))] (is (= '(1 (* 2 x) (* 3 x x) 0 0) - (v/freeze (take 5 (ps 'x)))) + (g/freeze (take 5 (ps 'x)))) "->function converts a series to a power series.")) (testing "function->" @@ -314,7 +369,7 @@ (-> ((s/function-> g/sin) 'x) (s/sum 10) (g/simplify) - (v/freeze))) + (g/freeze))) "power series expansion of g/sin around 0, evaluated at 'x") (is (= '(+ (* (/ 1 6) (expt dx 3) (exp a)) @@ -324,7 +379,7 @@ (-> ((s/function-> g/exp 'a) 'dx) (s/sum 3) (g/simplify) - (v/freeze))) + (g/freeze))) "power series expansion of g/exp around 'a, evaluated at 'dx") (is (= '(/ (+ @@ -343,7 +398,7 @@ (-> ((s/function-> g/atan 'a 'b) ['da 'db]) (s/sum 2) (g/simplify) - (v/freeze))) + (g/freeze))) "power series expansion of g/atan around 'a and 'b, evaluated at (the vector value) ['da 'db]. Shows that function-> can handle multiple arguments without a structural wrapping.") @@ -505,7 +560,7 @@ (/ 1 5040) (/ 1 40320) (/ 1 362880)) - (v/freeze (take 10 s/exp-series))))) + (g/freeze (take 10 s/exp-series))))) (testing "sine expansion" (is (= '(0 @@ -518,7 +573,7 @@ (/ -1 5040) 0 (/ 1 362880)) - (v/freeze (take 10 s/sin-series))))) + (g/freeze (take 10 s/sin-series))))) (testing "cosine expansion" (is (= '(1 @@ -531,7 +586,7 @@ 0 (/ 1 40320) 0) - (v/freeze (take 10 s/cos-series))))) + (g/freeze (take 10 s/cos-series))))) (testing "catalan numbers" (is (= [1 1 2 5 14 42 132 429 1430 4862] @@ -553,19 +608,19 @@ (is (->> (g/- s/sin-series (g/sqrt (g/- 1 (g/expt s/cos-series 2)))) (take 30) - (every? v/zero?)) + (every? g/zero?)) "sin(x) = sqrt(1-cos(x)^2) to 30 terms") (is (->> (g/- s/tan-series (s/revert s/atan-series)) (take 30) - (every? v/zero?)) + (every? g/zero?)) "tan(x) = revert(arctan(x))") (is (->> (g/- s/atan-series (s/integral (g/invert (s/power-series 1 0 1)))) (take 30) - (every? v/zero?)) + (every? g/zero?)) "atan(x) = integral(1/(1+x^2))")) (deftest series-trig-tests @@ -573,13 +628,24 @@ bit of the library." (is (= [0 1 0 0 0] (take 5 (g/sin s/asin-series)))) + (is (= [0 1 0 0 0] + (take 5 (g/asin s/sin-series)))) (is (= [0 1 0 0 0] (take 5 (g/tan s/atan-series)))) + (is (= [0 1 0 0 0] + (take 5 (g/atan s/tan-series)))) (is (= [0 1 0 0 0] (take 5 (g/sinh s/asinh-series)))) + (is (= [0 1 0 0 0] + (take 5 (g/asinh s/sinh-series)))) (is (= [0 1 0 0 0] (take 5 (g/tanh s/atanh-series)))) + (is (= [0 1 0 0 0] + (take 5 (g/atanh s/tanh-series)))) + + (is (= [1 0 0 0 0] (take 5 (g/cosh (s/constant 0))))) + (is (= [(g/acot 0) 0 0 0 0] (take 5 (g/acot (s/constant 0))))) (is (= (take 20 s/sec-series) (take 20 (g/invert s/cos-series)))) diff --git a/test/emmy/sicm/ch1_test.cljc b/test/emmy/sicm/ch1_test.cljc index 82420e11..43e1efc3 100644 --- a/test/emmy/sicm/ch1_test.cljc +++ b/test/emmy/sicm/ch1_test.cljc @@ -12,6 +12,7 @@ Rx Ry Rz]] [emmy.examples.driven-pendulum :as driven] [emmy.examples.pendulum :as pendulum] + [emmy.generic :as g] [emmy.mechanics.lagrange :as L] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.value :as v :refer [within]])) @@ -70,7 +71,7 @@ (is (= '(+ (* (/ 1 2) m (expt ((D x) t) 2)) (* (/ 1 2) m (expt ((D y) t) 2)) (* (/ 1 2) m (expt ((D z) t) 2))) - (v/freeze + (g/freeze (simplify ((compose (L/L-free-particle 'm) (e/Gamma q)) 't))))) ;; p. 20 @@ -179,7 +180,7 @@ (* (/ 1 2) m (expt r 2) (expt φdot 2)) (* (/ 1 2) m (expt rdot 2)) (* -1 (U r))) - (v/freeze + (g/freeze (simplify ((L-alternate-central-polar 'm U) (->local 't (up 'r 'φ) (up 'rdot 'φdot))))))) @@ -217,7 +218,7 @@ (* g l m (cos θ)) (* -1 g m (y_s t)) (* (/ 1 2) m (expt ((D y_s) t) 2))) - (v/freeze + (g/freeze (simplify ((L-pend2 'm 'l 'g y_s) (->local 't 'θ 'θdot)))))))))) (deftest ^:long section-1-7-1 @@ -243,7 +244,7 @@ ((harmonic-state-derivative 2. 1.) (up 0 (up 1. 2.) (up 3. 4.))))) (is (= '(1 3.0 4.0 (/ -1 2) -1.0) - (v/freeze + (g/freeze (flatten ((harmonic-state-derivative 2. 1.) (up 0 (up 1. 2.) (up 3. 4.))))))) ;; p. 72 @@ -328,7 +329,7 @@ (* (/ 1 2) m (expt r 2) (expt θdot 2)) (* (/ 1 2) m (expt rdot 2)) (V r)) - (v/freeze + (g/freeze (simplify ((e/Lagrangian->energy (L3-central 'm V)) spherical-state))))) (let [L (L/L-central-rectangular 'm U) F-tilde (fn [angle-x angle-y angle-z] diff --git a/test/emmy/sicm/ch2_test.cljc b/test/emmy/sicm/ch2_test.cljc index daab0fc7..f4a5b9e4 100644 --- a/test/emmy/sicm/ch2_test.cljc +++ b/test/emmy/sicm/ch2_test.cljc @@ -5,6 +5,7 @@ (:require [clojure.test :refer [is deftest use-fixtures]] [emmy.env :as e :refer [- / zero? ref partial simplify compose up]] + [emmy.generic :as g] [emmy.mechanics.rigid :as r] [emmy.mechanics.rotation :refer [Euler->M]] [emmy.polynomial.gcd :as pg] @@ -117,5 +118,5 @@ (* C φdot ψdot (cos θ)) (* (/ 1 2) A (expt θdot 2)) (* (/ 1 2) C (expt ψdot 2))) - (v/freeze + (g/freeze (simplify ((r/T-rigid-body 'A 'A 'C) Euler-state)))))) diff --git a/test/emmy/simplify/rules_test.cljc b/test/emmy/simplify/rules_test.cljc index b8fbd7dc..e94564f2 100644 --- a/test/emmy/simplify/rules_test.cljc +++ b/test/emmy/simplify/rules_test.cljc @@ -1,15 +1,16 @@ #_"SPDX-License-Identifier: GPL-3.0" (ns emmy.simplify.rules-test - (:require [clojure.test :refer [is deftest testing]] + (:require [clojure.test :refer [is deftest testing use-fixtures]] [emmy.complex :as c] [emmy.generic :as g] [emmy.numbers] [emmy.pattern.rule :as pr :refer [rule-simplifier template]] [emmy.ratio] [emmy.simplify :as s] - [emmy.simplify.rules :as r] - [emmy.value :as v])) + [emmy.simplify.rules :as r])) + +(use-fixtures :each s/hermetic-simplify-fixture) (deftest algebraic-tests (testing "unary elimination" @@ -120,6 +121,16 @@ ((rule) '(log (sqrt x)))) "Drop the internal sqrt down as a 1/2 exponent.")) + (testing "log-contract" + (is (= '(+ (log (* a b))) + ((r/log-contract s/*poly-simplify*) '(+ (log a) (log b))))) + (is (= '(+ (* (log (* x z)) 2) y) + ((r/log-contract s/*poly-simplify*) '(+ (* 2 (log x)) y (* 2 (log z))))))) + + (testing "contract-expt-trig" + (is (= '(* (/ 1 2) (expt (sin x) 0) (- 1 (cos (* 2 x)))) + (r/contract-expt-trig '(expt (sin x) 2))))) + (testing "exp-contract" (is (= '(exp (* 2 x)) (r/exp-contract '(expt (exp x) 2))) @@ -254,7 +265,7 @@ (is (= '(+ (* (/ 1 3) a) (* (/ 1 3) b) (* (/ 1 3) c)) - (v/freeze + (g/freeze (d '(/ (+ a b c) 3))))))) (deftest triginv-tests @@ -264,15 +275,15 @@ (triginv '(atan y x)))) (is (= '(/ pi 4) - (v/freeze + (g/freeze (triginv '(atan 1 1))))) (is (= '(/ pi 4) - (v/freeze + (g/freeze (triginv '(atan x x))))) (is (= '(- (/ (* 3 pi) 4)) - (v/freeze + (g/freeze (triginv '(atan -1 -1))))) (is (= '(atan -1) @@ -323,6 +334,10 @@ (rule '(+ 2 (- (* (expt (cos x) 2) z)) 3 (* z (expt (cos x) 4)))) (rule '(+ 2 (* z (expt (cos x) 4)) 3 (- (* (expt (cos x) 2) z)))))))) + (testing "multiangle" + (is (= '(+ (* -4 (expt (sin x) 3) (cos x)) (* 4 (sin x) (expt (cos x) 3))) + (s/*poly-simplify* (r/expand-multiangle '(sin (* 4 x))))))) + (testing "high degree cosines unwrap the (expt ... 1) remainder." (let [r (rule-simplifier r/split-high-degree-sincos)] (is (= '(+ 1 2 (* (expt (cos x) 2) @@ -352,6 +367,16 @@ "The full rule collects products into exponents."))) (deftest complex-test + (testing "complex-exp" + (is (= `(~'+ (~'cos 1.0) (~'* ~c/I (~'sin 1.0))) + (r/exp->sincos `(~'exp ~(c/complex 0 1))))) + (is (= `(~'* (~'exp 1.0) (~'+ (~'cos 1.0) (~'* ~c/I (~'sin 1.0)))) + (r/exp->sincos `(~'exp ~(c/complex 1 1))))) + (is (= `(~'expt (~'exp (~'* ~c/I ~'z)) 2.0) + (r/exp-expand `(~'exp (~'* ~(c/complex 0 2) ~'z))))) + (is (= '(expt (exp (* k t)) 2) + (r/exp-expand '(exp (* 2 k t)))))) + (testing "complex-trig" (is (= '(cosh 1) (r/complex-trig (list 'cos c/I))) diff --git a/test/emmy/simplify_test.cljc b/test/emmy/simplify_test.cljc index ed17e897..dbbcbc2f 100644 --- a/test/emmy/simplify_test.cljc +++ b/test/emmy/simplify_test.cljc @@ -5,6 +5,7 @@ (:require #?(:cljs [goog.string :refer [format]]) [clojure.test :refer [is deftest testing use-fixtures]] [emmy.complex :as c] + [emmy.expression :as e] [emmy.expression.analyze :as a] [emmy.generic :as g] [emmy.matrix :as m] @@ -62,7 +63,7 @@ (is (= '(* x y z) (simplify-expression '(* 1 x y z)))) (is (= '(+ x y) (simplify-expression '(/ (* 2 (+ x y)) 2)))) (is (= '(+ (* (/ 1 2) x) (* (/ 1 2) y)) - (v/freeze + (g/freeze (simplify-expression '(/ (+ x y) 2)))))) (deftest structures @@ -151,6 +152,17 @@ (is (= '(log x) (g/simplify (g/log 'x)))) (is (= '(exp x) (g/simplify (g/exp 'x))))) + (testing "symbols vs. constants" + (let [->exp #(e/make-literal ::e/numeric %)] + (is (= '(floor x) (g/simplify (g/floor 'x)))) + (is (= 3 (g/simplify (g/floor (->exp 3.14))))) + (is (= '(ceiling x) (g/simplify (g/ceiling 'x)))) + (is (= 4 (g/simplify (g/ceiling (->exp 3.14))))) + (is (= '(integer-part x) (g/simplify (g/integer-part 'x)))) + (is (= 3 (g/simplify (g/integer-part (->exp 3.14))))) + (is (= '(fractional-part x) (g/simplify (g/fractional-part 'x)))) + (is (= (- 3.14 3) (g/simplify (g/fractional-part (->exp 3.14))))))) + (testing "zero/one elimination" (is (= 'x (g/+ 0 'x))) (is (= 'x (g/* 1 'x))) @@ -164,16 +176,16 @@ (is (= 'x (g/* 'x 1.0))) (is (= 'x (g/divide 'x 1.0))) (is (= 'x (g/divide 'x 1))) - (is (v/zero? (g/divide 0 'x))) + (is (g/zero? (g/divide 0 'x))) (is (= 0 (g/* 0 'x))) (is (= 0 (g/* 'x 0))) (is (thrown? #?(:clj ArithmeticException :cljs js/Error) (g/divide 'x 0)))) (testing "symbolic moves" - (is (v/one? (g/expt 'x 0))) + (is (g/one? (g/expt 'x 0))) (is (= 'x (g/gcd 'x 'x))) - (is (v/one? (g/expt 1 'x))) + (is (g/one? (g/expt 1 'x))) (is (= (g/negate 'x) (g/- 0 'x))))) (deftest matrix-tests @@ -183,7 +195,7 @@ (is (= '(matrix-by-rows [(+ (* a e) (* b g)) (+ (* a f) (* b h))] [(+ (* c e) (* d g)) (+ (* c f) (* d h))]) - (v/freeze + (g/freeze (g/simplify (g/* M S))))))) (testing "div" @@ -192,7 +204,7 @@ (is (= '(up (/ (+ (* -1 b y) (* d x)) (+ (* a d) (* -1 b c))) (/ (+ (* a y) (* -1 c x)) (+ (* a d) (* -1 b c)))) - (v/freeze + (g/freeze (g/simplify (g/divide u M))))))) diff --git a/test/emmy/special/elliptic_test.cljc b/test/emmy/special/elliptic_test.cljc index 2363ac80..8301a6c9 100644 --- a/test/emmy/special/elliptic_test.cljc +++ b/test/emmy/special/elliptic_test.cljc @@ -9,6 +9,7 @@ [emmy.numerical.derivative :refer [D-numeric]] [emmy.numerical.quadrature :as q] [emmy.special.elliptic :as e] + [emmy.util :as u] [emmy.value :as v] [same.core :refer [ish? with-comparator]])) @@ -17,7 +18,7 @@ (def gen-k (gen/double* {:infinite? false :NaN? false :min 0 - :max (- 1 v/machine-epsilon)})) + :max (- 1 u/machine-epsilon)})) (def gen-phi-k (gen/tuple (sg/reasonable-double) diff --git a/test/emmy/special/factorial_test.cljc b/test/emmy/special/factorial_test.cljc index 4587d0c0..d2c71317 100644 --- a/test/emmy/special/factorial_test.cljc +++ b/test/emmy/special/factorial_test.cljc @@ -28,13 +28,13 @@ (testing "falling factorial works on unital rings, functions are game!" (is (= '(/ 1 (+ (expt x 3) (* 6 (expt x 2)) (* 11 x) 6)) - (v/freeze + (g/freeze (g/simplify ((sf/falling-factorial g/+ -3) 'x)))) "negative second arg") (is (= '(+ (expt x 3) (* -3 (expt x 2)) (* 2 x)) - (v/freeze + (g/freeze (g/simplify ((sf/falling-factorial g/+ 3) 'x)))) "positive second arg")) @@ -90,13 +90,13 @@ (testing "rising factorial works on unital rings, functions are game!" (is (= '(/ 1 (+ (expt x 3) (* -6 (expt x 2)) (* 11 x) -6)) - (v/freeze + (g/freeze (g/simplify ((sf/rising-factorial g/+ -3) 'x)))) "negative second arg") (is (= '(+ (expt x 3) (* 3 (expt x 2)) (* 2 x)) - (v/freeze + (g/freeze (g/simplify ((sf/rising-factorial g/+ 3) 'x)))) "positive second arg")) diff --git a/test/emmy/sr/boost_test.cljc b/test/emmy/sr/boost_test.cljc index dc8b0dae..1e1c963e 100644 --- a/test/emmy/sr/boost_test.cljc +++ b/test/emmy/sr/boost_test.cljc @@ -12,13 +12,12 @@ [emmy.polynomial.gcd :as pg] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.sr.boost :as sb] - [emmy.structure :as s :refer [up]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest boost-tests (is (= 0 (simplify diff --git a/test/emmy/sr/frames_test.cljc b/test/emmy/sr/frames_test.cljc index ab683b52..269b784d 100644 --- a/test/emmy/sr/frames_test.cljc +++ b/test/emmy/sr/frames_test.cljc @@ -8,13 +8,12 @@ [emmy.generic :as g :refer [+ - * /]] [emmy.simplify :refer [hermetic-simplify-fixture]] [emmy.sr.frames :as sf] - [emmy.structure :as s :refer [up]] - [emmy.value :as v])) + [emmy.structure :as s :refer [up]])) (use-fixtures :each hermetic-simplify-fixture) (def simplify - (comp v/freeze g/simplify)) + (comp g/freeze g/simplify)) (deftest sr-frames-tests (testing "Velocity addition formula" diff --git a/test/emmy/structure_test.cljc b/test/emmy/structure_test.cljc index a7abff43..0de1a4d6 100644 --- a/test/emmy/structure_test.cljc +++ b/test/emmy/structure_test.cljc @@ -51,41 +51,44 @@ (testing "value protocol" (testing "zero?" - (is (v/zero? (s/up))) - (is (v/zero? (s/down))) - (is (v/zero? (s/down 0))) - (is (v/zero? (s/up 0 0))) - (is (v/zero? (s/up 0))) - (is (v/zero? (s/down 0 0))) - (is (v/zero? (s/up 0 (s/down (s/up 0 0) (s/up 0 0))))) - (is (v/zero? (s/up 0 (u/long 0) (u/int 0))))) + (is (g/zero? (s/up))) + (is (g/zero? (s/down))) + (is (g/zero? (s/down 0))) + (is (g/zero? (s/up 0 0))) + (is (g/zero? (s/up 0))) + (is (g/zero? (s/down 0 0))) + (is (g/zero? (s/up 0 (s/down (s/up 0 0) (s/up 0 0))))) + (is (g/zero? (s/up 0 (u/long 0) (u/int 0))))) (testing "zero-like" - (is (v/zero? (v/zero-like (s/up 1 2 3)))) - (is (= (s/up 0 0 0) (v/zero-like (s/up 1 2 3)))) - (is (= (s/up) (v/zero-like (s/up)))) - (is (= (s/down 0 0 0) (v/zero-like (s/down 1 2 3)))) - (is (= (s/down) (v/zero-like (s/down)))) + (is (g/zero? (g/zero-like (s/up 1 2 3)))) + (is (= (s/up 0 0 0) (g/zero-like (s/up 1 2 3)))) + (is (= (s/up) (g/zero-like (s/up)))) + (is (= (s/down 0 0 0) (g/zero-like (s/down 1 2 3)))) + (is (= (s/down) (g/zero-like (s/down)))) (is (= (s/up 0 (s/down (s/up 0 0) (s/up 0 0))) - (v/zero-like (s/up 1 (s/down (s/up 2 3) (s/up 4 5)))))) + (g/zero-like (s/up 1 (s/down (s/up 2 3) (s/up 4 5)))))) (is (= (s/up (u/long 0) (u/int 0) 0) - (v/zero-like (s/up (u/long 1) (u/int 2) 3))))) + (g/zero-like (s/up (u/long 1) (u/int 2) 3))))) (testing "one-like" - (let [one (v/one-like (s/up 1 2 3))] - (is (= 1 one)) - (is (v/one? one)))) + (doseq [constructor [s/up s/down]] + (let [one (g/one-like (constructor 1 2 3))] + (is (= 1 one)) + (is (g/one? one))))) (testing "identity-like" - (let [id (v/identity-like (s/up 1 2 3))] - (is (= 1 id)) - (is (v/identity? id)))) + (doseq [constructor [s/up s/down]] + (let [id (g/identity-like (constructor 1 2 3))] + (is (= 1 id)) + (is (g/identity? id))))) (testing "exact?" - (is (v/exact? (s/up 1 2 3 4))) - (is (not (v/exact? (s/up 1.2 3 4)))) - (is (v/exact? (s/up 0 1 #emmy/ratio 3/2))) - (is (not (v/exact? (s/up 0 0 0.00001))))) + (doseq [constructor [s/up s/down]] + (is (g/exact? (constructor 1 2 3 4))) + (is (not (g/exact? (constructor 1.2 3 4)))) + (is (g/exact? (constructor 0 1 #emmy/ratio 3/2))) + (is (not (g/exact? (constructor 0 0 0.00001)))))) (testing "numerical?" (checking "no structure is numerical." 100 @@ -93,7 +96,8 @@ (is (not (v/numerical? s))))) (testing "freeze" - (is (= '(up 1 2 3) (v/freeze (s/up 1 2 3))))) + (is (= '(up 1 2 3) (g/freeze (s/up 1 2 3)))) + (is (= '(down 1 2 3) (g/freeze (s/down 1 2 3))))) (testing "kind" (is (= ::s/up (v/kind (s/up 1 2)))) @@ -251,6 +255,7 @@ (testing "a structure has a nth element (ILookup)" (is (= 14 (nth (s/up 10 12 14) 2))) (is (= 5 (nth (s/up 4 5 6) 1))) + (is (= 9 (nth (s/up 4 5 6) 3 9))) (is (thrown? #?(:clj IndexOutOfBoundsException :cljs js/Error) (nth (s/up 4 5 6) 4)) "out of bounds")) @@ -258,6 +263,7 @@ (testing "get, get-in work natively" (is (= 5 (get (s/up 4 5 6) 1))) (is (= 4 (get (s/up 4 5 6) 0))) + (is (= 9 (get (s/up 4 5 6) 3 9))) (is (= 4 (get-in (s/down (s/up 1 2) (s/up 3 4)) [1 1]))) (is (= 2 (get-in (s/down (s/up 1 2) (s/up 3 4)) [0 1])))) @@ -265,7 +271,9 @@ (is (= (s/up 4 55 6) (assoc (s/up 4 5 6) 1 55))) (is (= (s/down (s/up 1 22) (s/up 3 4)) - (assoc-in (s/down (s/up 1 2) (s/up 3 4)) [0 1] 22)))) + (assoc-in (s/down (s/up 1 2) (s/up 3 4)) [0 1] 22))) + (is (contains? (s/up 4 5 6) 1)) + (is (not (contains? (s/up 4 5 6) 3)))) (testing "IFn" (is (= (s/up 6 9 1) @@ -364,6 +372,10 @@ (is (= 1 (s/s:count n))) (is (= n (s/s:nth n 0)))) + (testing "s:nth throws for non-sequential objects at i != 0" + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (s/s:nth 99. 1)))) + (checking "s:count, s:nth for structures" 100 [s (sg/structure sg/real 5) n (gen/choose 0 4)] @@ -458,6 +470,21 @@ (is (= (s/up 1 4 9 16 25) (s/generate 5 ::s/up (comp #(* % %) inc))))) + (testing "invocation" + (let [sum (fn [& xs] (reduce + 0 xs)) + product (fn [& xs] (reduce * 1 xs)) + S (s/up sum product)] + (is (= (s/up 0 1) (S))) + (is (= (s/up 1 1) (S 1))) + (is (= (s/up 3 2) (S 1 2))) + (is (= (s/up 6 6) (S 1 2 3))) + (is (= (s/up 10 24) (S 1 2 3 4))) + (is (= (s/up 15 120) (S 1 2 3 4 5))) + (is (= (s/up 21 720) (S 1 2 3 4 5 6))) + (is (= (s/up 28 5040) (S 1 2 3 4 5 6 7))) + (is (= (s/up 36 40320) (S 1 2 3 4 5 6 7 8))) + (is (= (s/up 45 362880) (S 1 2 3 4 5 6 7 8 9))) + (is (= (s/up 55 3628800) (S 1 2 3 4 5 6 7 8 9 10))))) (testing "literal-up,down" (is (thrown? #?(:clj AssertionError :cljs js/Error) (s/literal 'x 3 ::random)) @@ -477,7 +504,7 @@ (is (= '(+ (* x↑0 x_0) (* x↑1 x_1) (* x↑2 x_2)) - (v/freeze + (g/freeze (g/* (s/literal-up 'x 3) (s/literal-down 'x 3)))) "It can be convenient to generate symbolic structures if you don't care @@ -631,7 +658,7 @@ (s/unflatten (range) s))) "flattening generates the replaced sequence") - (is (v/zero? (g/* s (s/transpose + (is (g/zero? (g/* s (s/transpose (s/unflatten (repeat 0) s)))) "flipping indices after replacing with all zeros creates a structure that annihilates the original on multiplying.")) @@ -671,13 +698,13 @@ (checking "s/compatible-zero works" 100 [s (sg/structure sg/real)] - (is (v/zero? (g/* s (s/compatible-zero s)))) - (is (v/zero? (g/* (s/compatible-zero s) s))) + (is (g/zero? (g/* s (s/compatible-zero s)))) + (is (g/zero? (g/* (s/compatible-zero s) s))) - (is (v/zero? (g/* s (s/dual-zero s))) + (is (g/zero? (g/* s (s/dual-zero s))) "dual-zero is an alias for compatible-zero.") - (is (v/zero? (g/* (s/dual-zero s) s)) + (is (g/zero? (g/* (s/dual-zero s) s)) "dual-zero is an alias for compatible-zero.")) (testing "compatible-shape" @@ -716,7 +743,7 @@ [[l inner r] (gen/let [rows (gen/choose 1 5) cols (gen/choose 1 5)] ( rows cols))] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (g/transpose (g/* l (g/* inner r))) @@ -728,7 +755,7 @@ [[l inner r] (gen/let [n (gen/choose 1 5)] ( n 0))] - (is (v/zero? + (is (g/zero? (g/- (g/transpose (g/* l (g/* inner r))) (g/* (g/transpose r) @@ -746,7 +773,7 @@ [[l inner r] (gen/let [rows (gen/choose 0 5) cols (gen/choose 1 5)] ( rows cols))] - (is (v/zero? + (is (g/zero? (g/simplify (g/- (g/* l (g/* inner r)) (g/* (g/* (s/transpose-outer inner) l) r)))))) @@ -754,7 +781,7 @@ (checking "cols=0 transpose-outer law produces incompatible sides" 100 [[l inner r] (gen/let [rows (gen/choose 1 5)] ( rows 0))] - (is (v/zero? + (is (g/zero? (g/* l (g/* inner r))) "the left side is a structure of zeros") @@ -823,6 +850,16 @@ (s/up (s/down 1 2)) (s/up (s/down 1 2 3 4)))))) + (testing "*allow-incompatible-multiplication*" + (let [a (s/up 1 2) + b (s/down 2 3 4)] + (is (not (s/compatible-for-contraction? a b))) + (is (= (s/down (s/up 2 4) (s/up 3 6) (s/up 4 8)) + (g/* a b))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) + (binding [s/*allow-incompatible-multiplication* false] + (g/* a b)))))) + (checking "dot-product equals inner for reals, complex on the right" 100 [v1 (-> (sg/up1 sg/real 5) (sg/down1 5)) @@ -853,8 +890,8 @@ (sg/structure1 2))] (let [I (s/up (s/up 1 0) (s/up 0 1))] - (is (= (s/up (s/up s (v/zero-like s)) - (s/up (v/zero-like s) s)) + (is (= (s/up (s/up s (g/zero-like s)) + (s/up (g/zero-like s) s)) (g/outer-product s I))))) (checking "inner, dot, outer with fns" 100 @@ -991,20 +1028,20 @@ (testing " * pushes operator multiplication into the structure (unlike * !)" - (is (= (v/freeze + (is (= (g/freeze (s/up (s/down (* 1 o/identity) (* 2 o/identity)) (s/down (* 4 o/identity) (* 5 o/identity)))) - (v/freeze + (g/freeze (* (s/up (s/down 1 2) (s/down 4 5)) o/identity)))) - (is (= (v/freeze + (is (= (g/freeze (s/up (s/down (* o/identity 1) (* o/identity 2)))) - (v/freeze + (g/freeze (* o/identity (s/up (s/down 1 2))))) "operator*structure is not commutative.")) diff --git a/test/emmy/value_test.cljc b/test/emmy/value_test.cljc index 58f3e4dc..6abaef9b 100644 --- a/test/emmy/value_test.cljc +++ b/test/emmy/value_test.cljc @@ -6,6 +6,7 @@ [clojure.test.check.generators :as gen] [com.gfredericks.test.chuck.clojure-test :refer [checking]] [emmy.generators :as sg] + [emmy.generic :as g] [emmy.util :as u] [emmy.value :as v]) #?(:clj @@ -27,29 +28,29 @@ (deftest vector-value-impl (testing "zero?" - (is (v/zero? [])) - (is (v/zero? [0 0])) - (is (not (v/zero? [1 2 3])))) + (is (g/zero? [])) + (is (g/zero? [0 0])) + (is (not (g/zero? [1 2 3])))) (testing "zero-like" - (is (= [0 0 0] (v/zero-like [1 2 3]))) - (is (= [] (v/zero-like []))) - (is (= [0 [0 0] [0 0]] (v/zero-like [1 [2 3] [4 5]]))) + (is (= [0 0 0] (g/zero-like [1 2 3]))) + (is (= [] (g/zero-like []))) + (is (= [0 [0 0] [0 0]] (g/zero-like [1 [2 3] [4 5]]))) (is (= [(u/long 0) (u/int 0) 0] - (v/zero-like [(u/long 1) (u/int 2) 3])))) + (g/zero-like [(u/long 1) (u/int 2) 3])))) - (is (= 1 (v/one-like [1 2 3])) + (is (= 1 (g/one-like [1 2 3])) "1 is the multiplicative identity for vector spaces.") (testing "exact?" - (is (v/exact? [1 2 3 4])) - (is (not (v/exact? [1.2 3 4]))) - (is (v/exact? [0 1 #emmy/ratio 3/2])) - (is (not (v/exact? [0 0 0.00001])))) + (is (g/exact? [1 2 3 4])) + (is (not (g/exact? [1.2 3 4]))) + (is (g/exact? [0 1 #emmy/ratio 3/2])) + (is (not (g/exact? [0 0 0.00001])))) (testing "freeze" (is (= '(up 1 2 3) - (v/freeze [1 2 3])))) + (g/freeze [1 2 3])))) (testing "kind" (is (= PersistentVector (v/kind [1 2]))))) @@ -57,13 +58,13 @@ (deftest numeric-value-protocol-tests (checking "*-like properly coerce" 100 [n sg/number] - (is (v/zero? (v/zero-like n))) - (is (not (v/zero? (v/one-like n)))) + (is (g/zero? (g/zero-like n))) + (is (not (g/zero? (g/one-like n)))) - (is (v/one? (v/one-like n))) - (is (not (v/one? (v/zero-like n)))) + (is (g/one? (g/one-like n))) + (is (not (g/one? (g/zero-like n)))) - (is (v/identity? (v/identity-like n)))) + (is (g/identity? (g/identity-like n)))) (let [n 50] (checking "all numbers act as hashmap keys" 100 @@ -79,16 +80,16 @@ "Any numeric key works in a hash-map and round-trips.")))) (testing "zero-like sticks with precision" - (is (= 0 (v/zero-like 2))) - (is (= 0.0 (v/zero-like 3.14)))) + (is (= 0 (g/zero-like 2))) + (is (= 0.0 (g/zero-like 3.14)))) (testing "one-like sticks with precision" - (is (= 1 (v/one-like 1))) - (is (= 1.0 (v/one-like 1.2)))) + (is (= 1 (g/one-like 1))) + (is (= 1.0 (g/one-like 1.2)))) - (checking "on non-rational reals, v/freeze is identity" 100 + (checking "on non-rational reals, g/freeze is identity" 100 [n (gen/one-of [sg/any-integral (sg/reasonable-double)])] - (is (= n (v/freeze n)))) + (is (= n (g/freeze n)))) (checking "all numbers are numerical" 100 [n sg/number] @@ -98,8 +99,8 @@ "Symbols are abstract numerical things.") (is (isa? (v/kind 10) ::v/real)) - (is (v/exact? 10)) - (is (not (v/exact? 10.1)))) + (is (g/exact? 10)) + (is (not (g/exact? 10.1)))) (deftest numeric-comparison-tests (checking "v/compare matches <, >, = behavior for reals" 100 @@ -133,16 +134,16 @@ (compare l r)))))) (deftest zero-tests - (is (v/zero? 0)) - (is (v/zero? 0.0)) - (is (not (v/zero? 1))) - (is (not (v/zero? 0.1)))) + (is (g/zero? 0)) + (is (g/zero? 0.0)) + (is (not (g/zero? 1))) + (is (not (g/zero? 0.1)))) (deftest one-tests - (is (v/one? 1)) - (is (v/one? 1.0)) - (is (not (v/one? 0))) - (is (not (v/one? 0.0)))) + (is (g/one? 1)) + (is (g/one? 1.0)) + (is (not (g/one? 0))) + (is (not (g/one? 0.0)))) (deftest kinds (is (= #?(:clj Long :cljs ::v/native-integral) (v/kind 1))) @@ -150,13 +151,13 @@ (is (= PersistentVector (v/kind [1 2])))) (deftest exactness - (is (v/exact? 1)) - (is (v/exact? 4N)) - (is (not (v/exact? 1.1))) - (is (not (v/exact? :a))) - (is (not (v/exact? "a"))) - (is (v/exact? #emmy/ratio 3/2)) - (is (v/exact? (u/biginteger 111)))) + (is (g/exact? 1)) + (is (g/exact? 4N)) + (is (not (g/exact? 1.1))) + (is (not (g/exact? :a))) + (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (g/exact? "a"))) + (is (g/exact? #emmy/ratio 3/2)) + (is (g/exact? (u/biginteger 111)))) (deftest argument-kinds (let [L #?(:clj Long :cljs ::v/native-integral)