Liking cljdoc? Tell your friends :D

Emmy-Viewers

Emmy-Viewers is a library of tools for visually rendering mathematical objects found in the Emmy computer algebra system. These tools allow you to build symbolic representations and 2D and 3D mathematical scenes for exploring physical and mathematical worlds.

Build Status License cljdoc badge Clojars Project Discord Shield

Examples

The first example uses the functions in the emmy.mafs namespace to plot

  • ((cube D) tanh), the third derivative of the hyperbolic tangent function, shifted by the x-value of the (interactive, try sliding!) pink point in the scene, and
  • the inequality between that function and (cos x):
(emmy.viewer/with-let [!phase [0 0]]
  (let [shifted (emmy.viewer/with-params {:atom !phase :params [0]}
                  (fn [shift]
                    (((cube D) tanh) (- identity shift))))]
    (emmy.mafs/mafs
     {:height 400}
     (emmy.mafs/cartesian)
     (emmy.mafs/of-x shifted)
     (emmy.mafs/inequality
      {:y {:<= shifted :> cos} :color :blue})
     (emmy.mafs/movable-point
      {:atom !phase :constrain "horizontal"}))))

2023-06-26 16 53 03

Note To play with interactive versions of these examples, visit https://emmy-viewers.mentat.org.

The next example uses the functions in emmy.mathbox.plot and emmy.leva to plot the functions

  • $f(x, y) := D^3\tanh(x) + \sin(y-\texttt{phase})$
  • $g(y) := \texttt{shift} * \cos(y - \texttt{phase})$

where $\texttt{phase}$ is supplied by the interactive slider hovering in the top right:

(emmy.viewer/with-let [!phase {:phase 0}]
  (emmy.mathbox.plot/scene
   (emmy.leva/controls
    {:folder {:name "Intro Demo"}
     :schema {:phase {:min -4 :max 4 :step 0.01}}
     :atom !phase})
   (emmy.mathbox.plot/of-y
    {:z (ev/with-params {:atom !phase :params [:phase]}
          (fn [shift]
            (fn [y]
              (* shift (sin (- y shift))))))

     :color "LimeGreen"})
   (emmy.mathbox.plot/of-xy
    {:color "#3090FF"
     :z (ev/with-params {:atom !phase :params [:phase]}
          (fn [shift]
            (fn [[x y]]
              (+ (((cube D) tanh) x)
                 (sin (- y shift))))))})))

2023-06-26 16 54 26

Emmy-Viewers uses either Clerk or Portal as a presentation environment.

Quickstart

Install Emmy-Viewers into your Clojure project using the instructions at its Clojars page:

Clojars Project

Or grab the most recent code using a Git dependency:

;; replace $GIT_SHA with the sha you'd like to target!

{org.mentat/emmy-viewers
  {:git/url "https://github.com/mentat-collective/emmy-viewers.git"
   :git/sha "$GIT_SHA"}}

Next visit "Quickstart via Clerk" to build scenes using Clerk, or "Quickstart via Portal" to use Portal.

Quickstart via Clerk

The fastest way to get started with Clerk is to use the emmy-viewers/clerk project template. Jump there, or read on to set Clerk up manually.

Install Clerk by adding the following entry to your deps.edn:

{:deps
 {<your other dependencies>
  io.github.nextjournal/clerk {:mvn/version "0.14.919"}}}

Require and start emmy.clerk in your user.clj:

(ns user
  (:require [emmy.clerk :as ec]))

;; start Clerk's built-in webserver on the default port 7777, opening the
;; browser when done
(ec/serve! {:browse true})

Note See Clerk's "Using Clerk" section for more configuration options. Replace any call in that guide to nextjournal.clerk/{serve!,halt!,build!} with emmy.clerk/{serve!,halt!,build!}.

Create a file called emmy/demo.clj and install our default Clerk viewers from emmy.clerk:

(ns emmy.demo
  (:refer-clojure
   :exclude [+ - * / zero? compare divide numerator denominator
             infinite? abs ref partial =])
  (:require [emmy.clerk :as ec]
            [emmy.env :as e :refer :all]
            [emmy.mafs :as mafs]
            [emmy.mathbox.plot :as plot]
            [emmy.viewer :as ev]
            [nextjournal.clerk :as clerk]))

^{::clerk/visibility {:code :hide :result :hide}}
(ec/install!)

Note This namespace imports every function from emmy.env. You'll need to build your mathematical functions out of these emmy.env functions to use them with Emmy-Viewers 2D and 3D plotting capabilities.

Thanks to the ec/install! call above, the return values of all Emmy-Viewers functions will be able to present themselves graphically.

Add this line to plot the sine function using the Mafs.cljs 2D plotting library:

(mafs/of-x sin {:color :blue})

Render it by calling (nextjournal.clerk/show! "src/emmy/demo.clj") at the REPL. You'll see the following:

Mafs plot example

See the Mafs Guide for 2D plotting examples.

Use plot/scene to render multiple plots onto a 3D canvas:

(plot/scene
 (plot/of-x {:z sin})
 (plot/parametric-curve
  {:f (up sin cos (/ identity 3))
   :t [-10 10]
   :color :green}))

MathBox plot example

See the MathBox Guide for 3D plotting examples.

Next, try clicking the 'show code' link for the examples at the top of the page and pasting their source into your namespace. Then visit the "Specific Guides" section, and click through "Demos" for inspiration.

Quickstart via Portal

Follow the Portal Usage instructions to the point where you have the Portal dependency installed and loadable.

You'll also need to install node.js to download JavaScript dependencies.

Test your installation by running this command at the REPL:

(require '[portal.api :as p])

If this returns nil you're all set.

Require emmy.portal in your user.clj, and call prepare! to make sure you've downloaded all JavaScript dependencies from npm:

(ns user
  (:require [emmy.portal :as ep]))

;; This really only needs to be run once, but run it at each system start to
;; be safe.
(ep/prepare!)

Start the Emmy-Viewers customization of Portal:

(def portal
  (ep/start!
   {:emmy.portal/tex {:macros {"\\f" "#1f(#2)"}}
    :theme :portal.colors/zenburn}))

The options passed above are optional; :emmy.portal/tex customizes the katex viewer with any option allowed by katex's option support, while :theme sets a custom theme. You can pass any Portal theme that you like, or remove this entry.

Use tap> to send values to Portal:

(tap> (mafs/of-x sin {:color :indigo}))
Portal Mafs

tap> an Emmy symbolic expression:

(tap> (((exp D) (literal-function 'f)) 'x))

then click the cell in the Portal UI and choose :emmy.portal/tex from the viewer list at the bottom. You should see the expression transform into nicely typeset mathematics.

Emmy Expression in Portal

Note Currently anything from emmy.mafs, emmy.leva, emmy.jsxgraph and emmy.mathlive will render. MathBox support is coming soon!

Visit the "Specific Guides" section, and click through "Demos" for inspiration.

Specific Guides

JSXGraph.cljs and MathLive.cljs guides are coming soon.

Computational Backends

Every function a viewer plots is compiled on the JVM before it reaches the browser. Every compiled Kernel, whichever backend produced it, is callable using Emmy's original calling convention (:native, :structure, or :primitive) and carries the same browser-side contract:

  • f.batch(xs, n, ps, out) evaluates n points from flattened row-major input states xs, using parameters ps, into out (or an allocated output buffer).
  • f.ready() reports whether the kernel is ready to execute.
  • f.dims gives the scalar counts {state, outputs, params}.

Viewers depend on the ClojureScript namespace emmy.viewer.kernel, not on a particular backend: kernel?, ready?, dims, batch!, bind, and bind-1d expose and preserve that contract. MathBox explicit surfaces sample their full grid through batch! when the function is a Kernel and both ranges are given; other functions retain per-point sampling.

On the JVM, backends implement emmy.viewer.kernel.backend/KernelBackend with one method, kernel-form, and are added using register-backend!. The dynamic var emmy.viewer.compile/*backend* accepts either a registered keyword or a backend value:

  • :js (the default) emits Emmy JavaScript with a batch adapter attached to the same per-point function.
  • :raster compiles to WebAssembly with raster. Emmy simplifies the function symbolically and raster compiles the output to a kernel that evaluates many points in one call. The module ships inside the fragment with Emmy's original calling convention and a JavaScript fallback.
(require '[emmy.viewer.compile :as vc])

(binding [vc/*backend* :raster]
  (emmy.mafs/of-x (fn [x] (* x (sin x)))))

Upgrading: Existing viewer call sites stay the same. Choosing :raster only changes the backend binding; introducing another backend means registering an implementation of KernelBackend, not editing viewers or compile call sites.

Measured with clojure -J-Xmx2g -M:raster:bench (node 22, JDK 25, ns per point; see bench/results/latest.edn). Each batch pass evaluates all points; surface-64 is a complete 64×64 grid on [-3, 3]² with two parameters. These numbers come from a shared, busy machine, so single per-point timings vary by up to 2x between runs; the batch columns are the stable result:

function:js per point:js batch:raster per point:raster batch
x sin x67.016.562.16.5
degree-7 polynomial280.5291.543.74.0
parametric curve, 2 outputs78.549.4131.610.5
a sin(bx) + x², 2 params51.128.926.36.1
MathBox surface, 3 outputs64.755.236.832.4
surface-64: a sin(bx) cos(y)99.346.551.912.7
double-pendulum state derivative89.880.363.435.0

What holds across runs: :raster batch is the fastest path for every case (4 to 35 ns per point, 2x to 70x faster than :js per point), and a MathBox surface-64 grid evaluates in one call at about 13 ns per point. :js batch is faster than, or within noise of, :js per point, so a viewer can batch through the contract whichever backend compiled its function.

The raster backend needs raster on the classpath (the :raster alias). Things to know:

  • Compiling is slower: 0.1 to 0.5 s per function on the JVM, against a few ms for :js, and the fragment is 2 to 4 KB larger.

  • raster's wasm math approximates the transcendental functions: sin/cos within 5e-6 and atan/asin/acos within 3e-5 of java.lang.Math. Plots cannot show the difference; long ODE integrations can drift from the :js result.

  • A function raster cannot compile (a literal function, an operator outside its vocabulary) throws at build time and names the form. Bind :js for that viewer.

  • The backend is layered: emmy.viewer.kernel.plan is backend-neutral; emmy.viewer.raster.lower (raster's vocabulary) and emmy.viewer.raster.glue (the browser side) are pure. Shared malli contracts live in emmy.viewer.kernel.schema, raster-specific ones in emmy.viewer.raster.schema; emmy.viewer.raster is the compilation boundary.

  • Each fragment also carries the :js function. A browser without WebAssembly uses it, and so does any call made while a module larger than 4 KB is still compiling asynchronously (f.batch returns null then).

  • Parameters are written to the module only when the caller passes a new parameter object, which is what the viewers do when a slider moves. Mutating the same array in place between calls is not seen.

Demos

These demos are in-progress, more advanced demonstrations of the capabilities of Emmy-Viewers. I'm using these as test cases to flesh out the more settled API, so please read them with that in mind!

Interactive Demos via Clerk

The project's interactive documentation was generated using Nextjournal's Clerk. If you'd like to edit or play with the documentation or demos, you'll need to install

Next, clone the repository:

git clone git@github.com:mentat-collective/emmy-viewers.git
cd emmy-viewers

Run this command in the cloned repository:

bb clerk-watch

This will open a browser window to http://localhost:7777 with the contents of the main documentation notebook. Any edits you make to any file in the src or dev directories will be picked up and displayed in the browser on save.

Dependencies

These viewers are built on the following libraries:

Thanks and Support

To support this work and my other open source projects, consider sponsoring me via my GitHub Sponsors page. Thank you to my current sponsors!

I'm grateful to Clojurists Together for financial support during this library's creation. Please consider becoming a member to support this work and projects like it.

License

Copyright © 2022-2023 Sam Ritchie.

Distributed under the MIT License. See LICENSE.

Can you improve this documentation? These fine people already did:
Sam Ritchie & Pedro Gomes Branquinho
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close