View All Release Announcements »

Version 12 Launches Today! (And It’s a Big Jump for Wolfram Language and Mathematica)

The Road to Version 12

Today we’re releasing Version 12 of Wolfram Language (and Mathematica) on desktop platforms, and in the Wolfram Cloud. We released Version 11.0 in August 2016, 11.1 in March 2017, 11.2 in September 2017 and 11.3 in March 2018. It’s a big jump from Version 11.3 to Version 12.0. Altogether there are 278 completely new functions, in perhaps 103 areas, together with thousands of different updates across the system:

Version 12 launches today

In an “integer release” like 12, our goal is to provide fully-filled-out new areas of functionality. But in every release we also want to deliver the latest results of our R&D efforts. In 12.0, perhaps half of our new functions can be thought of as finishing areas that were started in previous “.1” releases—while half begin new areas. I’ll discuss both types of functions in this piece, but I’ll be particularly emphasizing the specifics of what’s new in going from 11.3 to 12.0.

I must say that now that 12.0 is finished, I’m amazed at how much is in it, and how much we’ve added since 11.3. In my keynote at our Wolfram Technology Conference last October I summarized what we had up to that point—and even that took nearly 4 hours. Now there’s even more.

What we’ve been able to do is a testament both to the strength of our R&D effort, and to the effectiveness of the Wolfram Language as a development environment. Both these things have of course been building for three decades. But one thing that’s new with 12.0 is that we’ve been letting people watch our behind-the-scenes design process—livestreaming more than 300 hours of my internal design meetings. So in addition to everything else, I suspect this makes Version 12.0 the very first major software release in history that’s been open in this way.

OK, so what’s new in 12.0? There are some big and surprising things—notably in chemistry, geometry, numerical uncertainty and database integration. But overall, there are lots of things in lots of areas—and in fact even the basic summary of them in the Documentation Center is already 19 pages long:

New features in Version 12

First, Some Math

Although nowadays the vast majority of what the Wolfram Language (and Mathematica) does isn’t what’s usually considered math, we still put immense R&D effort into pushing the frontiers of what can be done in math. And as a first example of what we’ve added in 12.0, here’s the rather colorful ComplexPlot3D:

ComplexPlot3D
&#10005

ComplexPlot3D[Gamma[z],{z,-4-4I,4+4I}]

It’s always been possible to write Wolfram Language code to make plots in the complex plane. But only now have we solved the math and algorithm problems that are needed to automate the process of robustly plotting even quite pathological functions in the complex plane.

Years ago I remember painstakingly plotting the dilogarithm function, with its real and imaginary parts. Now ReImPlot just does it:

ReImPlot
&#10005

ReImPlot[PolyLog[2, x], {x, -4, 4}]

The visualization of complex functions is (pun aside) a complex story, with details making a big difference in what one notices about a function. And so one of the things we’ve done in 12.0 is to introduce carefully selected standardized ways (such as named color functions) to highlight different features:

ComplexPlot
&#10005

ComplexPlot[(z^2+1)/(z^2-1),{z,-2-2I,2+2I},ColorFunction->"CyclicLogAbsArg"]

The Calculus of Uncertainty

Measurements in the real world often have uncertainty that gets represented as values with ± errors. We’ve had add-on packages for handling “numbers with errors” for ages. But in Version 12.0 we’re building in computation with uncertainty, and we’re doing it right.

The key is the symbolic object Around[x, δ], which represents a value “around x”, with uncertainty δ:

Around
&#10005

Around[7.1,.25]

You can do arithmetic with Around, and there’s a whole calculus for how the uncertainties combine:

Sqrt
&#10005

Sqrt[Around[7.1,.25]]+Around[1,.1]

If you plot Around numbers, they’ll be shown with error bars:

ListPlot
&#10005

ListPlot[Table[Around[n,RandomReal[Sqrt[n]]],{n,20}]]

There are lots of options—like here’s one way to show uncertainty in both x and y:

ListPlot
&#10005

ListPlot[Table[Around[RandomReal[10],RandomReal[1]],20,2],IntervalMarkers->"Ellipses"]

You can have Around quantities:

1/Around[3m, 3.5cm]
&#10005

1/Around[Quantity[3, "Metres"], Quantity[3.5, "Centimetres"]]

And you can also have symbolic Around objects:

Around
&#10005

Around[x,Subscript[δ, x]]+Around[y,Subscript[δ, y]]

But what really is an Around object? It’s something where there are certain rules for combining uncertainties, that are based on uncorrelated normal distributions. But there’s no statement being made that Around[x, δ] represents anything that actually in detail follows a normal distribution—any more than that Around[x, δ] represents a number specifically in the interval defined by Interval[{x - δ, x + δ}]. It’s just that Around objects propagate their errors or uncertainties according to consistent general rules that successfully capture what’s typically done in experimental science.

OK, so let’s say you make a bunch of measurements of some value. You can get an estimate of the value—together with its uncertainty—using MeanAround (and, yes, if the measurements themselves have uncertainties, these will be taken into account in weighting their contributions):

MeanAround
&#10005

MeanAround[{1.4,1.7,1.8,1.2,1.5,1.9,1.7,1.3,1.7,1.9,1.0,1.7}]

Functions all over the system—notably in machine learning—are starting to have the option ComputeUncertaintyTrue, which makes them give Around objects rather than pure numbers.

Around might seem like a simple concept, but it’s full of subtleties—which is the main reason it’s taken until now for it to get into the system. Many of the subtleties revolve around correlations between uncertainties. The basic idea is that the uncertainty of every Around object is assumed to be independent. But sometimes one has values with correlated uncertainties—and so in addition to Around, there’s also VectorAround, which represents a vector of potentially correlated values with a specified covariance matrix.

There’s even more subtlety when one’s dealing with things like algebraic formulas. If one replaces x here with an Around, then, following the rules of Around, each instance is assumed to be uncorrelated:

(Exp[x]+Exp[x/2])
&#10005

(Exp[x]+Exp[x/2])/.x->Around[0,.3]

But probably one wants to assume here that even though the value of x may be uncertain, it’s going to be the same for each instance, and one can do this using the function AroundReplace (notice the result is different):

AroundReplace
&#10005

AroundReplace[Exp[x]+Exp[x/2],x->Around[0,.3]]

There’s lots of subtlety in how to display uncertain numbers. Like how many trailing 0s should you put in:

Around[1, .0006]
&#10005

Around[1,.0006]

Or how much precision of the uncertainty should you include (there’s a conventional breakpoint when the trailing digits are 35):

{Around[1.2345, .000312], Around[1.2345, .00037]}
&#10005

{Around[1.2345,.000312],Around[1.2345,.00037]}

In rare cases where lots of digits are known (think, for example, some physical constants), one wants to go to a different way to specify uncertainty:

Around[1.23456789, .000000001]
&#10005

Around[1.23456789,.000000001]

And it goes on and on. But gradually Around is going to start showing up all over the system. By the way, there are lots of other ways to specify Around numbers. This is a number with 10% relative error:

Around[2, Scaled[.1]]
&#10005

Around[2,Scaled[.1]]

This is the best Around can do in representing an interval:

Around[Interval[{2, 3}]]
&#10005

Around[Interval[{2,3}]]

For a distribution, Around computes variance:

Around[NormalDistribution[2, 1]]
&#10005

Around[NormalDistribution[2,1]]

It can also take into account asymmetry by giving asymmetric uncertainties:

Around[LogNormalDistribution[2, 1]]
&#10005

Around[LogNormalDistribution[2,1]]

Classic Math, Elementary and Advanced

In making math computational, it’s always a challenge to both be able to “get everything right”, and not to confuse or intimidate elementary users. Version 12.0 introduces several things to help. First, try solving an irreducible quintic equation:

Solve
&#10005

Solve[x^5 + 6 x + 1 == 0, x]

In the past, this would have shown a bunch of explicit Root objects. But now the Root objects are formatted as boxes showing their approximate numerical values. Computations work exactly the same, but the display doesn’t immediately confront people with having to know about algebraic numbers.

When we say Integrate, we mean “find an integral”, in the sense of an antiderivative. But in elementary calculus, people want to see explicit constants of integration (as they always have in Wolfram|Alpha), so we added an option for that (and C[n] also has a nice, new output form):

Integrate
&#10005

Integrate[x^3,x,GeneratedParameters->C]

When we benchmark our symbolic integration capabilities we do really well. But there’s always more that can be done, particularly in terms of finding the simplest forms of integrals (and at a theoretical level this is an inevitable consequence of the undecidability of symbolic expression equivalence). In Version 12.0 we’ve continued to pick away at the frontier, adding cases like:

Sqrt
&#10005

\[Integral]Sqrt[
   Sqrt[x] + Sqrt[2 x + 2 Sqrt[x] + 1] + 1] \[DifferentialD]x

Integral
&#10005

\[Integral]x^2/(ProductLog[a/x] + 1) \[DifferentialD]x

In Version 11.3 we introduced asymptotic analysis, being able to find asymptotic values of integrals and so on. Version 12.0 adds asymptotic sums, asymptotic recurrences and asymptotic solutions to equations:

AsymptoticSum
&#10005

AsymptoticSum[1/Sqrt[k], {k, 1, n}, {n, \[Infinity], 5}]

AsymptoticSolve
&#10005

AsymptoticSolve[x y^4 - (x + 1) y^2 + x == 1, y, {x, 0, 3}, Reals]

One of the great things about making math computational is that it gives us new ways to explain math itself. And something we’ve been doing is to enhance our documentation so that it explains the math as well as the functions. For example, here’s the beginning of the documentation about Limit—with diagrams and examples of the core mathematical ideas:

Limit

More with Polygons

Polygons have been part of the Wolfram Language since Version 1. But in Version 12.0 they’re getting generalized: now there’s a systematic way to specify holes in them. A classic geographic use case is the polygon for South Africa—with its hole for the country of Lesotho.

In Version 12.0, much like Root, Polygon gets a convenient new display form:

RandomPolygon[20]
&#10005

RandomPolygon[20]

You can compute with it just as before:

Area
&#10005

Area[%]

RandomPolygon is new too. You can ask, say, for 5 random convex polygons, each with 10 vertices, in 3D:

Graphics3D
&#10005

Graphics3D[RandomPolygon[3->{"Convex",10},5]]

There are lots of new operations on polygons. Like PolygonDecomposition, which can, for example, decompose a polygon into convex parts:

RandomPolygon[8]
&#10005

RandomPolygon[8]

PolygonDecomposition
&#10005

PolygonDecomposition[%, "Convex"]

Polygons with holes introduce a need for other kinds of operations too, like OuterPolygon, SimplePolygonQ, and CanonicalizePolygon.

Computing with Polyhedra

Polygons are pretty straightforward to specify: you just give their vertices in order (and if they have holes, you also give the vertices for the holes). Polyhedra are a bit more complicated: in addition to giving the vertices, you have to say how these vertices form faces. But in Version 12.0, Polyhedron lets you do this in considerable generality, including voids (the 3D analog of holes), etc.

But first, recognizing their 2000+ years of history, Version 12.0 introduces functions for the five Platonic solids:

Graphics3D[Dodecahedron[]]
&#10005

Graphics3D[Dodecahedron[]]

And given the Platonic solids, one can immediately start computing with them:

Volume[Dodecahedron[]]
&#10005

Volume[Dodecahedron[]]

Here’s the solid angle subtended at vertex 1 (since it’s Platonic, all the vertices give the same angle):

PolyhedronAngle
&#10005

PolyhedronAngle[Dodecahedron[],1]

Here’s an operation done on the polyhedron:

BeveledPolyhedron
&#10005

Graphics3D[BeveledPolyhedron[Dodecahedron[],1]]

Volume
&#10005

Volume[DualPolyhedron[BeveledPolyhedron[Dodecahedron[],1]]]

Beyond the Platonic solids, Version 12 also builds in all the “uniform polyhedra” (n edges and m faces meet at each vertex)—and you can also get symbolic Polyhedron versions of named polyhedra from PolyhedronData:

AugmentedPolyhedron
&#10005

Graphics3D[AugmentedPolyhedron[PolyhedronData["Spikey","Polyhedron"],2]]

You can make any polyhedron (including a “random” one, with RandomPolyhedron), then do whatever computations you want on it:

RegionUnion
&#10005

RegionUnion[Dodecahedron[{0,0,0}],Dodecahedron[{1,1,1}]]

SurfaceArea
&#10005

SurfaceArea[%]

Euclid-Style Geometry Made Computable

Mathematica and the Wolfram Language are very powerful at doing both explicit computational geometry and geometry represented in terms of algebra. But what about geometry the way it’s done in Euclid’s Elements—in which one makes geometric assertions and then sees what their consequences are?

Well, in Version 12, with the whole tower of technology we’ve built, we’re finally able to deliver a new style of mathematical computation—that in effect automates what Euclid was doing 2000+ years ago. A key idea is to introduce symbolic “geometric scenes” that have symbols representing constructs such as points, and then to define geometric objects and relations in terms of them.

For example, here’s a geometric scene representing a triangle a, b, c, and a circle through a, b and c, with center o, with the constraint that o is at the midpoint of the line from a to c:

GeometricScene
&#10005

GeometricScene[{a,b,c,o},{Triangle[{a,b,c}],CircleThrough[{a,b,c},o],o==Midpoint[{a,c}]}]

On its own, this is just a symbolic thing. But we can do operations on it. For example, we can ask for a random instance of it, in which a, b, c and o are made specific:

RandomInstance
&#10005

RandomInstance[GeometricScene[{a,b,c,o},{Triangle[{a,b,c}],CircleThrough[{a,b,c},o],o==Midpoint[{a,c}]}]]

You can generate as many random instances as you want. We try to make the instances as generic as possible, with no coincidences that aren’t forced by the constraints:

RandomInstance
&#10005

RandomInstance[GeometricScene[{a,b,c,o},{Triangle[{a,b,c}],CircleThrough[{a,b,c},o],o==Midpoint[{a,c}]}],3]

OK, but now let’s “play Euclid”, and find geometric conjectures that are consistent with our setup:

FindGeometricConjectures
&#10005

FindGeometricConjectures[GeometricScene[{a,b,c,o},{Triangle[{a,b,c}],CircleThrough[{a,b,c},o],o==Midpoint[{a,c}]}]]

For a given geometric scene, there may be many possible conjectures. We try to pick out the interesting ones. In this case we come up with two—and what’s illustrated is the first one: that the line ba is perpendicular to the line cb. As it happens, this result actually appears in Euclid (it’s in Book 3, as part of Proposition 31)— though it’s usually called Thales’s theorem.

In 12.0, we now have a whole symbolic language for representing typical things that appear in Euclid-style geometry. Here’s a more complex situation—corresponding to what’s called Napoleon’s theorem:

RandomInstance
&#10005

RandomInstance[
 GeometricScene[{"C", "B", "A", "C'", "B'", "A'", "Oc", "Ob", 
   "Oa"}, {Triangle[{"C", "B", "A"}], 
   TC == Triangle[{"A", "B", "C'"}], TB == Triangle[{"C", "A", "B'"}],
    TA == Triangle[{"B", "C", "A'"}], 
   GeometricAssertion[{TC, TB, TA}, "Regular"], 
   "Oc" == TriangleCenter[TC, "Centroid"], 
   "Ob" == TriangleCenter[TB, "Centroid"], 
   "Oa" == TriangleCenter[TA, "Centroid"], 
   Triangle[{"Oc", "Ob", "Oa"}]}]]

In 12.0 there are lots of new and useful geometric functions that work on explicit coordinates:

CircleThrough
&#10005

CircleThrough[{{0,0},{2,0},{0,3}}]

TriangleMeasurement
&#10005

TriangleMeasurement[Triangle[{{0,0},{1,2},{3,4}}],"Inradius"]

For triangles there are 12 types of “centers” supported, and, yes, there can be symbolic coordinates:

TriangleCenter
&#10005

TriangleCenter[Triangle[{{0,0},{1,2},{3,y}}],"NinePointCenter"]

And to support setting up geometric statements we also need “geometric assertions”. In 12.0 there are 29 different kinds—such as "Parallel", "Congruent", "Tangent", "Convex", etc. Here are three circles asserted to be pairwise tangent:

RandomInstance
&#10005

RandomInstance[GeometricScene[{a,b,c},{GeometricAssertion[{Circle[a],Circle[b],Circle[c]},"PairwiseTangent"]}]]

Going Super-Symbolic with Axiomatic Theories

Version 11.3 introduced FindEquationalProof for generating symbolic representations of proofs. But what axioms should be used for these proofs? Version 12.0 introduces AxiomaticTheory, which gives axioms for various common axiomatic theories.

Here’s my personal favorite axiom system:

WolframAxioms
&#10005

AxiomaticTheory["WolframAxioms"]

What does this mean? In a sense it’s a more symbolic symbolic expression than we’re used to. In something like 1 + x we don’t say what the value of x is, but we imagine that it can have a value. In the expression above, a, b and c are pure “formal symbols” that serve an essentially structural role, and can’t ever be thought of as having concrete values.

What about the · (center dot)? In 1 + x we know what + means. But the · is intended to be a purely abstract operator. The point of the axiom is in effect to define a constraint on what · can represent. In this particular case, it turns out that the axiom is an axiom for Boolean algebra, so that · can represent Nand and Nor. But we can derive consequences of the axiom completely formally, for example with FindEquationalProof:

FindEquationalProof
&#10005

FindEquationalProof[p·q==q·p,AxiomaticTheory["WolframAxioms"]]

There’s quite a bit of subtlety in all of this. In the example above, it’s useful to have · as the operator, not least because it displays nicely. But there’s no built-in meaning to it, and AxiomaticTheory lets you give something else (here f) as the operator:

WolframAxioms Nand
&#10005

AxiomaticTheory[{"WolframAxioms",<|"Nand"->f|>}]

What’s the “Nand” doing there? It’s a name for the operator (but it shouldn’t be interpreted as anything to do with the value of the operator). In the axioms for group theory, for example, several operators appear:

GroupAxioms
&#10005

AxiomaticTheory["GroupAxioms"]

This gives the default representations of the various operators here:

AxiomaticTheory
&#10005

AxiomaticTheory["GroupAxioms","Operators"] 

AxiomaticTheory knows about notable theorems for particular axiomatic systems:

NotableTheorems
&#10005

AxiomaticTheory["GroupAxioms","NotableTheorems"]

The basic idea of formal symbols was introduced in Version 7, for doing things like representing dummy variables in generated constructs like these:

PDF[NormalDistribution,0,1]]
&#10005

PDF[NormalDistribution[0,1]]

DifferenceRoot
&#10005

Sum[2^n n!, n]

Torus surface
&#10005

Entity["Surface", "Torus"][EntityProperty["Surface", "AlgebraicEquation"]]

You can enter a formal symbol using \[FormalA] or Esc.aEsc, etc. But back in Version 7,
\[FormalA] was rendered as a. And that meant the expression above looked like:

Function[{\[FormalA], \[FormalC]}, 
 Function[{\[FormalX], \[FormalY], \[FormalZ]}, \[FormalA]^4 - 
   2 \[FormalA]^2 \[FormalC]^2 + \[FormalC]^4 - 
   2 \[FormalA]^2 \[FormalX]^2 - 
   2 \[FormalC]^2 \[FormalX]^2 + \[FormalX]^4 - 
   2 \[FormalA]^2 \[FormalY]^2 - 2 \[FormalC]^2 \[FormalY]^2 + 
   2 \[FormalX]^2 \[FormalY]^2 + \[FormalY]^4 - 
   2 \[FormalA]^2 \[FormalZ]^2 + 2 \[FormalC]^2 \[FormalZ]^2 + 
   2 \[FormalX]^2 \[FormalZ]^2 + 
   2 \[FormalY]^2 \[FormalZ]^2 + \[FormalZ]^4]]

I always thought this looked incredibly complicated. And for Version 12 we wanted to simplify it. We tried many possibilities, but eventually settled on single gray underdots—which I think look much better.

In AxiomaticTheory, both the variables and the operators are “purely symbolic”. But one thing that’s definite is the arity of each operator, which one can ask AxiomaticTheory:

BooleanAxioms
&#10005

AxiomaticTheory["BooleanAxioms"]

OperatorArities
&#10005

AxiomaticTheory["BooleanAxioms","OperatorArities"]

Conveniently, the representation of operators and arities can immediately be fed into Groupings, to get possible expressions involving particular variables:

Groupings[{a,b}, %]
&#10005

Groupings[{a,b},%]

The n-Body Problem

Axiomatic theories represent a classic historical area for mathematics. Another classical historical area—much more on the applied side—is the n-body problem. Version 12.0 introduces NBodySimulation, which gives simulations of the n-body problem. Here’s a three-body problem (think Earth-Moon-Sun) with certain initial conditions (and inverse-square force law):

NBodySimulation
&#10005

NBodySimulation["InverseSquare",{<|"Mass"->1,"Position"->{0,0},"Velocity"->{0,.5}|>,
<|"Mass"->1,"Position"->{1,1},"Velocity"->{0,-.5}|>,
<|"Mass"->1,"Position"->{0,1},"Velocity"->{0,0}|>},4]

You can ask about various aspects of the solution; this plots the positions as a function of time:

ParametricPlot
&#10005

ParametricPlot[Evaluate[%[All, "Position", t]], {t, 0, 4}]

Underneath, this is just solving differential equations, but—a bit like SystemModelNBodySimulation provides a convenient way to set up the equations and handle their solutions. And, yes, standard force laws are built in, but you can define your own.

Language Extensions & Conveniences

We’ve been polishing the core of the Wolfram Language for more than 30 years now, and in each successive version we end up introducing some new extensions and conveniences.

We’ve had the function Information ever since Version 1.0, but in 12.0 we’ve greatly extended it. It used to just give information about symbols (although that’s been modernized as well):

Information[Sin]
&#10005

Information[Sin]

But now it also gives information about lots of kinds of objects. Here’s information on a classifier:

Information[Classify["NotablePerson"]]
&#10005

Information[Classify["NotablePerson"]]

Here’s information about a cloud object:

Information[CloudPut[100!]]
&#10005

Information[CloudPut[100!]]

Hover over the labels in the “information box” and you can find out the names of the corresponding properties:

FileHashMD5
&#10005

Information[CloudPut[100!],"FileHashMD5"]

For entities, Information gives a summary of known property values:

Information[tungsten]
&#10005

Information[Entity["Element", "Tungsten"]]

Over the past few versions, we’ve introduced a lot of new summary display forms. In Version 11.3 we introduced Iconize, which is essentially a way of creating a summary display form for anything. Iconize has proved to be even more useful than we originally anticipated. It’s great for hiding unnecessary complexity both in notebooks and in pieces of Wolfram Language code. In 12.0 we’ve redesigned how Iconize displays, particularly to make it “read nicely” inside expressions and code.

You can explicitly iconize something:

{a,b, Iconize[Range[10]]}
&#10005

{a,b,Iconize[Range[10]]}

Press the + and you’ll see some details:

Iconize expanded

Press Uniconize and you’ll get the original expression again:

Uniconize

If you have lots of data you want to reference in a computation, you can always store it in a file, or in the cloud (or even in a data repository). It’s usually more convenient, though, to just put it in your notebook, so you have everything in the same place. One way to avoid the data “taking over your notebook” is to put in closed cells. But Iconize provides a much more flexible and elegant way to do this.

When you’re writing code, it’s often convenient to “iconize in place”. The right-click menu now lets you do that:

Iconize workflow
&#10005

Plot[Sin[x], {x, 0, 10}, PlotStyle -> Red, Filling -> Axis, 
 FillingStyle -> LightYellow]

Talking of display, here’s something small but convenient that we added in 12.0:

PercentForm
&#10005

PercentForm[0.3]

And here are a couple of other “number conveniences” that we added:

NumeratorDenominator
&#10005

NumeratorDenominator[11/4]

MixedFractionParts
&#10005

MixedFractionParts[11/4]

Functional programming has always been a central part of the Wolfram Language. But we’re continually looking to extend it, and to introduce new, generally useful primitives. An example in Version 12.0 is SubsetMap:

SubsetMap[Reverse, {a, b, c, xxx, yyy, zzz}, {2, 5}]
&#10005

SubsetMap[Reverse, {a, b, c, xxx, yyy, zzz}, {2, 5}]

SubsetMap[Reverse@*Map[f], {a, b, c, xxx, yyy, zzz}, {2, 5}]
&#10005

SubsetMap[Reverse@*Map[f], {a, b, c, xxx, yyy, zzz}, {2, 5}]

Functions are normally things that can take several inputs, but always give a single piece of output. In areas like quantum computing, however, one’s interested instead in having inputs and outputs. SubsetMap effectively implements functions, picking up inputs from specified positions in a list, applying some operation to them, then putting back the results at the same positions.

I started formulating what’s now SubsetMap about a year ago. And I quickly realized that actually I could really have used this function in all sorts of places over the years. But what should this particular “lump of computational work” be called? My initial working name was ArrayReplaceFunction (which I shortened to ARF in my notes). In a sequence of (livestreamed) meetings we went back and forth. There were ideas like ApplyAt (but it’s not really Apply) and MutateAt (but it’s not doing mutation in the lvalue sense), as well as RewriteAt, ReplaceAt, MultipartApply and ConstructInPlace. There were ideas about curried “function decorator” forms, like PartAppliedFunction, PartwiseFunction, AppliedOnto, AppliedAcross and MultipartCurry.

But somehow when we explained the function we kept on coming back to talking about how it was operating on a subset of a list, and how it was really like Map, except that it was operating on multiple elements at a time. So finally we settled on the name SubsetMap. And—in yet another reinforcement of the importance of language design—it’s remarkable how, once one has a name for something like this, one immediately finds oneself able to reason about it, and see where it can be used.

More Machine Learning Superfunctions

For many years we’ve worked hard to make the Wolfram Language the highest-level and most automated system for doing state-of-the-art machine learning. Early on, we introduced the “superfunctions” Classify and Predict that do classification and prediction tasks in a completely automated way, automatically picking the best approach for the particular input given. Along the way, we’ve introduced other superfunctions—like SequencePredict, ActiveClassification and FeatureExtract.

In Version 12.0 we’ve got several important new machine learning superfunctions. There’s FindAnomalies, which finds “anomalous elements” in data:

FindAnomalies
&#10005

FindAnomalies[{1.2, 2.5, 3.2, 107.6, 4.6, 5, 5.1, 204.2}]

Along with this, there’s DeleteAnomalies, which deletes elements it considers anomalous:

DeleteAnomalies
&#10005

DeleteAnomalies[{1.2, 2.5, 3.2, 107.6, 4.6, 5, 5.1, 204.2}]

There’s also SynthesizeMissingValues, which tries to generate plausible values for missing pieces of data:

SynthesizeMissingValues
&#10005

SynthesizeMissingValues[{{1.1,1.4},{2.3,3.1},{3,4},{Missing[],5.4},{8.7,7.5}}]

How do these functions work? They’re all based on a new function called LearnDistribution, which tries to learn the underlying distribution of data, given a certain set of examples. If the examples were just numbers, this would essentially be a standard statistics problem, for which we could use something like EstimatedDistribution. But the point about LearnDistribution is that it works with data of any kind, not just numbers. Here it is learning an underlying distribution for a collection of colors:

dist=LearnDistribution
&#10005

dist = LearnDistribution[{RGBColor[0.5172966964096541, 
    0.4435322033449375, 1.], 
   RGBColor[0.3984626930847484, 0.5592892024442906, 1.], 
   RGBColor[0.6149389612362844, 0.5648721294502163, 1.], 
   RGBColor[0.4129156497559272, 0.9146065592632544, 1.], 
   RGBColor[0.7907065846445507, 0.41054133291260947`, 1.], 
   RGBColor[0.4878854162550912, 0.9281119680196579, 1.], 
   RGBColor[0.9884362181280959, 0.49025178842859785`, 1.], 
   RGBColor[0.633242503827218, 0.9880985331612835, 1.], 
   RGBColor[0.9215182482568276, 0.8103084921468551, 1.], 
   RGBColor[0.667469513641223, 0.46420827644204676`, 1.]}]

Once we have this “learned distribution”, we can do all sorts of things with it. For example, this generates 20 random samples from it:

RandomVariate[dist,20]
&#10005

RandomVariate[dist,20]

But now think about FindAnomalies. What it has to do is to find out which data points are anomalous relative to what’s expected. Or, in other words, given the underlying distribution of the data, it finds what data points are outliers, in the sense that they should occur only with very low probability according to the distribution.

And just like for an ordinary numerical distribution, we can compute the PDF for a particular piece of data. Purple is pretty likely given the distribution of colors we’ve learned from our examples:

PDF[dist, purple]
&#10005

PDF[dist, RGBColor[
 0.6323870562875563, 0.3525878887878987, 1.0002083564175581`]]

But red is really really unlikely:

PDF[dist, red]
&#10005

PDF[dist, RGBColor[1, 0, 0]]

For ordinary numerical distributions, there are concepts like CDF that tell us cumulative probabilities, say that we’ll get results that are “further out” than a particular value. For spaces of arbitrary things, there isn’t really a notion of “further out”. But we’ve come up with a function we call RarerProbability, that tells us what the total probability is of generating an example with a smaller PDF than something we give:

RarerProbability[dist, purple]
&#10005

RarerProbability[dist, RGBColor[
 0.6323870562875563, 0.3525878887878987, 1.0002083564175581`]]

RarerProbability[dist, red]
&#10005

RarerProbability[dist, RGBColor[1, 0, 0]]

Now we’ve got a way to describe anomalies: they’re just data points that have a very small rarer probability. And in fact FindAnomalies has an option AcceptanceThreshold (with default value 0.001) that specifies what should count as “very small”.

OK, but let’s see this work on something more complicated than colors. Let’s train an anomaly detector by looking at 1000 examples of handwritten digits:

AnomalyDetection
&#10005

AnomalyDetection[RandomSample[ResourceData["MNIST"][[All,1]],1000]]

Now FindAnomalies can tell us which examples are anomalous:

FindAnomalies

FindAnomalies[AnomalyDetection[RandomSample[ResourceData["MNIST"][[All,1]],1000]], {\!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x+84O9URsb6P1ilPk1jAoLzWOUymJiEcchNY2Srm80kcAObHC9z1/8w
Jm9sUh0sWf+/2DItxyJ1T5Cp9f8tJqbDWOTmMgHlinDK8UpyMVn+xCL3K4iJ
Eei7TdicAgT2jIyFOKT+5zGJ38YhtYiRtR6H1CtuRkNcJlozMa/BIfVYiMkA
h9QjAyatF9gkrqo2GjDpPMeq6RzQ0zrPsBv4NI4p+AcuN1ITAABxtMfa
"], {{0, 
       28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x/kgJGJcTUOqV4mFqY12KWKmBiZZI9jlwPqYsEu9ciKgYnRGrsuK6Au
68e4dDEw4dbFVIpdFyNeu7D77NEqoC6mXhLt+n8Mt79C5XGGYhhuf4F14bAL
t7+OyeMKw///LYH+wi7z//9jayYWXHLUBgCB+cHS
"], {{0, 28}, {28, 0}}, {0, 
       255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x/M4I7MI1SBfL2vMOYpxsuocqGMd2DMLehyzoy9MKYTulwWYwuMKY0p
dxanXCJCQJrtBqqcOWMXlPVLUhdVCmim76qdm+fNu76wktHr27dvyHLtjChA
GFnuZbkTI6NiQIB/ABvDhOXn0Ez9+/37LxAtJPDsPy4gZIZT6gZnC065HYyn
cMr1IQIWAyQy+q/ELSd7FbfcBNxmir/DKUcVAADomc0b
"], {{0, 28}, {28, 0}}, {
       0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwUDwZwxxCnVOZfnHJ8T75rYAiKC4Cp2n+rMKRcXm8E099+GaNLsW/7OQ9E
2/9ZhqFtwT8HEMV07J8RulT7v1WMINru3xsBNKmw339cQDTroX/FaFLSD/9N
BzOU/n1gR5Mz+vdeBESz7P2XwsAujSIn+/zfxZychqO9//7dOHoRzX8xf/5B
wN9fi/250ExVCwWC4n8//TE8BwW6/97ikmJI+HcLl5Tc43+TcUhxrP33uxmH
nO+/P6W4jDz4bxNOl9AFAAAYpls0
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x8UgKEep5QDbrn9DAz7SdC2vx6uDYtyGO2AKQU1CtO2/bAAxLStHqYa
05FAKTBwwLRtPwMSwHQHg0M9RDu6bRAZCAPd/fX1cLPRtSGZjaENydr9uOTw
pR88cvuxuBJJDqd19AAAMwi/NQ==
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x+M4FkLV9+VK1fmZ2czqn5GlbotwwwETBDiOKrcPCYkuW1oZvZJOMWu
BgJrZiaJ92hyPx6DqSkCzMInsTrokAwTk/AybDLH6oH2WWPR9emYPCczE1fj
DwyZ9/tVwU6Uj9//BU3qoBIz3A+qaPbVMzExMjHJNU8p0hFgYij9jSy3Sl4t
48CBVyDm1UIm5lcoGj8ignAquhwCXHHFDBeYq3CFy9srSUxMTJjhcvbYxn51
kB+CMKSmcHGygPwnf/wzhpwbSIts8GrMIAO6gUktp+05DrdTEQAAo1CVcQ==

"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x/s4PcnHBKvNzqZCNX8wRD/8nmfPx8jEAjFo0udtVcAinNouWpp5aPL
WTIysoauvIvNpq0cIhpJ2B1xgpvR7zsOKWFGRhxufyLEyLj5H1apz9ZAF0rk
fsP01/8f9YwQEP0VTeZGqTojo+Xmfd0yjIy6P1HlUhk52yc+BrG6uTnPosol
MDKKN4K93MDPuA1V7m0qMBA5tUr0tRgZ1dAt/L/SGOIUBqU3mA79sn9KhrOz
c+0HrD4c3AAAH4+4UQ==
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x+UoCeKkZGBkZGx4hmmHDsTFBg/wi3H1I0h186hbwkErExM2piGbnsP
JN7xYpUDgr9vzUAWYkpcqK3NB9u3BUMqghXqlKq/6FKdMFfqY/qvFu4Fz1fo
clOYmTSNjAyx27ei/Or//3+WcaHJ/UZi8qHKnbdtug6TckEzcx4Tk+xVMOuz
J1DKFsmY/3v5gZLX/v/fPUkeKCV7AsUZU4FCchYWPCBHStehOnG/EsxvzNpn
0N3/ygEiJbcIw2v//7/v7nYUKOq+hkWKugAABiF8Xw==
"], {{0, 28}, {28, 0}}, {
       0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x9GwAHim/376+sxpRgYHOoZwABdjgEJYJOrd6iv378fS1DtR6jC7Sg8
cvV45erxGEl1OWzeI8Ip+LU5kGMk0JX7ybHOgTwj0QEApknS3g==
"], {{0, 28}, {
       28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwUD16Qe0EEp9yBfw045Vb924hTbtm/YJxyH/964ZY7j1Mq4F8/Trl6PHKb
yJbrwiNngEtK6CduOZF/N7hwy53DaZ3Ifzxy/1bgkcvHYyZuOd5DrjjlqAUA
H0Iyqg==
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x+s4FJKpNVW7FLzeBgZGdnPYJPaz83IKcXIGIVF6q8ro+zlN6u5NBu/
YMidY2RfBaS2MjLOQpf67MiYDaL/KDLyP0WT62OUfQBmTGJkbECTC2PMh9qr
xMh4HkXqLovsTyjzDi/jORS5HsZkOFsMTS6csRfGvMfDegVZ6pU41w0Y25C7
HEXbOkZxKOtvC8tGVFduhMn97GZMQPNBCUyunpHxCppcG0Tu6yZWsdP/0OSe
mXAD3X1Jg1Hi/H8MUMgo1mUkyqq+HlPq/3JmYLzyVmCRAYLZVTbBP7FLURMA
AEeuuRo=
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\), \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x9gcJ9hDy6pX9GMc3FI/bjAyPgWh9wrZUbeH7hsY2QMwmXdHUamaTik
PlgzsuHStpORMQKXnCuj0C8cUqdZGZVxaZvFyIjLJf/dGKU+4TKSjTERl7bt
eIz0YpR9h0PqPDNuI2czyj/GIfVRl9EVl7Y5jIwzccntl5X+jEuOTgAACjPm
MQ==
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
      "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\)}]

The Latest in Neural Networks

We first introduced our symbolic framework for constructing, exploring and using neural networks back in 2016, as part of Version 11. And in every version since then we’ve added all sorts of state-of-the-art features. In June 2018 we introduced our Neural Net Repository to make it easy to access the latest neural net models from the Wolfram Language—and already there are nearly 100 curated models of many different types in the repository, with new ones being added all the time.

So if you need the latest BERT “transformer” neural network (that was added today!), you can get it from NetModel:

NetModel
&#10005

NetModel["BERT Trained on BookCorpus and English Wikipedia Data"]

You can open this up and see the network that’s involved (and, yes, we’ve updated the display of net graphs for Version 12.0):

NetChain

And you can immediately use the network, here to produce some kind of “meaning features” array:

MatrixPlot
&#10005

NetModel["BERT Trained on BookCorpus and English Wikipedia Data"][
"What a wonderful network!"] // MatrixPlot

In Version 12.0 we’ve introduced several new layer types—notably AttentionLayer, which lets one set up the latest “transformer” architectures—and we’ve enhanced our “neural net functional programming” capabilities, with things like NetMapThreadOperator, and multiple-sequence NetFoldOperator. In addition to these “inside-the-net” enhancements, Version 12.0 adds all sorts of new NetEncoder and NetDecoder cases, such as BPE tokenization for text in hundreds of languages, and the ability to include custom functions for getting data into and out of neural nets.

But some of the most important enhancements in Version 12.0 are more infrastructural. NetTrain now supports multi-GPU training, as well as dealing with mixed-precision arithmetic, and flexible early-stopping criteria. We’re continuing to use the popular MXNet low-level neural net framework (to which we’ve been major contributors)—so we can take advantage of the latest hardware optimizations. There are new options for seeing what’s happening during training, and there’s also NetMeasurements that allows you to make 33 different types of measurements on the performance of a network:

NetMeasurements

NetMeasurements[NetModel["LeNet Trained on MNIST Data"], {\!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x9YUI/HAQ4M+3HKMTDU4zYSt5wDA24z8QUGHjmgdQ54tOFySj0eIx3w
+ICAkftxa8NpHR4jCXicrECpxxPO+3G7hE4AAARG3ZY=
"], {{0, 28}, {28, 0}}, {
        0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
       "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\) -> 1, \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x964N8LDwZGxtQ72OROMvJOKA9glLmJKXWRVWTn//8fuhkljqBLfZZn
fQyiTzExWl5Hk/Nn1AHTmxgZGd2/ocopMn4E0z9NGbnT/6BIvRMzggg8VmDq
RjPyHOMsEPV7tRyjH7pTVjOeA8pcjWJk1DiIIcem2NygD3QHH4bU//9NYoyi
kQv4GDsxpYCuefz2nQJj3V9scv///wpk9MYh9W8mo/wH7FL/rzDynsAh9Vqa
uR2H1BdFpnxcUoaMoTik/ocxOv3BIfVcEBRmVAIAcZ7Grw==
"], {{0, 28}, {28, 
        0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
       "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\) -> 9, \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x8Q8C2ckTEiPz//DRapaCYmKUkmJqZ7mHJ3mZjMUlOnpqa+xpR7r2X2
Fad9sfm43dKDR86Pq6F1dV9RpqpKa+v7b2hyTBCgIQIkxLuuIMtt4Wdikm3e
u/fbLP9VtcpMnA+QJZ9c3fEKyvxzxYtJ9ChO6ycz7sUp99vd6gVOyRQFLCEE
AV9YRR/hkrvNVIrEu1O2bNkFOC+eaSmS3De3UBkeccvWw58/v2qNZ/a5i2pQ
FyswTBjAwSNzA92Ww6vElBlBUrKXsbjh+Rv+3nv37r3CIkUPAAABtrX9
"], {{0, 
        28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
       "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\) -> 5, \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/6cx+OTNyMjIkIdN6n0UEwjwP8UiF8gEAcYvMeUEoHJMN7HK8WmKAeVS
MeVShXvW/z8uxMTkgCn36xOIlMcqBwZLuHDLNQPt68cutYsXuzuB4Ls7UIr3
LrrwgubmSf/TgVJcO9BkrjVwMjGxybMC5WaiSd1XhYUJk+FjNLlOuBSTxg1U
qZVcCDkmlYMHD9ZvhsvNYkIHFhhyVnlKGHLrQT5myrz08v/9Nk0gi8XiAMLC
KUCB8J9g5uM5c+bMxxowAwoAzGhtzQ==
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
       "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\) -> 2, \!\(\*
GraphicsBox[
TagBox[RasterBox[CompressedData["
1:eJxTTMoPSmNiYGAo5gASQYnljkVFiZXBAkBOaF5xZnpeaopnXklqemqRRRJI
mQwU/x8u4FBT01YEb11TJQPLIRiviYmJxaoTAorYWZiYmDj+wOR+bmNnQgGO
25FMfVuS5YqQ4tqHZum3Z2DgzsQk+QK7sxZyM9luwOFkDyamJTiklvExeb/H
LnWKn4n/MHapN15M/CtwmBjGxDQdh9RqASad19ilDvMz8S3CLvXBl4kpHIeJ
AcBgfIVdagsfE9Np7FIHeJiYrHBom8bEZITDjSC5KBxS/88ElbzBJYcNAAB0
/LWr
"], {{0, 28}, {28, 0}}, {0, 255},
ColorFunction->GrayLevel],
BoxForm`ImageTag[
       "Byte", ColorSpace -> Automatic, Interleaving -> None],
Selectable->False],
DefaultBaseStyle->"ImageGraphics",
ImageSizeRaw->{28, 28},
PlotRange->{{0, 28}, {0, 28}}]\) -> 7}, "Perplexity"]

Neural nets aren’t the only—or even always the best—way to do machine learning. But one thing that’s new in Version 12.0 is that we’re now able to use self-normalizing networks automatically in Classify and Predict, so they can easily take advantage of neural nets when it makes sense.

Computing with Images

We introduced ImageIdentify, for identifying what an image is of, back in Version 10.1. In Version 12.0 we’ve managed to generalize this, to figure out not only what an image is of, but also what’s in an image. So, for example, ImageCases will show us cases of known kinds of objects in an image:

ImageCases
&#10005

ImageCases[CloudGet["https://wolfr.am/CMoUVVTH"]]

For more details, ImageContents gives a dataset about what’s in an image:

ImageContents

ImageContents[CloudGet["https://wolfr.am/CMoUVVTH"]]

You can tell ImageCases to look for a particular kind of thing:

ImageCases
&#10005

ImageCases[CloudGet["https://wolfr.am/CMoUVVTH"], "zebra"]

And you can also just test to see whether an image contains a particular kind of thing:

ImageContainsQ
&#10005

ImageContainsQ[CloudGet["https://wolfr.am/CMoUVVTH"], "zebra"]

In a sense, ImageCases is like a generalized version of FindFaces, for finding human faces in an image. Something new in Version 12.0 is that FindFaces and FacialFeatures have become more efficient and robust—with FindFaces now based on neural networks rather than classical image processing, and the network for FacialFeatures now being 10 MB rather than 500 MB:

FacialFeatures
&#10005

FacialFeatures[CloudGet["https://wolfr.am/CO20sk12"]] // Dataset

Functions like ImageCases represent “new-style” image processing, of a type that didn’t seem conceivable only a few years ago. But while such functions let one do all sorts of new things, there’s still lots of value in more classical techniques. We’ve had fairly complete classical image processing in the Wolfram Language for a long time, but we continue to make incremental enhancements.

An example in Version 12.0 is the ImagePyramid framework, for doing multiscale image processing:

ImagePyramid

ImagePyramid[CloudGet["https://wolfr.am/CTWBK9Em"]][All]

There are several new functions in Version 12.0 concerned with color computation. A key idea is ColorsNear, which represents a neighborhood in perceptual color space, here around the color Pink:

ChromaticityPlot3D
&#10005

ChromaticityPlot3D[ColorsNear[Pink,.2]]

The notion of color neighborhoods can be used, for example, in the new ImageRecolor function:

ImageRecolor

ImageRecolor[CloudGet["https://wolfr.am/CT2rFF6e"], 
 ColorsNear[RGBColor[
    Rational[1186, 1275], 
    Rational[871, 1275], 
    Rational[1016, 1275]], .02] -> Orange]

Speech Recognition & More with Audio

As I sit at my computer writing this, I’ll say something to my computer, and capture it:

Play Audio
AudioCapture

Here’s a spectrogram of the audio I captured:

Spectrogram
&#10005

Spectrogram[%]

So far we could do this in Version 11.3 (though Spectrogram got 10 times faster in 12.0). But now here’s something new:

SpeechRecognize
&#10005

SpeechRecognize[%%]

We’re doing speech-to-text! We’re using state-of-the-art neural net technology, but I’m amazed at how well it works. It’s pretty streamlined, and we’re perfectly well able to handle even very long pieces of audio, say stored in files. And on a typical computer the transcription will run at about actual real-time speed, so that an hour of speech will take about an hour to transcribe.

Right now we consider SpeechRecognize experimental, and we’ll be continuing to enhance it. But it’s interesting to see another major computational task just become a single function in the Wolfram Language.

In Version 12.0, there are other enhancements too. SpeechSynthesize supports new languages and new voices (as listed by VoiceStyleData[]).

There’s now WebAudioSearch—analogous to WebImageSearch—that lets you search for audio on the web:

WebAudioSearch
&#10005

WebAudioSearch["rooster"]

You can retrieve actual Audio objects:

WebAudioSearch samples
&#10005

WebAudioSearch["rooster","Samples",MaxItems->3]

Then you can make spectrograms or other measurements:

Spectrogram
&#10005

Spectrogram /@%

And then—new in Version 12.0—you can use AudioIdentify to try to identify the category of sound (is that a talking rooster?):

AudioIdentify
&#10005

AudioIdentify/@%%

We still consider AudioIdentify experimental. It’s an interesting start, but it definitely doesn’t, for example, work as well as ImageIdentify.

A more successful audio function is PitchRecognize, which tries to recognize the dominant frequency in an audio signal (it uses both “classical” and neural net methods). It can’t yet deal with “chords”, but it works pretty much perfectly for “single notes”.

When one deals with audio, one often wants not just to identify what’s in the audio, but to annotate it. Version 12.0 introduces the beginning of a large-scale audio framework. Right now AudioAnnotate can mark where there’s silence, or where there’s something loud. In the future, we’ll be adding speaker identification and word boundaries, and lots else. And to go along with these, we also have functions like AudioAnnotationLookup, for picking out parts of an audio object that have been annotated in particular ways.

Underneath all this high-level audio functionality there’s a whole infrastructure of low-level audio processing. Version 12.0 greatly enhances AudioBlockMap (for applying filters to audio signals), as well as introduces functions like ShortTimeFourier.

A spectrogram can be viewed a bit like a continuous analog of a musical score, in which pitches are plotted as a function of time. In Version 12.0 there’s now InverseSpectrogram—that goes from an array of spectrogram data to audio. Ever since Version 2 in 1991, we’ve had Play to generate sound from a function (like Sin[100 t]). Now with InverseSpectrogram we have a way to go from a “frequency-time bitmap” to a sound. (And, yes, there are tricky issues about best guesses for phases when one only has magnitude information.)

Natural Language Processing

Starting with Wolfram|Alpha, we’ve had exceptionally strong natural language understanding (NLU) capabilities for a long time. And this means that given a piece of natural language, we’re good at understanding it as Wolfram Language—that we can then go and compute from:

Flags of 5 most populous countries in Europe

EntityValue[
 EntityClass[
  "Country", {EntityProperty["Country", "EntityClasses"] -> 
    EntityClass["Country", "Europe"], 
   EntityProperty["Country", "Population"] -> TakeLargest[5]}], 
 EntityProperty["Country", "Flag"]]

But what about natural language processing (NLP)—where we’re taking potentially long passages of natural language, and not trying to completely understand them, but instead just find or process particular features of them? Functions like TextSentences, TextStructure, TextCases and WordCounts have given us basic capabilities in this area for a while. But in Version 12.0—by making use of the latest machine learning, as well as our longstanding NLU and knowledgebase capabilities—we’ve now jumped to having very strong NLP capabilities.

The centerpiece is the dramatically enhanced version of TextCases. The basic goal of TextCases is to find cases of different types of content in a piece of text. An example of this is the classic NLP task of “entity recognition”—with TextCases here finding what country names appear in the Wikipedia article about ocelots:

TextCases
&#10005

TextCases[WikipediaData["ocelots"],"Country"->"Interpretation"]

We could also ask what islands are mentioned, but now we won’t ask for a Wolfram Language interpretation:

TextCases
&#10005

TextCases[WikipediaData["ocelots"],"Island"]

TextCases isn’t perfect, but it does pretty well:

TextCases
&#10005

TextCases[WikipediaData["ocelots"],"Date"]

It supports a whole lot of different content types too:

Text content types

You can ask it to find pronouns, or reduced relative clauses, or quantities, or email addresses, or occurrences of any of 150 kinds of entities (like companies or plants or movies). You can also ask it to pick out pieces of text that are in particular human or computer languages, or that are about particular topics (like travel or health), or that have positive or negative sentiment. And you can use constructs like Containing to ask for combinations of these things (like noun phrases that contain the name of a river):

TextCases
&#10005

TextCases[WikipediaData["ocelots"],Containing["NounPhrase","River"]]

TextContents lets you see, for example, details of all the entities that were detected in a particular piece of text:

TextContents
&#10005

TextContents[TextSentences[WikipediaData["ocelots"], 1]]

And, yes, one can in principle use these capabilities through FindTextualAnswer to try to answer questions from text—but in a case like this, the results can be pretty wacky:

FindTextualAnswer
&#10005

FindTextualAnswer[WikipediaData["ocelots"],"weight of an ocelot",5]

Of course, you can get a real answer from our actual built-in curated knowledgebase:

Entity

Entity["Species", "Species:LeopardusPardalis"][
 EntityProperty["Species", "Weight"]]

By the way, in Version 12.0 we’ve added a variety of little “natural language convenience functions”, like Synonyms and Antonyms:

Synonyms
&#10005

Synonyms["magnificent"]

Computational Chemistry

One of the “surprise” new areas in Version 12.0 is computational chemistry. We’ve had data on explicit known chemicals in our knowledgebase for a long time. But in Version 12.0 we can compute with molecules that are specified simply as pure symbolic objects. Here’s how we can specify what turns out to be a water molecule:

Molecule
&#10005

Molecule[{Atom["H"],Atom["H"],Atom["O"]},{Bond[{1,3}],Bond[{2,3}]}]

And here’s how we can make a 3D rendering:

MoleculePlot3D
&#10005

MoleculePlot3D[%]

We can deal with “known chemicals”:

Caffeine
&#10005

Molecule[Entity["Chemical", "Caffeine"]]

We can use arbitrary IUPAC names:

MoleculePlot3D
&#10005

MoleculePlot3D[Molecule["2,4,6-trimethoxybenzaldehyde"]]

Or we “make up” chemicals, for example specifying them by their SMILES strings:

MoleculePlot3D
&#10005

MoleculePlot3D[Molecule["O1CNNONC(N(OOC)OO)CCNONOCONCCONNCOC1"]]

But we’re not just generating pictures here. We can also compute things from the structure—like symmetries:

Molecule["C1=CC=CC=C1"]["PointGroup"]
&#10005

Molecule["C1=CC=CC=C1"]["PointGroup"]

Given a molecule, we can do things like highlight carbon-oxygen bonds:

MoleculePlot
&#10005

MoleculePlot[
 Molecule["C=C1[C@H](O)C2O[C@@]3(CC[C@H](/C=C/[C@@H](C)[C@@H]4CC(C)=C[\
C@@]5(O[C@H](C[C@@](C)(O)C(=O)O)CC[C@H]5O)O4)O3)CC[C@H]2O[C@H]1[C@@H](\
O)C[C@H](C)C1O[C@@]2(CCCCO2)CC[C@H]1C"], Bond[{"C", "O"}]]

Or highlight structures, say specified by SMARTS strings (here any 5-member ring):

MoleculePlot
&#10005

MoleculePlot[Molecule["C=C1[C@H](O)C2O[C@@]3(CC[C@H](/C=C/[C@@H](C)[C@@H]4CC(C)=C[C@@]5(O[C@H](C[C@@](C)(O)C(=O)O)CC[C@H]5O)O4)O3)CC[C@H]2O[C@H]1[C@@H](O)C[C@H](C)C1O[C@@]2(CCCCO2)CC[C@H]1C"], MoleculePattern["[r5]"]]

You can also do searches for “molecule patterns”; the results come out in terms of atom numbers:

FindMoleculeSubstructure
&#10005

FindMoleculeSubstructure[Molecule["C=C1[C@H](O)C2O[C@@]3(CC[C@H](/C=C/[C@@H](C)[C@@H]4CC(C)=C[C@@]5(O[C@H](C[C@@](C)(O)C(=O)O)CC[C@H]5O)O4)O3)CC[C@H]2O[C@H]1[C@@H](O)C[C@H](C)C1O[C@@]2(CCCCO2)CC[C@H]1C"], MoleculePattern["[r5]"], All]

The computational chemistry capabilities we’ve added in Version 12.0 are pretty general and pretty powerful (with the caveat that so far they only deal with organic molecules). At the lowest level they view molecules as labeled graphs with edges corresponding to bonds. But they also know about physics, and correctly account for atomic valences and bond configurations. Needless to say, there are lots of details (about stereochemistry, symmetry, aromaticity, isotopes, etc.). But the end result is that molecular structure and molecular computation have now successfully been added to the list of areas that are integrated into the Wolfram Language.

Geographic Computing Extended

The Wolfram Language already has strong capabilities for geographic computing, but Version 12.0 adds more functions, and enhances some of those that were already there.

For example, there’s now RandomGeoPosition, which generates a random lat-long location. One might think this would be trivial, but of course one has to worry about coordinate transformations—and what makes it much more nontrivial is that one can tell it to pick points only inside a certain region, here the country of France:

GeoListPlot
&#10005

GeoListPlot[RandomGeoPosition[Entity["Country", "France"],100]]

A theme of new geographic capabilities in Version 12.0 is handling not just geographic points and regions, but also geographic vectors. Here’s the current wind vector, for example, at the position of the Eiffel Tower, represented as a GeoVector, with speed and direction (there’s also GeoVectorENU, which gives east, north and up components, as well as GeoGridVector and GeoVectorXYZ):

WindVectorData
&#10005

WindVectorData[Entity["Building", "EiffelTower::5h9w8"],Now,"DownwindGeoVector"]

Functions like GeoGraphics let you visualize discrete geo vectors. GeoStreamPlot is the geo analog of StreamPlot (or ListStreamPlot)—and shows streamlines formed from geo vectors (here from WindDirectionData):

GeoStreamPlot
&#10005

GeoStreamPlot[CloudGet["https://wolfr.am/CTZnxuQI"]]

Geodesy is a mathematically sophisticated area, and we pride ourselves on doing it well in the Wolfram Language. In Version 12.0, we’ve added a few new functions to fill in some details. For example, we now have functions like GeoGridUnitDistance and GeoGridUnitArea which give the distortion (basically, eigenvalues of the Jacobian) associated with different geo projections at every position on Earth (or Moon, Mars, etc.).

Lots of Little Visualization Enhancements

One direction of visualization that we’ve been steadily developing is what one might call “meta-graphics”: the labeling and annotation of graphical things. We introduced Callout in Version 11.0; in Version 12.0 it’s been extended to things like 3D graphics:

Plot3D
&#10005

Plot3D[Callout[Exp[-(x^2+y^2)],"maximum",{0,0}],{x,-2,2},{y,-2,2}]

It’s pretty good at figuring out where to label things, even when they get a bit complex:

PolarPlot
&#10005

PolarPlot[Evaluate[Table[Callout[Sin[n θ],n],{n,4}]],{θ,0,π}]

There are lots of details that matter in making graphics really look good. Something that’s been enhanced in Version 12.0 is ensuring that columns of graphics line up on their frames, regardless of the length of their tick labels. We’ve also added LabelVisibility, which allows you to specify the relative priorities with which different labels should be made visible.

Another new feature of Version 12.0 is multipanel plot layout, where different datasets are shown in different panels, but the panels share axes whenever they can:

ListPlot
&#10005

ListPlot[Table[RandomReal[10,50],6],PlotLayout->{"Column",3}]

Tightening Knowledgebase Integration

Our curated knowledgebase—that for example powers Wolfram|Alpha—is vast and continually growing. And with every version of the Wolfram Language we’re progressively tightening its integration into the core of the language.

In Version 12.0 one thing we’re doing is to expose hundreds of types of entities directly in the language:

Knowledgebase integration

Before Version 12.0, the Wolfram|Alpha Example pages served as a proxy for documenting many types of entities. But now there’s Wolfram Language documentation for all of them:

University entity

There are still functions like SatelliteData, WeatherData and FinancialData that handle entity types that routinely need complex selection or computation. But in Version 12.0, every entity type can be accessed in the same way, with natural language (“control + =”) input, and “yellow-boxed” entities and properties:

Entity
&#10005

Entity["Element", "Tungsten"][
 EntityProperty["Element", "MeltingPoint"]]

By the way, one can also use entities implicitly, like here asking for the 5 elements with the highest known melting points:

Entity
&#10005

Entity["Element", "MeltingPoint" -> TakeLargest[5]] // EntityList

And one can use Dated to get a time series of values:

Dated
&#10005

Entity["University", "HarvardUniversity::cmp42"][
 Dated[EntityProperty["University", "EstTotalUndergrad"], All]]

Integrating Big Data from External Databases

We’ve made it really convenient to work with data that’s built into the Wolfram Knowledgebase. You have entities, and it’s very easy to ask about properties and so on:

Entity
&#10005

Entity["City", {"NewYork", "NewYork", "UnitedStates"}]["Population"]

But what if you have your own data? Can you set it up so you can use it as easily as this? A major new feature of Version 11 was the addition of EntityStore, in which one can define one’s own entity types, then specify entities, properties and values.

The Wolfram Data Repository contains a bunch of examples of entity stores. Here’s one:

ResourceData
&#10005

ResourceData["Entity Store of Books in Stephen Wolfram's Library"]

It describes a single entity type: an "SWLibraryBook". To be able to use entities of this type just like built-in entities, we “register” the entity store:

EntityRegister
&#10005

EntityRegister[%]

Now we can do things like ask for 10 random entities of type "SWLibraryBook":

RandomEntity
&#10005

RandomEntity["SWLibraryBook",10]

Each entity in the entity store has a variety of properties. Here’s a dataset of the values of properties for one particular entity:

Entity
&#10005

Entity["SWLibraryBook", "OL4258186M::mudwv"]["Dataset"]

OK, but with this setup we’re basically reading the whole contents of an entity store into memory. This makes it very efficient to do whatever Wolfram Language operations one wants on it. But it’s not a good scalable solution for large amounts of data—for example, data that is too big to fit in memory.

But what’s a typical source of large data? Very often it’s a database, and usually a relational one that can be accessed using SQL. We’ve had our DatabaseLink package for low-level read-write access to SQL databases for well over a decade. But in Version 12.0 we’re adding some major built-in features that allow external relational databases to be handled in the Wolfram Language just like entity stores, or built-in parts of the Wolfram Knowledgebase.

Let’s start off with a toy example. Here’s a symbolic representation of a small relational database that happens to be stored in a file:

RelationalDatabase
&#10005

RelationalDatabase[FindFile["ExampleData/ecommerce-database.sqlite"]]

Immediately we get a box that summarizes what’s in the database, and tells us that this database has 8 tables. If we open up the box, we can start inspecting the structure of those tables:

RelationalDatabase

We can then set this relational database up as an entity store in the Wolfram Language. It looks very much the same as the library book entity store above, but now the actual data isn’t pulled into memory; instead it’s still in the external relational database, and we’re just defining a (“ORM-like”) mapping to entities in the Wolfram Language:

EntityStore
&#10005

EntityStore[%]

Now we can register this entity store, which sets up a bunch of entity types that (at least by default) are named after the names of the tables in the database:

EntityRegister
&#10005

EntityRegister[%]

And now we can do “entity computations” on these, just like we would on built-in entities in the Wolfram Knowledgebase. Each entity here corresponds to a row in the “employees” table in the database:

EntityList["employees"]
&#10005

EntityList["employees"]

For a given entity type, we can ask what properties it has. These “properties” correspond to columns in the table in the underlying database:

EntityProperties
&#10005

EntityProperties["employees"]

Now we can ask for the value of a particular property of a particular entity:

EntityProperty
&#10005

Entity["employees", 1076][EntityProperty["employees", "lastName"]]

We can also pick out entities by giving criteria; here we’re asking for “payments” entities with the 4 largest values of the “amount” property:

TakeLargest
&#10005

EntityList[EntityClass["payments","amount"->TakeLargest[4]]]

We can equally ask for the values of these largest amounts:

TakeLargest
&#10005

EntityValue[EntityClass["payments","amount"->TakeLargest[4]],"amount"]

OK, but here’s where it gets more interesting: so far we’ve been looking at a little file-backed database. But we can do exactly the same thing with a giant database hosted on an external server.

As an example, let’s connect to the terabyte-sized OpenStreetMap PostgreSQL database that contains what is basically the street map of the world:

RelationalDatabase

As before, let’s register the tables in this database as entity types. Like most in-the-wild databases there are little glitches in the structure, which are worked around, but generate warnings:

EntityRegister
&#10005

EntityRegister[EntityStore[%]]

But now we can ask questions about the database—like how many geo points or “nodes” there are in all the streets of the world (and, yes, it’s a big number, which is why the database is big):

EntityValue
&#10005

EntityValue["planet_osm_nodes", "EntityCount"]

Here we’re asking for the names of the objects with the 10 largest (projected) areas in the (101 GB) planet_osm_polygon table (and, yes, it takes under a second):

EntityValue
&#10005

EntityValue[
  EntityClass["planet_osm_polygon", "way_area" -> TakeLargest[10]], 
  "name"] // Timing

So how does all this work? Basically what’s happening is that our Wolfram Language representation is getting compiled into low-level SQL queries that are then sent to be executed directly on the database server.

Sometimes you’ll ask for results that are just final values (like, say, the “amounts” above). But in other cases you’ll want something intermediate—like a collection of entities that have been selected in a particular way. And of course this collection could have a billion entries. So a very important feature of what we’re introducing in Version 12.0 is that we can represent and manipulate such things purely symbolically, resolving them to something specific only at the end.

Going back to our toy database, here’s an example of how we’d specify a class of entities obtained by aggregating the total creditLimit for all customers with a given value of country:

AggregatedEntityClass
&#10005

AggregatedEntityClass["customers", "creditLimit" -> Total, "country"]

At first, this is just something symbolic. But if we ask for specific values, then actual database queries get done, and we get specific results:

EntityValue
&#10005

EntityValue[%, {"country", "creditLimit"}]

There’s a family of new functions for setting up different kinds of queries. And the functions actually work not only for relational databases, but also for entity stores, and for the built-in Wolfram Knowledgebase. So, for example, we can ask for the average atomic mass for a given period in the periodic table of elements:

AggregatedEntityClass
&#10005

AggregatedEntityClass["Element", "AtomicMass" -> Mean, "Period"]["AtomicMass"]

An important new construct is EntityFunction. EntityFunction is like Function, except that its variables represent entities (or classes of entities) and it describes operations that can be performed directly on external databases. Here’s an example with built-in data, in which we’re defining a “filtered” entity class in which the filtering criterion is a function which tests population values. The FilteredEntityClass itself is just represented symbolically, but EntityList actually performs the query, and resolves an explicit list of (here, unsorted) entities:

FilteredEntityClass
&#10005

FilteredEntityClass["Country", 
  EntityFunction[c, c["Population"] > Quantity[10^8"People"]]]

EntityList
&#10005

EntityList[%]

In addition to EntityFunction, AggregatedEntityClass and SortedEntityClass, Version 12.0 includes SampledEntityClass (to get a few entities from a class), ExtendedEntityClass (to add computed properties) and CombinedEntityClass (to combine properties from different classes). With these primitives, one can build up all the standard operations of “relational algebra”.

In standard database programming, one typically ends up with a whole jungle of “joins” and “foreign keys” and so on. Our Wolfram Language representation lets you operate at a higher level—where basically joins become function composition and foreign keys are just different entity types. (If you want to do explicit joins, though, you can—for example using CombinedEntityClass.)

What’s going on under the hood is that all those Wolfram Language constructs are getting compiled into SQL, or, more accurately, the specific dialect of SQL that’s suitable for the particular database you’re using (we currently support SQLite, MySQL, PostgreSQL and MS-SQL, with support for OracleSQL coming soon). When we do the compilation, we’re automatically checking types, to make sure you get a meaningful query. Even fairly simple Wolfram Language specifications can end up turning into many lines of SQL. For example,

EntityFunction
&#10005

EntityFunction[c, 
  c["employees"]["firstName"] <> " " <> c["employees"]["lastName"] <> 
   " is the rep for " <> c["customerName"] <> ". Their manager is " <>
    c["employees"]["employees-reportsTo"]["firstName"] <> " " <> 
   c["employees"]["employees-reportsTo"]["lastName"] <> "."][
 Entity["customers", 103]]

would produce the following intermediate SQL (here for querying the SQLite database):

SQL

The database integration system we have in Version 12.0 is pretty sophisticated—and we’ve been working on it for quite a few years. It’s an important step forward in allowing the Wolfram Language to directly handle a new level of “bigness” in big data—and to let the Wolfram Language directly do data science on terabyte-sized datasets and beyond. Like finding which street-like entities in the world have “Wolfram” in their name:

FilteredEntityClass
&#10005

FilteredEntityClass["planet_osm_line", 
  EntityFunction[s, StringContainsQ[s["name"], "Wolfram"]]]["name"]

RDF, SPARQL and All That

What is the best way to represent knowledge about the world? It’s an issue that’s been debated by philosophers (and others) since antiquity. Sometimes people said logic was the key. Sometimes mathematics. Sometimes relational databases. But now we at least know one solid foundation (or at least, I’m pretty sure we do): everything can be represented by computation. This is a powerful idea—and in a sense that’s what makes everything we do with Wolfram Language possible.

But are there subsets of general computation that are useful for representing at least certain kinds of knowledge? One that we use extensively in the Wolfram Knowledgebase is the notion of entities (“New York City”), properties (“population”) and their values (“8.6 million people”). Of course such triples don’t represent all knowledge in the world (“what will the position of Mars be tomorrow?”). But they’re a decent start when it comes to certain kinds of “static” knowledge about distinct things.

So how can one formalize this kind of knowledge representation? One answer is through graph databases. And in Version 12.0—in alignment with many “semantic web” projects—we’re supporting graph databases using RDF, and queries against them using SPARQL. In RDF the central object is an IRI (“Internationalized Resource Identifier”), that can represent an entity or a property. A “triplestore” then consists of a collection of triples (“subject”, “predicate”, “object”), with each element in each triple being an IRI (or a literal, such as a number). The whole object can then be thought of as a graph database or graph store, or, mathematically, a hypergraph. (It’s a hypergraph because the predicate “edges” can also be vertices elsewhere.)

You can build your own RDFStore much like you build an EntityStore—and in fact you can query any Wolfram Language EntityStore using SPARQL just like you query an RDFStore. And since the entity-property part of the Wolfram Knowledgebase can be treated as an entity store, you can also query this. So here, finally, is an example. The country-city list Entity["Country"], Entity["City"]} in effect represents an RDF store. Then SPARQLSelect is an operator acting on this store. What it does is to try to find a triple that matches what you’re asking for, with a particular value for the “SPARQL variable” x:

Needs["GraphStore`"]
&#10005

Needs["GraphStore`"]

SPARQLSelect
&#10005

SPARQLSelect[RDFTriple[Entity["Country", "USA"], EntityProperty["Country", "CapitalCity"], SPARQLVariable["x"]]][{Entity["Country"], Entity["City"]}]

Of course, there’s also a much simpler way to do this in the Wolfram Language:

Entity
&#10005

Entity["Country", "USA"][EntityProperty["Country", "CapitalCity"]]

But with SPARQL you can do much more exotic things—like ask what properties relate the US to Mexico:

SPARQLSelect
&#10005

SPARQLSelect[RDFTriple[Entity["Country", "USA"],SPARQLVariable["x"],Entity["Country", "Mexico"]]][{Entity["Country"]}]

Or whether there is a path based on the bordering country relation from Portugal to Germany:

SPARQLAsk
&#10005

SPARQLAsk[
  SPARQLPropertyPath[
   Entity["Country", 
    "Portugal"], {EntityProperty["Country", "BorderingCountries"] ..},
    Entity["Country", "Germany"]]][Entity["Country"]]

In principle you can just write a SPARQL query as a string (a bit like you can write an SQL string). But what we’ve done in Version 12.0 is introduce a symbolic representation of SPARQL that allows computation on the representation itself, making it easy, for example, to automatically generate complex SPARQL queries. (And it’s particularly important to do this because, on their own, practical SPARQL queries have a habit of getting extremely long and ponderous.)

OK, but are there RDF stores out in the wild? It’s been a long-running hope that a large part of the web will somehow eventually be tagged enough to “become semantic” and in effect be a giant RDF store. It’d be great if this happened, but so far it definitely hasn’t. Still, there are a few public RDF stores out there, and also some RDF stores within organizations, and with our new capabilities in Version 12.0 we’re in a unique position to do interesting things with them.

Numerical Optimization

An incredibly common form of problem in industrial applications of mathematics is: “What configuration minimizes cost (or maximizes payoff) if certain constraints have to be satisfied?” More than half a century ago, the so-called simplex algorithm was invented for solving linear versions of this kind of problem, in which both the objective function (cost, payoff) and the constraints are linear functions of the variables in the problem. By the 1980s much more efficient (“interior point”) methods had been invented—and we’ve had these for doing “linear programming” in the Wolfram Language for a long time.

But what about nonlinear problems? Well, in the general case, one can use functions like NMinimize. And they do a state-of-the-art job. But it’s a hard problem. However, some years ago, it became clear that even among nonlinear optimization problems, there’s a class of so-called convex optimization problems that can actually be solved almost as efficiently as linear ones. (“Convex” means that both the objective and the constraints involve only convex functions—so that nothing can “wiggle” as one approaches an extremum, and there can’t be any local minima that aren’t global minima.)

In Version 12.0, we’ve now got strong implementations for all the various standard classes of convex optimization. Here’s a simple case, involving minimizing a quadratic form with a couple of linear constraints:

QuadraticOptimization
&#10005

QuadraticOptimization[2x^2+20y^2+6x y+5x,{-x+y>=2,y>=0},{x,y}]

NMinimize could already do this particular problem in Version 11.3:

NMinimize
&#10005

NMinimize[{2x^2+20y^2+6x y+5x,{-x+y>=2,y>=0}},{x,y}]

But if one had more variables, the old NMinimize would quickly bog down. In Version 12.0, however, QuadraticOptimization will continue to work just fine, up to more than 100,000 variables with more than 100,000 constraints (so long as they’re fairly sparse).

In Version 12.0 we’ve got “raw convex optimization” functions like SemidefiniteOptimization (that handles linear matrix inequalities) and ConicOptimization (that handles linear vector inequalities). But functions like NMinimize and FindMinimum will also automatically recognize when a problem can be solved efficiently by being transformed to a convex optimization form.

How does one set up convex optimization problems? Larger ones involve constraints on whole vectors or matrices of variables. And in Version 12.0 we now have functions like VectorGreaterEqual (input as ) that can immediately represent these.

Nonlinear Finite Element Analysis

Partial differential equations are hard, and we’ve been working on more and more sophisticated and general ways to handle them for 30 years. We first introduced NDSolve (for ODEs) in Version 2, back in 1991. We had our first (1+1-dimensional) numerical PDEs by the mid-1990s. In 2003 we introduced our powerful, modular framework for handling numerical differential equations. But in terms of PDEs we were still basically only dealing with simple, rectangular regions. To go beyond that required building our whole computational geometry system, which we introduced in Version 10. And with this, we released our first finite element PDE solvers. In Version 11, we then generalized to eigen problems.

Now, in Version 12, we’re introducing another major generalization: nonlinear finite element analysis. Finite element analysis involves decomposing regions into little discrete triangles, tetrahedra, etc.—on which the original PDE can be approximated by a large number of coupled equations. When the original PDE is linear, these equations will also be linear—and that’s the typical case people consider when they talk about “finite element analysis”.

But there are many PDEs of practical importance that aren’t linear—and to tackle these one needs nonlinear finite element analysis, which is what we now have in Version 12.0.

As an example, here’s what it takes to solve the nastily nonlinear PDE that describes the height of a 2D minimal surface (say, an idealized soap film), here over an annulus, with (Dirichlet) boundary conditions that make it wiggle sinusoidally at the edges (as if the soap film were suspended from wires):

NDSolveValue
&#10005

NDSolveValue[{Inactive[Div][(1/Sqrt[1 + \!\(
\*SubscriptBox[\(\[Del]\), \({x, y}\)]\(u[x, y]\)\).\!\(
\*SubscriptBox[\(\[Del]\), \({x, y}\)]\(u[x, y]\)\)]) Inactive[Grad][
      u[x, y], {x, y}], {x, y}] == 0, 
  DirichletCondition[u[x, y] == Sin[2 \[Pi] (x + y)], True]}, u, {x, 
   y} \[Element] Region[Annulus[{0, 0}, {0.3, 1}]]] 

On my computer it takes just a quarter of a second to solve this equation, and get an interpolating function. Here’s a plot of the interpolating function representing the solution:

Plot3D
&#10005

Plot3D[%[x, y], {x, y} \[Element] 
  Region[Annulus[{0, 0}, {0.3, 1}]] , MeshFunctions -> {#3 &}]

New, Sophisticated Compiler

We’ve put a lot of engineering into optimizing the execution of Wolfram Language programs over the years. Already in 1989 we started automatically compiling simple machine-precision numerical computations to instructions for an efficient virtual machine (and, as it happens, I wrote the original code for this). Over the years, we’ve extended the capabilities of this compiler, but it’s always been limited to fairly simple programs.

In Version 12.0 we’re taking a major step forward, and we’re releasing the first version of a new, much more powerful compiler that we’ve been working on for several years. This compiler is both able to handle a much broader range of programs (including complex functional constructs and elaborate control flows), and it’s also compiling not to a virtual machine but instead directly to optimized native machine code.

In Version 12.0 we still consider the new compiler experimental. But it’s advancing rapidly, and it’s going to have a dramatic effect on the efficiency of lots of things in the Wolfram Language. In Version 12.0, we’re just exposing a “kit form” of the new compiler, with specific compilation functions. But we’ll progressively be making the compiler operate more and more automatically—figuring out with machine learning and other methods when it’s worth taking the time to do what level of compilation.

At a technical level, the new Version 12.0 compiler is based on LLVM, and works by generating LLVM code—linking in the same low-level runtime library that the Wolfram Language kernel itself uses, and calling back to the full Wolfram Language kernel for functionality that isn’t in the runtime library.

Here’s the basic way one compiles a pure function in the current version of the new compiler:

FunctionCompile
&#10005

FunctionCompile[Function[Typed[x,"Integer64"],x^2]]

The resulting compiled code function works just like the original function, though faster:

CompiledCodeFunction

%[12]

A big part of what lets FunctionCompile produce a faster function is that you’re telling it to make assumptions about the type of argument it’s going to get. We’re supporting lots of basic types (like "Integer32" and "Real64"). But when you use FunctionCompile, you’re committing to particular argument types, so much more streamlined code can be produced.

A lot of the sophistication of the new compiler is associated with inferring what types of data will be generated in the execution of a program. (There are lots of graph theoretic and other algorithms involved, and needless to say, all the metaprogramming for the compiler is done with the Wolfram Language.)

Here’s an example that involves a bit of type inference (the type of fib is deduced to be "Integer64""Integer64": an integer function returning an integer):

FunctionCompile
&#10005

FunctionCompile[Function[{Typed[n,"Integer64"]},Module[{fib},fib=Function[{x},If[x<=1,1,fib[x-1]+fib[x-2]]];
fib[n]]]]

On my computer cf[25] runs about 300 times faster than the uncompiled function. (Of course, the compiled version fails when its output is no longer of type "Integer64", but the standard Wolfram Language version continues to work just fine.)

Already the compiler can handle hundreds of Wolfram Language programming primitives, appropriately tracking what types are produced—and generating code that directly implements these primitives. Sometimes, however, one will want to use sophisticated functions in the Wolfram Language for which it doesn’t make sense to generate one’s own compiled code—and where what one really wants to do is just to call into the Wolfram Language kernel for these functions. In Version 12.0 KernelFunction lets one do this:

FunctionCompile
&#10005

FunctionCompile[Function[Typed[x,"Real64"],Typed[KernelFunction[AiryAi],{"Real64"}->"Real64"][x]]]

OK, but let’s say one’s got a compiled code function. What can one do with it? Well, first of all one can just run it inside the Wolfram Language. One can store it too, and run it later. Any particular compilation is done for a specific processor architecture (e.g. 64-bit x86). But CompiledCodeFunction automatically keeps enough information to do additional compilation for a different architecture if it’s needed.

But given a CompiledCodeFunction, one of the interesting new possibilities is that one can directly generate code that can be run even outside the Wolfram Language environment. (Our old compiler had the CCodeGenerate package which provided slightly similar capabilities in simple cases—though even then relies on an elaborate toolchain of C compilers etc.)

Here’s how one can export raw LLVM code (notice that things like tail recursion optimization automatically get done—and notice also the symbolic function and compiler options at the end):

FunctionCompileExportString
&#10005

FunctionCompileExportString[Function[{Typed[n,"Integer64"]},Module[{fib},fib=Function[{x},If[x<=1,1,fib[x-1]+fib[x-2]]];
fib[n]]]]

If one uses FunctionCompileExportLibrary, then one gets a library file—.dylib on Mac, .dll on Windows and .so on Linux. One can use this in the Wolfram Language by doing LibraryFunctionLoad. But one can also use it in an external program.

One of the main things that determines the generality of the new compiler is the richness of its type system. Right now the compiler supports 14 atomic types (such as "Boolean", "Integer8", "Complex64", etc.). It also supports type constructors like "PackedArray"—so that, for example, TypeSpecifier["PackedArray"]["Real64", 2] corresponds to a rank-2 packed array of 64-bit reals.

In the internal implementation of the Wolfram Language (which, by the way, is itself mostly in Wolfram Language) we’ve had an optimized way to store arrays for a long time. In Version 12.0 we’re exposing it as NumericArray. Unlike ordinary Wolfram Language constructs, you have to tell NumericArray in detail how it should store data. But then it works in a nice, optimized way:

NumericArray
&#10005

NumericArray[Range[10000], "UnsignedInteger16"]

ByteCount
&#10005

ByteCount[%]

ByteCount
&#10005

ByteCount[Range[10000]]

Calling Python & Other Languages

In Version 11.2 we introduced ExternalEvaluate, that lets you do computations in languages like Python and JavaScript from within the Wolfram Language (in Python, “^” means BitXor):

ExternalEvaluate
&#10005

ExternalEvaluate["Python", "23424^2542"]

In Version 11.3, we introduced external language cells, to make it easy to enter external-language programs or other input directly in a notebook:

ExternalEvaluate["Python", "23424^2542"]

ExternalEvaluate["Python", "23424^2542"]

In Version 12.0, we’re tightening the integration. For example, inside an external language string, you can use <* ... *> to give Wolfram Language code to evaluate:

ExternalEvaluate
&#10005

ExternalEvaluate["Python","<* Prime[1000] *> + 10"]

This works in external language cells too:

ExternalEvaluate

ExternalEvaluate["Python", "<* Prime[1000] *> + 10"]

Of course, Python is not Wolfram Language, so many things don’t work:

ExternalEvaluate
&#10005

ExternalEvaluate["Python","2+ <* Range[10] *>"]

But ExternalEvaluate can at least return many types of data from Python, including lists (as List), dictionaries (as Association), images (as Image), dates (as DateObject), NumPy arrays (as NumericArray) and pandas datasets (as TimeSeries, Dataset, etc.). (ExternalEvaluate can also return ExternalObject that’s basically a handle to an object that you can send back to Python.)

You can also directly use external functions (the slightly bizarrely named ord is basically the Python analog of ToCharacterCode):

ExternalFunction
&#10005

ExternalFunction["Python", "ord"]["a"]

And here’s a Python pure function, represented symbolically in the Wolfram Language:

ExternalFunction
&#10005

ExternalFunction["Python", "lambda x:x+1"]

ExternalFunction
&#10005

%[100]

Calling the Wolfram Language from Python & Other Places

How should one access the Wolfram Language? There are many ways. One can use it directly in a notebook. One can call APIs that execute it in the cloud. Or one can use WolframScript in a command-line shell. WolframScript can run either against a local Wolfram Engine, or against a Wolfram Engine in the cloud. It lets you directly give code to execute:

WolframScript

And it lets you do things like define functions, for example with code in a file:

WolframScript

WolframScript

Along with the release of Version 12.0, we’re also releasing our first new Wolfram Language Client Library—for Python. The basic idea of this library is to make it easy for Python programs to call the Wolfram Language. (It’s worth pointing out that we’ve effectively had a C Language Client Library for no less than 30 years—through what’s now called WSTP.)

The way a Language Client Library works is different for different languages. For Python—as an interpreted language (that was actually historically informed by early Wolfram Language)—it’s particularly simple. After you set up the library, and start a session (locally or in the cloud), you can then just evaluate Wolfram Language code and get the results back in Python:

Python external function

You can also directly access Wolfram Language functions (as a kind of inverse of ExternalFunction):

Python external function

And you can directly interact with things like pandas structures, NumPy arrays, etc. In fact, you can in effect just treat the whole of the Wolfram Language like a giant library that can be accessed from Python. Or, of course, you can just use the nice, integrated Wolfram Language directly, perhaps creating external APIs if you need them.

More for the Wolfram “Super Shell”

One feature of using the Wolfram Language is that it lets you get away from having to think about the details of your computer system, and about things like files and processes. But sometimes one wants to work at a systems level. And for fairly simple operations, one can just use an operating system GUI. But what about for more complicated things? In the past I usually found myself using the Unix shell. But for a long time now, I’ve instead used Wolfram Language.

It’s certainly very convenient to have everything in a notebook, and it’s been great to be able to programmatically use functions like FileNames (ls), FindList (grep), SystemProcessData (ps), RemoteRunProcess (ssh) and FileSystemScan. But in Version 12.0 we’re adding a bunch of additional functions to support using the Wolfram Language as a “super shell”.

There’s RemoteFile for symbolically representing a remote file (with authentication if needed)— that you can immediately use in functions like CopyFile. There’s FileConvert for directly converting files between different formats.

And if you really want to dive deep, here’s how you’d trace all the packets on ports 80 and 443 used in reading from wolfram.com:

NetworkPacketTrace
&#10005

NetworkPacketTrace[URLRead["wolfram.com"], {80, 443}]

Puppeting a Web Browser

Within the Wolfram Language, it’s been easy for a long time to interact with web servers, using functions like URLExecute and HTTPRequest, as well as $Cookies, etc. But in Version 12.0 we’re adding something new: the ability of the Wolfram Language to control a web browser, and programmatically make it do what we want. The most immediate thing we can do is just to get an image of what a website looks like to a web browser:

WebImage
&#10005

WebImage["https://www.wolfram.com"]

The result is an image that we can compute with:

EdgeDetect
&#10005

EdgeDetect[%]

To do something more detailed, we have to start a browser session (we currently support Firefox and Chrome):

session = StartWebSessionsession = StartWebSession
&#10005

session = StartWebSession["Chrome"]

Immediately a blank browser window appears on our screen. Now we can use WebExecute to open a webpage:

WebExecuteWebExecute
&#10005

WebExecute["OpenPage" -> "http://www.wolfram.com"]

Now that we’ve opened the page, there are lots of commands we can run. This clicks the first hyperlink containing the text “Programming Lab”:

WebExecuteWebExecute
&#10005

WebExecute[
 "ClickElement" -> "PartialHyperlinkText" -> "Programming Lab"]

This returns the title of the page we’ve reached:

WebExecute
&#10005

WebExecute["PageTitle"]

You can type into fields, run JavaScript, and basically do programmatically anything you could do by hand with a web browser. Needless to say, we’ve been using a version of this technology for years inside our company to test all our various websites and web services. But now, in Version 12.0, we’re making a streamlined version generally available.

Standalone Microcontrollers

For every general-purpose computer in the world today, there are probably 10 times as many microcontrollers—running specific computations without any general operating system. A microcontroller might cost a few cents to a few dollars, and in something like a mid-range car, there might be 30 of them.

In Version 12.0 we’re introducing a Microcontroller Kit for the Wolfram Language, that lets you give symbolic specifications from which it automatically generates and deploys code to run autonomously in microcontrollers. In the typical setup, a microcontroller is continuously doing computations on data coming in from sensors, and in real time putting out signals to actuators. The most common types of computations are effectively ones in control theory and signal processing.

We’ve had extensive support for doing control theory and signal processing directly in the Wolfram Language for a long time. But now what’s possible with the Microcontroller Kit is to take what’s specified in the language and download it as embedded code in a standalone microcontroller that can be deployed anywhere (in devices, IoT, appliances, etc.).

As an example, here’s how one can generate a symbolic representation of an analog signal-processing filter:

ButterworthFilterModel
&#10005

ButterworthFilterModel[{3,2}]

We can use this filter directly in the Wolfram Language—say using RecurrenceFilter to apply it to an audio signal. We can also do things like plot its frequency response:

BodePlot
&#10005

BodePlot[%]

To deploy the filter in a microcontroller, we first have to derive from this continuous-time representation a discrete-time approximation that can be run in a tight loop (here, every 0.1 seconds) in the microcontroller:

ToDiscreteTimeModel
&#10005

filter=ToDiscreteTimeModel[ButterworthFilterModel[{3,2}],0.1]//Chop

Now we’re ready to use the Microcontroller Kit to actually deploy this to a microcontroller. The kit supports more than a hundred different types of microcontrollers. Here’s how we could deploy the filter to an Arduino Uno that we have connected to a serial port on our computer:

Needs["MicrocontrollerKit`"]
&#10005

Needs["MicrocontrollerKit`"]

MicrocontrollerEmbedCode
&#10005

MicrocontrollerEmbedCode[filter,<|"Target"->"ArduinoUno","Inputs"->"Serial","Outputs"->"Serial"|>,"/dev/cu.usbmodem141101"]

MicrocontrollerEmbedCode works by generating appropriate C-like source code, compiling it for the microcontroller architecture you want, then actually deploying it to the microcontroller through its so-called programmer. Here’s the actual source code that was generated in this particular case:

MicrocontrollerEmbedCode title=
&#10005

%["SourceCode"]

So now we have a thing like this that runs our Butterworth filter, that we can use anywhere:

Running our Butterworth filter

If we want to check what it’s doing, we can always connect it back into the Wolfram Language using DeviceOpen to open its serial port, and read and write from it.

Linking to the Unity Universe

What’s the relation between the Wolfram Language and video games? Over the years, the Wolfram Language has been used behind the scenes in many aspects of game development (simulating strategies, creating geometries, analyzing outcomes, etc.). But for some time now we’ve been working on a closer link between Wolfram Language and the Unity game environment, and in Version 12.0 we’re releasing a first version of this link.

The basic scheme is to have Unity running alongside the Wolfram Language, then to set up two-way communication, allowing both objects and commands to be exchanged. The under-the-hood plumbing is quite complex, but the result is a nice merger of the strengths of Wolfram Language and Unity.

This sets up the link, then starts a new project in Unity:

Needs["UnityLink`"]
&#10005

Needs["UnityLink`"]

UnityOpen
&#10005

UnityOpen["NewProject"]

Now create some complex shape:

RevolutionPlot3D
&#10005

RevolutionPlot3D[{Sin[t] + Sin[5 t]/10, Cos[t] + Cos[5 t]/10}, {t, 0, 
  Pi}, Sequence[
 RegionFunction -> (Sin[5 (#4 + #5)] > 0& ), Boxed -> False, 
  Axes -> None, PlotTheme -> "ThickSurface"]]

Then it takes just one command to put this into the Unity game as an object called "thingoid":

Thingoid
&#10005

CreateUnityGameObject["thingoid", CloudGet["https://wolfr.am/COrZtVvA"], 
  Properties -> {
     "SharedMaterial" -> UnityLink`CreateUnityMaterial[Orange]}]

Within the Wolfram Language there’s a symbolic representation of the object, and UnityLink now provides hundreds of functions for manipulating such objects, always maintaining versions both in Unity and in the Wolfram Language.

It’s very powerful that one can take things from the Wolfram Language and immediately put them into Unity—whether they’re geometry, images, audio, geo terrain, molecular structures, 3D anatomy, or whatever. It’s also very powerful that such things can then be manipulated within the Unity game, either through things like game physics, or by user action. (Eventually, one can expect to have Manipulate-like functionality, in which the controls aren’t just sliders and things, but complex pieces of gameplay.)

We’ve done experiments with putting Wolfram Language–generated content into virtual reality since the early 1990s. But in modern times Unity has become something of a de facto standard for setting up VR/AR environments—and with UnityLink it’s now straightforward to routinely put things from Wolfram Language into any modern XR environment.

One can use the Wolfram Language to prepare material for Unity games, but within a Unity game UnityLink also basically lets one just insert Wolfram Language code that can be executed during a game either on a local machine or through an API in the Wolfram Cloud. And, among other things, this makes it straightforward to put hooks into a game so the game can send “telemetry” (say to the Wolfram Data Drop) for analysis in the Wolfram Language. (It’s also possible to script the playing of the game—which is, for example, very useful for game testing.)

Writing games is a complex matter. But UnityLink provides an interesting new approach that should make it easier to prototype all sorts of games, and to learn the ideas of game development. One reason for this is that it effectively lets one script a game at a higher level by using symbolic constructs in the Wolfram Language. But another reason is that it lets the development process be done incrementally in a notebook, and explained and documented every step of the way. For example, here’s what amounts to a computational essay describing the development of a “piano game”:

UnityLink piano game

UnityLink isn’t a simple thing: it contains more than 600 functions. But with those functions it’s possible to access pretty much all the capabilities of Unity, and to set up pretty much any imaginable game.

Simulated Environments for Machine Learning

For something like reinforcement learning it’s essential to have a manipulable external environment in the loop when one’s doing machine learning. Well, ServiceExecute lets you call APIs (what’s the effect of posting that tweet, or making that trade?), and DeviceExecute lets you actuate actual devices (turn the robot left) and get data from sensors (did the robot fall over?).

But for many purposes what one instead wants is to have a simulated external environment. And in a way, just the pure Wolfram Language already to some extent does that, for example providing access to a rich “computational universe” full of modifiable programs and equations (cellular automata, differential equations, …). And, yes, the things in that computational universe can be informed by the real world—say with the realistic properties of oceans, or chemicals or mountains.

But what about environments that are more like the ones we modern humans typically learn in—full of built engineering structures and so on? Conveniently enough, SystemModel gives access to lots of realistic engineering systems. And through UnityLink we can expect to have access to rich game-based simulations of the world.

But as a first step, in Version 12.0 we’re setting up connections to some simple games—in particular from the OpenAI “gym”. The interface is much as it would be for interacting with the real world, with the game accessed like a “device” (after appropriate sometimes-“open-source-painful” installation):

env = DeviceOpen
&#10005

env = DeviceOpen["OpenAIGym", "MontezumaRevenge-v0"]

We can read the state of the game:

DeviceRead
&#10005

DeviceRead[env]

And we can show it as an image:

ObservedState
&#10005

Image[DeviceRead[env]["ObservedState"]]

With a bit more effort, we can take 100 random actions in the game (always checking that we didn’t “die”), then show a feature space plot of the observed states of the game:

FeatureSpacePlot
&#10005

FeatureSpacePlot[
 Table[If[DeviceRead[env]["Ended"], Return[], 
   Image[DeviceExecute[env, "Step", 
      DeviceExecute[env, "RandomAction"]]["ObservedState"]]], 100]]

Blockchain (and CryptoKitty) Computation

In Version 11.3 we began our first connection to the blockchain. Version 12.0 adds a lot of new features and capabilities, perhaps most notably the ability to write to public blockchains, as well as read from them. (We also have our own Wolfram Blockchain for Wolfram Cloud users.) We’re currently supporting Bitcoin, Ethereum and ARK blockchains, both their mainnets and testnets (and, yes, we have our own nodes connecting directly to these blockchains).

In Version 11.3 we allowed raw reading of transactions from blockchains. In Version 12.0 we’ve added a layer of analysis, so that, for example, you can ask for a summary of “CK” tokens (AKA CryptoKitties) on the Ethereum blockchain:

BlockchainTokenData["CK"]
&#10005

BlockchainTokenData["CK"]

It’s quick to look at all token transactions in history, and make a word cloud of how active different tokens have been:

WordCloud
&#10005

WordCloud[SortBy[BlockchainTokenData[All,{"Name","TransfersCount"}],Last]]

But what about doing our own transaction? Let’s say we want to use a Bitcoin ATM (like the one that, bizarrely, exists at a bagel store near me) to transfer cash to a Bitcoin address. Well, first we create our crypto keys (and we need to make sure we remember our private key!):

keys=GenerateAsymmetricKeyPair["Bitcoin"]
&#10005

keys=GenerateAsymmetricKeyPair["Bitcoin"]

Next, we have to take our public key and generate a Bitcoin address from it:

BlockchainKeyEncode
&#10005

BlockchainKeyEncode[keys["PublicKey"],"Address",BlockchainBase->"Bitcoin"]

Make a QR code from that and you’re ready to go to the ATM:

BarcodeImage
&#10005

BarcodeImage[%,"QR"]

But what if we want to write to the blockchain ourselves? Here we’ll use the Bitcoin testnet (so we’re not spending real money). This shows an output from a transaction we did before—that includes 0.0002 bitcoin (i.e. 20,000 satoshi):

BlockchainBase
&#10005

$BlockchainBase={"Bitcoin", "Testnet"};

BlockchainTransactionData
&#10005

First[BlockchainTransactionData["17a422eebfbf9cdee19b600740597bafea45cc4c703c67afcc8fb889f4cf7f28","Outputs"]]

Now we can set up a transaction which takes this output, and, for example, sends 8000 satoshi to each of two addresses (that we defined just like for the ATM transaction):

BlockchainTransaction
&#10005

BlockchainTransaction[<|
  "Inputs" -> {<|
     "TransactionID" -> 
      "17a422eebfbf9cdee19b600740597bafea45cc4c703c67afcc8fb889f4cf7f28", "Index" -> 0|>}, 
  "Outputs" -> {<|"Amount" -> Quantity[8000, "Satoshi"], 
     "Address" -> "munDTMqa9V9Uhi3P21FpkY8UfYzvQqpmoQ"|>, <|
     "Amount" -> Quantity[8000, "Satoshi"], 
     "Address" -> "mo9QWLSJ1g1ENrTkhK9SSyw7cYJfJLU8QH"|>}, 
  "BlockchainBase" -> {"Bitcoin", "Testnet"}|>]

OK, so now we’ve got a blockchain transaction object—that would offer a fee (shown in red because it’s “actual money” you’ll spend) of all the leftover cryptocurrency (here 4000 satoshi) to a miner willing to put the transaction in the blockchain. But before we can submit this transaction (and “spend the money”) we have to sign it with our private key:

BlockchainTransactionSign
&#10005

BlockchainTransactionSign[%, keys["PrivateKey"]]

Finally, we just apply BlockchainTransactionSubmit and we’ve submitted our transaction to be put on the blockchain:

BlockchainTransactionSubmit
&#10005

BlockchainTransactionSubmit[%]

Here’s its transaction ID:

txid
&#10005

txid=%["TransactionID"]

If we immediately ask about this transaction, we’ll get a message saying it isn’t in the blockchain:

BlockchainTransactionDataBlockchainTransactionData
&#10005

BlockchainTransactionData[txid]

But after we wait a few minutes, there it is—and it’ll soon spread to every copy of the Bitcoin testnet blockchain:

BlockchainTransactionDataBlockchainTransactionData
&#10005

BlockchainTransactionData[txid]

If you’re prepared to spend real money, you can use exactly the same functions to do a transaction on a main net. You can also do things like buy CryptoKitties. Functions like BlockchainContractValue can be used for any (for now, only Ethereum) smart contract, and are set up to immediately understand things like ERC-20 and ERC-721 tokens.

And Ordinary Crypto as Well

Dealing with blockchains involves lots of cryptography, some of which is new in Version 12.0 (notably, handling elliptic curves). But in Version 12.0 we’re also extending our non-blockchain cryptographic functions. For example, we’ve now got functions for directly dealing with digital signatures. This creates a digital signature using the private key from above:

message
&#10005

message="This is my genuine message";

signature
&#10005

signature=GenerateDigitalSignature[message,keys["PrivateKey"]]

Now anyone can verify the message using the corresponding public key:

VerifyDigitalSignature
&#10005

VerifyDigitalSignature[{message,signature},keys["PublicKey"]]

In Version 12.0, we added several new types of hashes for the Hash function, particularly to support various cryptocurrencies. We also added ways to generate and verify derived keys. Start from any password, and GenerateDerivedKey will “puff it out” to something longer (to be more secure you should add “salt”):

GenerateDerivedKey["meow"]
&#10005

GenerateDerivedKey["meow"]

Here’s a version of the derived key, suitable for use in various authentication schemes:

GenerateDerivedKey["meow"]["PHCString"]
&#10005

GenerateDerivedKey["meow"]["PHCString"]

Connecting to Financial Data Feeds

The Wolfram Knowledgebase contains all sorts of financial data. Typically there’s a financial entity (like a stock), then there’s a property (like price). Here’s the complete daily history of Apple’s stock price (it’s very impressive that it looks best on a log scale):

DateListLogPlot
&#10005

DateListLogPlot[Entity["Financial", "NASDAQ:AAPL"][Dated["Price",All]]]

But while the financial data in the Wolfram Knowledgebase, and standardly available in the Wolfram Language, is continuously updated, it’s not real time (mostly it’s 15-minute delayed), and it doesn’t have all the detail that many financial traders use. For serious finance use, however, we’ve developed Wolfram Finance Platform. And now, in Version 12.0, it’s got direct access to Bloomberg and Reuters financial data feeds.

The way we architect the Wolfram Language, the framework for the connections to Bloomberg and Reuters is always available in the language—but it’s only activated if you have Wolfram Finance Platform, as well as the appropriate Bloomberg or Reuters subscriptions. But assuming you have these, here’s what it looks like to connect to the Bloomberg Terminal service:

ServiceConnect["BloombergTerminal"]
&#10005

ServiceConnect["BloombergTerminal"]

All the financial instruments handled by the Bloomberg Terminal now become available as entities in the Wolfram Language:

Entity["BloombergTerminal","AAPL US Equity"]
&#10005

Entity["BloombergTerminal","AAPL US Equity"]

Now we can ask for properties of this entity:

Entity["BloombergTerminal","AAPL US Equity"]["PX_LAST"]
&#10005

%["PX_LAST"]

Altogether there are more than 60,000 properties accessible from the Bloomberg Terminal:

Length[EntityProperties["BloombergTerminal"]]
&#10005

Length[EntityProperties["BloombergTerminal"]]

Here are 5 random examples (yes, they’re pretty detailed; those are Bloomberg names, not ours):

RandomSample
&#10005

RandomSample[EntityProperties["BloombergTerminal"],5]

We support the Bloomberg Terminal service, the Bloomberg Data License service, and the Reuters Elektron service. One sophisticated thing one can now do is to set up a continuous task to asynchronously receive data, and call a “handler function” every time a new piece of data comes in:

ServiceSubmit
&#10005

ServiceSubmit[
ContinuousTask[ServiceRequest[ServiceConnect["Reuters"],
"MarketData",{"Instrument"-> "AAPL.O","TriggerFields"->{"BID","ASK"}}]],
HandlerFunctions-><|"MarketDataEvents"->(action[#Result]&)|>]

Software Engineering & Platform Updates

I’ve talked about lots of new functions and new functionality in the Wolfram Language. But what about the underlying infrastructure of the Wolfram Language? Well, we’ve been working hard on that too. For example, between Version 11.3 and Version 12.0 we’ve managed to fix nearly 8000 reported bugs. We’ve also made lots of things faster and more robust. And in general we’ve been tightening the software engineering of the system, for example reducing the initial download size by nearly 10% (despite all the functionality that’s been added). (We’ve also done things like improve the predictive prefetching of knowledgebase elements from the cloud—so when you need similar data it’s more likely to be already cached on your computer.)

It’s a longstanding feature of the computing landscape that operating systems are continually getting updated—and to take advantage of their latest features, applications have to get updated too. We’ve been working for several years on a major update to our Mac notebook interface—which is finally ready in Version 12.0. As part of the update, we’ve rewritten and restructured large amounts of code that have been developed and polished over more than 20 years, but the result is that in Version 12.0, everything about our system on the Mac is fully 64-bit, and makes use of the latest Cocoa APIs. This means that the notebook front end is significantly faster—and can also go beyond the previous 2 GB memory limit.

There’s also a platform update on Linux, where now the notebook interface fully supports Qt 5, which allows all rendering operations to take place “headlessly”, without any X server—greatly streamlining deployment of the Wolfram Engine in the cloud. (Version 12.0 doesn’t yet have high-dpi support for Windows, but that’s coming very soon.)

The development of the Wolfram Cloud is in some ways separate from the development of the Wolfram Language, and Wolfram Desktop applications (though for internal compatibility we’re releasing Version 12.0 at the same time in both environments). But in the past year since Version 11.3 was released, there’s been dramatic progress in the Wolfram Cloud.

Especially notable are the advances in cloud notebooks—supporting more interface elements (including some, like embedded websites and videos, that aren’t even yet available in desktop notebooks), as well as greatly increased robustness and speed. (Making our whole notebook interface work in a web browser is no small feat of software engineering, and in Version 12.0 there are some pretty sophisticated strategies for things like maintaining consistent fast-to-load caches, along with full symbolic DOM representations.)

In Version 12.0 there’s now just a simple menu item (File > Publish to Cloud …) to publish any notebook to the cloud. And once the notebook is published, anyone in the world can interact with it—as well as make their own copy so they can edit it.

It’s interesting to see how broadly the cloud has entered what can be done in the Wolfram Language. In addition to all the seamless integration of the cloud knowledgebase, and the ability to reach out to things like blockchains, there are also conveniences like Send To sending any notebook through email, using the cloud if there’s no direct email server connection available.

And a Lot Else…

Even though this has been a long piece, it’s not even close to telling the whole story of what’s new in Version 12.0. Along with the rest of our team, I’ve been working very hard on Version 12.0 for a long time now—but it’s still exciting to see just how much is actually in it.

But what’s critical (and a lot of work to achieve!) is that everything we’ve added is carefully designed to fit coherently with what’s already there. From the very first version more than 30 years ago of what’s now the Wolfram Language, we’ve been following the same core principles—and this is part of what’s allowed us to so dramatically grow the system while maintaining long-term compatibility.

It’s always difficult to decide exactly what to prioritize developing for each new version, but I’m very pleased with the choices we made for Version 12.0. I’ve given many talks over the past year, and I’ve been very struck with how often I’ve been able to say about things that come up: “Well, it so happens that that’s going to be part of Version 12.0!”

I’ve personally been using internal preliminary builds of Version 12.0 for nearly a year, and I’ve come to take for granted many of its new capabilities—and to use and enjoy them a lot. So it’s a great pleasure that today we have the final Version 12.0—with all these new capabilities officially in it, ready to be used by anyone and everyone…

9 comments

  1.  

    Finally! ^_^

    Been asking about v12.0 for the entire last year. ;)

    Guess now I can start asking “when will ‘upgrades’ be ‘on sale’ next?”

    Ready to drop a little coin on v12.0. Should be a significant upgrade over v9.0… ;) Can’t wait to take it for a test drive!

    Wonder if it’ll have any algorithms for aligning/combining multiple images into a single “super resolution” image, or a higher quality or better detail / higher dynamic range image of similar quality? Things one can do with some specialized software out there, but that I’d love to be able to natively batch process, or whatever, in Mathematica.

    Likewise, any improvements to ImageStyleTransfer[], or whatever it’s called?

    MG
  2.  

    It’s wonderful to see this version finished! The “Around” functionality really seems close to what I’ve developed for my own purposes when doing lab reports, but better (just learned about asymmetric error)!
    It looks from the docs like it’s going to play nicely with various formatting options. I have been ‘hacking’ my way through it before: https://mathematica.stackexchange.com/questions/87338/tidy-form-of-numbers-with-errors-using-scientificform

    Michal Mandrysz
  3.  

    will it do 4k on windows 10 properly?

    Dan Reznik
  4.  

    Looking forward to utilizing v12.x for graduate courses I instruct.

    AND
  5.  

    Very impressive. Our species and many others would be very grateful if the Wolfram team would figure out the meaning of some of the cetacean vocalizations, starting with the probably easier dolphin whistles. I have an Android app that can recognize dolphin whistles but it does not have sufficient data to figure out the meaning. We need vocal exchanges for that and we would need a system like Wolfram to help us out. Similar to what was done in the movie Arrival but in real life and with species without written languages. Serge Masse

  6.  

    Mathematica 12 is a beautiful product.

    Could Wolfram however put some thought and effort into getting advanced features such as ImageCases implemented in a way, that allows the user to also use his own categories and entities, and not only those (cats, dogs, bicycles) hard coded into the underlying classifier?

    I rarely work on image recognition problems that require me to identify pets.

    See https://mathematica.stackexchange.com/questions/197054/how-to-extend-imagecases.

    Please get this into a more useful shape!
    Otherwise most people will of course stick to Python/TensorFlow for ML.

    HW
    •  

      We’re glad you’re enjoying Version 12, and appreciate the feedback! Allowing for new categories that are not known to ImageCases is definitely something we have been discussing, and we are hoping to have some considerable development towards that in upcoming versions.

      Administrator