3 Understanding Wolframite
Where you learn a little more about how Wolframite works, so that you can use it effectively.
If you are in a hurry, you can just skim through this to know what answers you can find here when you need them. Though make sure to learn about the three types of expressions → Section 3.2 that Wolframite supports. We will refer to this chapter from other parts of the documentation.
First, we need some namespaces:
ns understanding-wolframite
(:require [scicloj.kindly.v4.api :as kindly]
(:as kind]
[scicloj.kindly.v4.kind :as k]
[scicloj.kindly.v4.kind repl :as repl]
[clojure.:as wl]
[wolframite.api.v1 :as wc]
[wolframite.core :as h]
[wolframite.lib.helpers :as w :refer :all
[wolframite.wolfram :exclude [* + - -> / < <= = == > >= fn
Byte Character Integer Number Short String Thread]] wolframite.runtime.defaults))
Next, we need to actually start a Wolfram Kernel and connect to it:
(wl/start!)
:status :ok, :wolfram-version 14.1} {
3.1 How it all works
Wolframite works by translating Clojure data into Wolfram JLink Expr(essions) and sending them to an external, Wolframite-managed Wolfram process for evaluation, then translating the response back into data.
3.2 Expressions
Wolframite expressions consist of stuff that can be translated to Wolfram: data structures ([]
for Lists and {}
for Associations), constants, and Wolfram symbols (plus Wolframite aliases → Section 3.2.7). There are also some “Wolframite-isms” that we support, namely w/fn
for defining ad-hoc functions, primarily for use with Map
.
There are three ways of writing these expressions.
3.2.1 Evaluated form
The primary form you will encounter is the evaluated form, which uses vars from the wolframite.wolfram
namespace as proxies for the actual Wolfram functions and symbols. It is the most convenient form, with support for code completion and mixing with evaluated Clojure code:
5 4) (w/Minus 1))) (wl/! (w/+ (clojure.core/-
0
When requiring wolframite.wolfram
, we can “refer” most of its symbols, excluding those that conflict with Clojure or Java (such as +
; have a look at the require
at the top of this page), and thus we can also write:
- 5 4) (Minus 1))) (wl/! (w/+ (
0
Notice that we have convenience vars for both Wolfram symbols and Wolframite aliases (see below) and thus both of the following work:
=
(1 (w/- 1)))
(wl/! (w/+ 1 (Minus 1)))) (wl/! (Plus
true
We can also mix Clojure and Wolfram - but the Clojure parts are evaluated on our side, before the rest of the expression is translated and sent to Wolfram.
The evaluated form is actually translated into the raw form (see Section 3.2.3) before being evaluated, as we can see if we run it on its own, without passing it to wl/!
:
- 5 4) (w/Minus 1)) (w/Plus (
1 (Minus 1)) (Plus
You can see here that the convenience functions from the w/
namespace essentially evaluate to themselves in the symbolic, raw form - (w/Plus arguments...)
becomes '(Plus arguments...)
.
3.2.2 A word on aliases
When we come back to our original expression, '(+ 1 (Minus 1))
, you may notice that +
is not actually a Wolfram function. It is a Wolframite alias, which we replace with Plus
before we send it to Wolfram. You can read about it further down this document, in Section 3.2.7.
3.2.3 Raw form
The second form is the raw (quoted) data form, using actual Clojure symbols corresponding to Wolfram symbols or Wolframite aliases. This is what Wolframite uses internally.
Notice the '
quote in front of the expression, telling Clojure not to evaluate it but return it as-is:
+ 1 (Minus 1))) (wl/! '(
0
We could also be more explicit and replace '
with the quote
that it turns into under the hood:
+ 1 (Minus 1)))) (wl/! (quote (
0
However, quoting the whole form does not allow us to have any evaluations inside the expression. We can instead build the form manually, quoting only the symbols:
list 'Plus 1 (list 'Minus 1))) (wl/! (
0
Notice that you will leverage quoting also with the evaluated form, namely when you create and refer to Wolfram-side variables, such as those created by (w/= 'myVar ...)
.
3.2.4 Advanced: Syntax quote
As mentioned, quoting a form makes it impossible to refer to any vars or evaluate any code within the form. We can bypass the problem by not quoting the whole form but replacing (...)
with (list ...)
and quoting each symbol. But there is yet another way, used by Clojure macros - namely the syntax quote `
. It makes it possible to evaluate things within the quoted form by prefixing them with the “unquote” ~
(or “splicing unquote” ~@
). The only problem here is that `
automatically fully qualifies all symbols, as we can see here:
do `(Minus 42)) (
42) (wolframite.wolfram/Minus
This would of course break our translation to Wolfram. This expression would be converted to wolframite.wolfram`Minus[42]
and Wolfram knows no module called wolframite.wolfram
. There is fortunately one trick to tell the Clojure Reader to keep a symbol unqualified by combining ~'
, essentially telling it “evaluate this expression, which returns a simple symbol”:
do `(~'Minus 42)) (
42) (Minus
3.2.5 Wolfram string form
There is one more form, the Wolfram string form, which is the raw Wolfram code in a string:
"Plus[1,Minus[1]]") (wl/!
0
This is useful when you just want to paste in some Wolfram of when you need to use a feature that we don’t yet support.
3.2.6 Mixing different kinds of forms
The evaluated form may also contain sections in the other forms. You’d typically need that when the Wolframite evaluated form does not (yet) support that which you are trying to do.
When nesting a Wolfram string form, we need to explicitly tell Wolframite to treat it as an expression and not just as a primitive string, by passing it through wc/wolfram-expr
:
(wl/! (w/Plus"-1.5")
'(Internal/StringToMReal "Minus[3]"))) (wc/wolfram-expr
4.5 -
3.2.7 Aside: Wolframite aliases
Aside of symbols that directly correspond to Wolfram symbols, you can also use Wolframite aliases. The aliases provide alternative names for Wolfram symbols. We have used above +
, which is an alias for Plus
. Here are all the built-in aliases that we currently support:
:column-names [:Alias :Wolfram "Can be used without `w/` prefix?"],
(k/table {:row-vectors (-> wolframite.runtime.defaults/all-aliases
dissoc '-)
(assoc '- "Minus or Subtract")
(->> (map (fn [[k v]] [k v (when-not (contains? recommended-exclusions k) "✅")]))))}) (
Alias | Wolfram | Can be used without `w/` prefix? |
---|---|---|
>_< | Simplify | ✅ |
! | Not | ✅ |
= | Set | |
<*> | Dot | ✅ |
< | Less | |
=! | Unset | ✅ |
|| | Or | ✅ |
<= | LessEqual | |
** | Power | ✅ |
* | Times | |
-> | Rule | |
x> | Replace | ✅ |
fn | Function | |
do | CompoundExpression | ✅ |
>>_<< | FullSimplify | ✅ |
<<_>> | ExpandAll | ✅ |
> | Greater | |
_> | RuleDelayed | ✅ |
<_> | Expand | ✅ |
- | Minus or Subtract | |
&& | And | ✅ |
x>> | ReplaceAll | ✅ |
=== | SameQ | ✅ |
/ | Divide | |
>= | GreaterEqual | |
++<_> | ComplexExpand | ✅ |
⮾ | NonCommutativeMultiply | ✅ |
<> | StringJoin | ✅ |
∫ | Integrate | ✅ |
+ | Plus | |
+= | AddTo | ✅ |
?? | Information | ✅ |
== | Equal | |
_= | SetDelayed | ✅ |
-= | SubtractFrom | ✅ |
√ | Sqrt | ✅ |
++ | Conjugate | ✅ |
Thus, the following two expressions are equivalent
+ 1 (- 1))) (wl/! '(
0
1 (Minus 1))) (wl/! '(Plus
0
You can even add your own aliases, as discussed in Section 11.0.1.
3.2.8 Aside: Wolfram modules and fully qualified names
Most Wolfram functions are global, but they can also be placed inside modules and need to be referred to by their fully qualified names. While Wolfram uses `
to separate the module and the symbol, we write it as /
. Thus, these two are equivalent:
"Internal`StringToMReal[\"-1.5\"]") (wl/!
1.5 -
"-1.5")) (wl/! '(Internal/StringToMReal
1.5 -
(Of course, you normally do not want to use the Internal/* functions, as they may disappear or change between Wolfram versions.)
3.3 Aside: Translating Wolfram to Wolframite
When asking the internets or ChatGPT how to do a certain thing in Wolfram, you will get a Wolfram answer. Thus you need to know how to translate it from Wolfram to Wolframite. Let’s say you have this Wolfram snippet:
Import["demo.csv.gz", {"Data", 1 ;; 3}, "HeaderLines" -> 1]
How do you turn it into Wolframite Clojure? Let’s ask for help!
"Import[\"demo.csv.gz\", {\"Data\", 1 ;; 3}, \"HeaderLines\" -> 1]") (wl/->clj
"demo.csv.gz" ["Data" (Span 1 3)] (-> "HeaderLines" 1)) (Import
Now, we either need to quote that to turn it into the raw form, or rewrite it using the convenience functions of the evaluated form:
"demo.csv.gz" ["Data" (Span 1 3)] (w/-> "HeaderLines" 1))) (wl/! (Import
3.4 Errors
We try to detect when Wolfram wasn’t able to evaluate your expression and throw an exception. However, sometimes we are not able to detect that. In such cases, the failure will often be indicated by the fact that you get the same, unevaluated expression back.
Here it works as designed:
try (wl/! (FromDigits "-87.6"))
(catch Exception e
(:blockquote (ex-message e)]))) (k/hiccup [
Evaluation seems to have failed. Result: FromDigits["-87.6"] Details: FromDigits::nlst: The expression -87.6 is not a list of digits or a string of valid digits.
Correct:
"87")) (wl/! (FromDigits
87
3.5 Documentation
We can leverage Clojure repl’s documentation support with the Wolfram convenience vars:
(show-stdout (repl/doc GeoGraphics))
------------------------- wolframite.wolfram/GeoGraphics GeoGraphics[primitives, options] represents a two-dimensional geographical image.
Search for symbols (case-insensitive):
->> (repl/apropos #"(?i)geo")
(drop 2)
(take 3)) (
(wolframite.wolfram/$GeoLocationCountry
wolframite.wolfram/$GeoLocationSource wolframite.wolfram/ArithmeticGeometricMean)
Search complete docstrings for a pattern:
(show-stdout"two-dimensional")) (repl/find-doc
------------------------- wolframite.wolfram/Area Area[reg] gives the area of the two-dimensional region reg. Area[{x1, …, xn}, {s, smin, smax}, {t, tmin, tmax}] gives the area of the parametrized surface whose Cartesian coordinates xi are functions of s and t. Area[{x1, …, xn}, {s, smin, smax}, {t, tmin, tmax}, chart] interprets the xi as coordinates in the specified coordinate chart. ------------------------- wolframite.wolfram/AstroGraphics AstroGraphics[primitives, options] represents a ...
If we evaluate (h/help! 'ArithmeticGeometricMean)
then it will open the Wolfram documentation page for ArithmeticGeometricMean
.
We could instead ask for the link(s):
:links true) (h/help! w/ArithmeticGeometricMean
"https://reference.wolfram.com/language/ref/ArithmeticGeometricMean.html"] [
h/help!
also works on whole expressions, providing docs for each symbol:
"City" ["NewYork" "NewYork" "UnitedStates"])) :links true) (h/help! '(GeoImage (Entity
"https://reference.wolfram.com/language/ref/GeoImage.html"
("https://reference.wolfram.com/language/ref/Entity.html")
(Notice that help! works both with symbols and our convenience vars.)