Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retire value protocol in favor of multimethods #149

Merged
merged 27 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03fe1b1
Move `zero?` from protocol to multifn
littleredcomputer Sep 30, 2023
4e676e0
retire Value protocol members one?, identity?
littleredcomputer Oct 1, 2023
0e0fb0a
zero-like, one-like, identity-like moved from Value protocol to multifns
littleredcomputer Oct 2, 2023
5fa2a53
Level up Clojurescript
littleredcomputer Oct 9, 2023
9ff9a82
exact? and freeze (clojure only).
littleredcomputer Oct 10, 2023
afbbb05
cljs level up (almost: there are two test case failures having to do …
littleredcomputer Oct 10, 2023
efa73e0
cljs bug fixes
littleredcomputer Oct 10, 2023
186cdaf
rename Value to IKind
littleredcomputer Oct 10, 2023
9d8049d
doc tweaks
littleredcomputer Oct 10, 2023
f37e03e
add some more test coverage
littleredcomputer Oct 11, 2023
bf86cf5
tweaks
littleredcomputer Oct 11, 2023
c5d86cf
tweaks
littleredcomputer Oct 11, 2023
9840eae
fix
littleredcomputer Oct 11, 2023
3099424
try to get bigfraction to work in CI (it works on my desktop, but...)
littleredcomputer Oct 11, 2023
d1cf4b3
more coverage
littleredcomputer Oct 11, 2023
b48789f
use a patch-package to get bigfraction.js to import cleanly for cljs
littleredcomputer Oct 12, 2023
2d75ffa
coverage.
littleredcomputer Oct 17, 2023
496a3d1
code coverage
littleredcomputer Oct 18, 2023
8d13acc
doc, coverage
littleredcomputer Oct 18, 2023
ec33728
cov
littleredcomputer Oct 18, 2023
e35d090
coverage
littleredcomputer Oct 24, 2023
316d36c
coverage
littleredcomputer Oct 24, 2023
49e88e6
coverage
littleredcomputer Oct 26, 2023
5faf68c
coverage
littleredcomputer Oct 26, 2023
b171645
remove literal ratios for cljs
littleredcomputer Oct 26, 2023
c104511
pin fraction.js to 4.2.1; remove patch stuff. Will this pass CI?
littleredcomputer Oct 27, 2023
49a92a3
add PR number to changelog entry
littleredcomputer Oct 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cljfmt-vscode.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{:sort-ns-references? true
:extra-indents {sci-macro [[:block 1]]}
}
56 changes: 51 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
littleredcomputer marked this conversation as resolved.
Show resolved Hide resolved
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious to see how this is done, as I introduced this bug, not knowing you could do it another way... do you compare now to the zero-like of the type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! I was inspired by similar examples you had designed.

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,
Expand Down Expand Up @@ -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`:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/deps.cljs
Original file line number Diff line number Diff line change
@@ -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"}}
31 changes: 15 additions & 16 deletions src/emmy/abstract/function.cljc
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
;;
Expand All @@ -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))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the range always have an instance of the output type? I thought we could have things like '(UP Real Real).

I have a PR bringing in the Sussman-style here: #113

I wonder if this implementation will work there, or what you think...

(defmethod g/one-like [::function] [^Function a] (fn [& _] (g/one-like (.-range a))))
(defmethod g/identity-like [::function] [^Function a]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it worth making this identity a top-level var and then returning that here/

(let [meta {:arity (.-arity a) :from :identity-like}]
(with-meta identity meta)))
(defmethod g/exact? [::function] [a] (f/compose g/exact? a))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, won't this always return symbols, which will register as false? I guess that makes sense!

(defmethod g/freeze [::function] [^Function a] (g/freeze (.-f-name a)))
15 changes: 6 additions & 9 deletions src/emmy/abstract/number.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
22 changes: 11 additions & 11 deletions src/emmy/calculus/covariant.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
21 changes: 10 additions & 11 deletions src/emmy/calculus/form_field.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
;;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading