The Release Pipeline

Last September we released Version 11.2 of the Wolfram Language and Mathematica—with all sorts of new functionality, including 100+ completely new functions. Version 11.2 was a big release. But today we’ve got a still bigger release: Version 11.3 that, among other things, includes nearly 120 completely new functions.

This June 23rd it’ll be 30 years since we released Version 1.0, and I’m very proud of the fact that we’ve now been able to maintain an accelerating rate of innovation and development for no less than three decades. Critical to this, of course, has been the fact that we use the Wolfram Language to develop the Wolfram Language—and indeed most of the things that we can now add in Version 11.3 are only possible because we’re making use of the huge stack of technology that we’ve been systematically building for more than 30 years.

We’ve always got a large pipeline of R&D underway, and our strategy for .1 versions is to use them to release everything that’s ready at a particular moment in time. Sometimes what’s in a .1 version may not completely fill out a new area, and some of the functions may be tagged as “experimental”. But our goal with .1 versions is to be able to deliver the latest fruits of our R&D efforts on as timely a basis as possible. Integer (.0) versions aim to be more systematic, and to provide full coverage of new areas, rounding out what has been delivered incrementally in .1 versions.

In addition to all the new functionality in 11.3, there’s a new element to our process. Starting a couple of months ago, we began livestreaming internal design review meetings that I held as we brought Version 11.3 to completion. So for those interested in “how the sausage is made”, there are now almost 122 hours of recorded meetings, from which you can find out exactly how some of the things you can now see released in Version 11.3 were originally invented. And in this post, I’m going to be linking to specific recorded livestreams relevant to features I’m discussing.

What’s New?

OK, so what’s new in Version 11.3? Well, a lot of things. And, by the way, Version 11.3 is available today on both desktop (Mac, Windows, Linux) and the Wolfram Cloud. (And yes, it takes extremely nontrivial software engineering, management and quality assurance to achieve simultaneous releases of this kind.)

In general terms, Version 11.3 not only adds some completely new directions, but also extends and strengthens what’s already there. There’s lots of strengthening of core functionality: still more automated machine learning, more robust data import, knowledgebase predictive prefetching, more visualization options, etc. There are all sorts of new conveniences: easier access to external languages, immediate input iconization, direct currying, etc. And we’ve also continued to aggressively push the envelope in all sorts of areas where we’ve had particularly active development in recent years: machine learning, neural nets, audio, asymptotic calculus, external language computation, etc.

Here’s a word cloud of new functions that got added in Version 11.3:





Blockchain

There are so many things to say about 11.3, it’s hard to know where to start. But let’s start with something topical: blockchain. As I’ll be explaining at much greater length in future posts, the Wolfram Language—with its built-in ability to talk about the real world—turns out to be uniquely suited to defining and executing computational smart contracts. The actual Wolfram Language computation for these contracts will (for now) happen off the blockchain, but it’s important for the language to be able to connect to blockchains—and that’s what’s being added in Version 11.3. [Livestreamed design discussion.]

The first thing we can do is just ask about blockchains that are out there in the world. Like here’s the most recent block added to the main Ethereum blockchain:

✕ BlockchainBlockData[-1, BlockchainBase -> "Ethereum"]

Now we can pick up one of the transactions in that block, and start looking at it:

✕ BlockchainTransactionData[\ "735e1643c33c6a632adba18b5f321ce0e13b612c90a3b9372c7c9bef447c947c", BlockchainBase -> "Ethereum"]

And we can then start doing data science—or whatever analysis—we want about the structure and content of the blockchain. For the initial release of Version 11.3, we’re supporting Bitcoin and Ethereum, though other public blockchains will be added soon.

But already in Version 11.3, we’re supporting a private (Bitcoin-core) Wolfram Blockchain that’s hosted in our Wolfram Cloud infrastructure. We’ll be periodically publishing hashes from this blockchain out in the world (probably in things like physical newspapers). And it’ll also be possible to run versions of it in private Wolfram Clouds.

It’s extremely easy to write something to the Wolfram Blockchain (and, yes, it charges a small number of Cloud Credits):

✕ BlockchainPut[Graphics[Circle[]]]

The result is a transaction hash, which one can then look up on the blockchain:

✕ BlockchainTransactionData[\ "9db73562fb45a75dd810456d575abbeb313ac19a2ec5813974c108a6935fcfb9"]

Here’s the circle back again from the blockchain:

✕

By the way, the Hash function in the Wolfram Language has been extended in 11.3 to immediately support the kinds of hashes (like “RIPEMD160SHA256”) that are used in cryptocurrency blockchains. And by using Encrypt and related functions, it’s possible to start setting up some fairly sophisticated things on the blockchain—with more coming soon.

System Modeling

Alright, so now let’s talk about something really big that’s new—at least in experimental form—in Version 11.3. One of our long-term goals in the Wolfram Language is to be able to compute about anything in the world. And in Version 11.3 we’re adding a major new class of things that we can compute about: complex engineering (and other) systems. [Livestreamed design discussions 1 and 2.]

Back in 2012 we introduced Wolfram SystemModeler: an industrial-strength system modeling environment that’s been used to model things like jet engines with tens of thousands of components. SystemModeler lets you both run simulations of models, and actually develop models using a sophisticated graphical interface.

What we’re adding (experimentally) in Version 11.3 is the built-in capability for the Wolfram Language to run models from SystemModeler—or in fact basically any model described in the Modelica language.

Let’s start with a simple example. This retrieves a particular model from our built-in repository of models:

✕ SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"]

If you press the [+] you see more detail:

But the place where it gets really interesting is that you can actually run this model. SystemModelPlot makes a plot of a “standard simulation” of the model:

✕ SystemModelPlot[ SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"]]

What actually is the model underneath? Well, it’s a set of equations that describe the dynamics of how the components of the system behave. And for a very simple system like this, these equations are already pretty complicated:

✕ SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"][\ "SystemEquations"]

It comes with the territory in modeling real-world systems that there tend to be lots of components, with lots of complicated interactions. SystemModeler is set up to let people design arbitrarily complicated systems graphically, hierarchically connecting together components representing physical or other objects. But the big new thing is that once you have the model, then with Version 11.3 you can immediately work with it in the Wolfram Language.

Every model has lots of properties:

✕ [SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"] \ "Properties"]

One of these properties gives the variables that characterize the system. And, yes, even in a very simple system like this, there are already lots of those:

✕ [SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"] \ "SystemVariables"]

Here’s a plot of how one of those variables behaves in the simulation:

✕ SystemModelPlot[[SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"], "idealTriac.capacitor.p.i"]]

A typical thing one wants to do is to investigate how the system behaves when parameters are changed. This simulates the system with one of its parameters changed, then makes a plot:

✕ SystemModelSimulate[[SystemModel["Modelica.Electrical.Analog.Examples.IdealTriacCircuit"]], {"V.freqHz" -> 2.5}|>]

✕ SystemModelPlot[%, "idealTriac.capacitor.p.i"]

We could go on from here to sample lots of different possible inputs or parameter values, and do things like studying the robustness of the system to changes. Version 11.3 provides a very rich environment for doing all these things as an integrated part of the Wolfram Language.

In 11.3 there are already over 1000 ready-to-run models included—of electrical, mechanical, thermal, hydraulic, biological and other systems. Here’s a slightly more complicated example—the core part of a car:

✕ SystemModel["IndustryExamples.AutomotiveTransportation.Driveline.\ DrivelineModel"]

If you expand the icon, you can mouse over the parts to find out what they are:

This gives a quick summary of the model, showing that it involves 1110 variables:

✕ SystemModel["IndustryExamples.AutomotiveTransportation.Driveline.\ DrivelineModel"]["Summary"]

In addition to complete ready-to-run models, there are also over 6000 components included in 11.3, from which models can be constructed. SystemModeler provides a full graphical environment for assembling these components. But one can also do it purely with Wolfram Language code, using functions like ConnectSystemModelComponents (which essentially defines the graph of how the connectors of different components are connected):

✕ components = {"R" \[Element] "Modelica.Electrical.Analog.Basic.Resistor", "L" \[Element] "Modelica.Electrical.Analog.Basic.Inductor", "AC" \[Element] "Modelica.Electrical.Analog.Sources.SineVoltage", "G" \[Element] "Modelica.Electrical.Analog.Basic.Ground"};

✕ connections = {"G.p" -> "AC.n", "AC.p" -> "L.n", "L.p" -> "R.n", "R.p" -> "AC.n"};

✕ model = ConnectSystemModelComponents[components, connections]

You can also create models directly from their underlying equations, as well as making “black-box models” purely from data or empirical functions (say from machine learning).

It’s taken a long time to build all the system modeling capabilities that we’re introducing in 11.3. And they rely on a lot of sophisticated features of the Wolfram Language—including large-scale symbolic manipulation, the ability to robustly solve systems of differential-algebraic equations, handling of quantities and units, and much more. But now that system modeling is integrated into the Wolfram Language, it opens all sorts of important new opportunities—not only in engineering, but in all fields that benefit from being able to readily simulate multi-component real-world systems.

New in Notebooks

We first introduced notebooks in Version 1.0 back in 1988—so by now we’ve been polishing how they work for no less than 30 years. Version 11.3 introduces a number of new features. A simple one is that closed cell groups now by default have an “opener button”, as well as being openable using their cell brackets:

I find this helpful, because otherwise I sometimes don’t notice closed groups, with extra cells inside. (And, yes, if you don’t like it, you can always switch it off in the stylesheet.)

Another small but useful change is the introduction of “indefinite In/Out labels”. In a notebook that’s connected to an active kernel, successive cells are labeled In[1], Out[1], etc. But if one’s no longer connected to the same kernel (say, because one saved and reopened the notebook), the In/Out numbering no longer makes sense. So in the past, there were just no In, Out labels shown. But as of Version 11.3, there are still labels, but they’re grayed down, and they don’t have any explicit numbers in them:

Another new feature in Version 11.3 is Iconize . Here’s the basic problem it solves. Let’s say you’ve got some big piece of data or other input that you want to store in the notebook, but you don’t want it to visually fill up the notebook. Well, one thing you can do is to put it in closed cells. But then to use the data you have to do something like creating a variable and so on. Iconize provides a simple, inline way to save data in a notebook.

Here’s how you make an iconized version of an expression:

✕ Iconize[Range[10]]

Now you can use this iconized form in place of giving the whole expression; it just immediately evaluates to the full expression:

✕ Reverse[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}]

Another convenient use of Iconize is to make code easier to read, while still being complete. For example, consider something like this:

✕ Plot[Sin[Tan[x]], {x, 0, 10}, Filling -> Axis, PlotTheme -> "Scientific"]

You can select the options here, then go to the right-click menu and say to Iconize them:

The result is an easier-to-read piece of code—that still evaluates just as it did before:

✕ Plot[Sin[Tan[x]], {x, 0, 10}, Sequence[ Filling -> Axis, PlotTheme -> "Scientific"]]

In Version 11.2 we introduced ExternalEvaluate , for evaluating code in external languages (initially Python and JavaScript) directly from the Wolfram Language. (This is supported on the desktop and in private clouds; for security and provisioning reasons, the public Wolfram Cloud only runs pure Wolfram Language code.)

In Version 11.3 we’re now making it even easier to enter external code in notebooks. Just start an input cell with a > and you’ll get an external code cell (you can stickily select the language you want):

✕ ExternalEvaluate["Python", "import platform; platform.platform()"]

And, yes, what comes back is a Wolfram Language expression that you can compute with:

✕ StringSplit[%, "-"]

Workflow Documentation

We put a lot of emphasis on documenting the Wolfram Language—and traditionally we’ve had basically three kinds of components to our documentation: “reference pages” that cover a single function, “guide pages” that give a summary with links to many functions, and “tutorials” that provide narrative introductions to areas of functionality. Well, as of Version 11.3 there’s a fourth kind of component: workflows—which is what the gray tiles at the bottom of the “root guide page” lead to.

When everything you’re doing is represented by explicit Wolfram Language code, the In/Out paradigm of notebooks is a great way to show what’s going on. But if you’re clicking around, or, worse, using external programs, this isn’t enough. And that’s where workflows come in—because they use all sorts of graphical devices to present sequences of actions that aren’t just entering Wolfram Language input.

So if you’re getting coordinates from a plot, or deploying a complex form to the web, or adding a banner to a notebook, then expect to follow the new workflow documentation that we have. And, by the way, you’ll find links to relevant workflows from reference pages for functions.

Another big new interface-related thing in Version 11.3 is Presenter Tools—a complete environment for creating and running presentations that include live interactivity. What makes Presenter Tools possible is the rich notebook system that we’ve built over the past 30 years. But what it does is to add all the features one needs to conveniently create and run really great presentations.

People have been using our previous SlideShow format to give presentations with Wolfram Notebooks for about 20 years. But it was never a complete solution. Yes, it provided nice notebook features like live computation in a slide show environment, but it didn’t do “PowerPoint-like” things such as automatically scaling content to screen resolution. To be fair, we expected that operating systems would just intrinsically solve problems like content scaling. But it’s been 20 years and they still haven’t. So now we’ve built the new Presenter Tools that both solves such problems, and adds a whole range of features to create great presentations with notebooks as easy as possible.

To start, just choose File > New > Presenter Notebook. Then pick your template and theme, and you’re off and running:

Here’s what it looks like when you’re editing your presentation (and you can change themes whenever you want):

When you’re ready to present, just press Start Presentation. Everything goes full screen and is automatically scaled to the resolution of the screen you’re using. But here’s the big difference from PowerPoint-like systems: everything is live, interactive, editable, and scrollable. For example, you can have a Manipulate right inside a slide, and you can immediately interact with it. (Oh, and everything can be dynamic, say recreating graphics based on data that’s being imported in real time.) You can also use things like cell groups to organize content in slides. And you can edit what’s on a slide, and for example, do livecoding, running your code as you go.

When you’re ready to go to a new slide, just press a single key (or have your remote do it for you). By default, the key is Page Down (so you can still use arrow keys in editing), but you can set a different key if you want. You can have Presenter Tools show your slides on one display, then display notes and controls on another display. When you make your slides, you can include SideNotes and SideCode. SideNotes are “PowerPoint-like” textual notes. But SideCode is something different. It’s actually based on something I’ve done in my own talks for years. It’s code you’ve prepared, that you can “magically” insert onto a slide in real time during your presentation, immediately evaluating it if you want.

I’ve given a huge number of talks using Wolfram Notebooks over the years. A few times I’ve used the SlideShow format, but mostly I’ve just done everything in an ordinary notebook, often keeping notes on a separate device. But now I’m excited that with Version 11.3 I’ve got basically exactly the tools I need to prepare and present talks. I can pre-define some of the content and structure, but then the actual talk can be very dynamic and spontaneous—with live editing, livecoding and all sorts of interactivity.

Wolfram Chat

While we’re discussing interface capabilities, here’s another new one: Wolfram Chat. When people are interactively working together on something, it’s common to hear someone say “let me just send you a piece of code” or “let me send you a Manipulate ”. Well, in Version 11.3 there’s now a very convenient way to do this, built directly into the Wolfram Notebook system—and it’s called Wolfram Chat. [Livestreamed design discussion.]

Just select File > New > Chat; you’ll get asked who you want to “chat with”—and it could be anyone anywhere with a Wolfram ID (though of course they do have to accept your invitation):

Then you can start a chat session, and, for example, put it alongside an ordinary notebook:

The neat thing is that you can send anything that can appear in a notebook, including images, code, dynamic objects, etc. (though it’s sandboxed so people can’t send “code bombs” to each other).

There are lots of obvious applications of Wolfram Chat, not only in collaboration, but also in things like classroom settings and technical support. And there are some other applications too. Like for running livecoding competitions. And in fact one of the ways we stress-tested Wolfram Chat during development was to use it for the livecoding competition at the Wolfram Technology Conference last fall.

One might think that chat is something straightforward. But actually it’s surprisingly tricky, with a remarkable number of different situations and cases to cover. Under the hood, Wolfram Chat is using both the Wolfram Cloud and the new pub-sub channel framework that we introduced in Version 11.0. In Version 11.3, Wolfram Chat is only being supported for desktop Wolfram Notebooks, but it’ll be coming soon to notebooks on the web and on mobile.

Language Conveniences

We’re always polishing the Wolfram Language to make it more convenient and productive to use. And one way we do this is by adding new little “convenience functions” in every version of the language. Often what these functions do is pretty straightforward; the challenge (which has often taken years) is to come up with really clean designs for them. (You can see quite a bit of the discussion about the new convenience functions for Version 11.3 in livestreams we’ve done recently.)

Here’s a function that it’s sort of amazing we’ve never explicitly had before—a function that just constructs an expression from its head and arguments:

✕ Construct[f, x, y]

Why is this useful? Well, it can save explicitly constructing pure functions with Function or & , for example in a case like this:

✕ Fold[Construct, f, {a, b, c}]

Another function that at some level is very straightforward (but about whose name we agonized for quite a while) is Curry . Curry (named after “currying”, which is in turn named after Haskell Curry) essentially makes operator forms, with Curry [f,n] “currying in” n arguments:

✕ Curry[f, 3][a][b][c][d][e]

The one-argument form of Curry itself is:

✕ Curry[f][x][y]

Why is this useful? Well, some functions (like Select , say) have built-in “operator forms”, in which you give one argument, then you “curry in” others:

✕ Select[# > 5 &][Range[10]]

But what if you wanted to create an operator form yourself? Well, you could always explicitly construct it using Function or & . But with Curry you don’t need to do that. Like here’s an operator form of D , in which the second argument is specified to be x:

✕ Curry[D][x]

Now we can apply this operator form to actually do differentiation with respect to x:

✕ %[f[x]]

Yes, Curry is at some level rather abstract. But it’s a nice convenience if you understand it—and understanding it is a good exercise in understanding the symbolic structure of the Wolfram Language.

Talking of operator forms, by the way, NearestTo is an operator-form analog of Nearest (the one-argument form of Nearest itself generates a NearestFunction ):

✕ NearestTo[2.3][{1, 2, 3, 4, 5}]

Here’s an example of why this is useful. This finds the 5 chemical elements whose densities are nearest to 10 g/cc:

✕ Entity["Element", "Density" -> NearestTo[\!\(\* NamespaceBox["LinguisticAssistant", DynamicModuleBox[{Typeset`query$$ = "10 g/cc", Typeset`boxes$$ = TemplateBox[{"10", RowBox[{"\"g\"", " ", "\"/\"", " ", SuperscriptBox["\"cm\"", "3"]}], "grams per centimeter cubed", FractionBox["\"Grams\"", SuperscriptBox["\"Centimeters\"", "3"]]}, "Quantity", SyntaxForm -> Mod], Typeset`allassumptions$$ = {}, Typeset`assumptions$$ = {}, Typeset`open$$ = {1, 2}, Typeset`querystate$$ = { "Online" -> True, "Allowed" -> True, "mparse.jsp" -> 0.777394`6.342186177878503, "Messages" -> {}}}, DynamicBox[ToBoxes[ AlphaIntegration`LinguisticAssistantBoxes["", 4, Automatic, Dynamic[Typeset`query$$], Dynamic[Typeset`boxes$$], Dynamic[Typeset`allassumptions$$], Dynamic[Typeset`assumptions$$], Dynamic[Typeset`open$$], Dynamic[Typeset`querystate$$]], StandardForm], ImageSizeCache->{94., {8., 19.}}, TrackedSymbols:>{ Typeset`query$$, Typeset`boxes$$, Typeset`allassumptions$$, Typeset`assumptions$$, Typeset`open$$, Typeset`querystate$$}], DynamicModuleValues:>{}, UndoTrackedVariables:>{Typeset`open$$}], BaseStyle->{"Deploy"}, DeleteWithContents->True, Editable->False, SelectWithContents->True]\), 5]] // EntityList

In Version 10.1 in 2015 we introduced a bunch of functions that operate on sequences in lists. Version 11.3 adds a couple more such functions. One is SequenceSplit . It’s like StringSplit for lists: it splits lists at the positions of particular sequences:

✕ uenceSplit[{a, b, x, x, c, d, x, e, x, x, a, b}, {x, x}]

Also new in the “Sequence family” is the function SequenceReplace :

✕ SequenceReplace[{a, b, x, x, c, d, x, e, x, x, a, b}, {x, n_} -> {n, n, n}]

Just as we’re always polishing the core programming functionality of the Wolfram Language, we’re also always polishing things like visualization.

In Version 11.0, we added GeoHistogram , here showing “volcano density” in the US:

✕ GeoHistogram[GeoPosition[GeoEntities[\!\(\* NamespaceBox["LinguisticAssistant", DynamicModuleBox[{Typeset`query$$ = "USA", Typeset`boxes$$ = TemplateBox[{"\"United States\"", RowBox[{"Entity", "[", RowBox[{"\"Country\"", ",", "\"UnitedStates\""}], "]"}], "\"Entity[\\\"Country\\\", \\\"UnitedStates\\\"]\"", "\"country\""}, "Entity"], Typeset`allassumptions$$ = {{ "type" -> "Clash", "word" -> "USA", "template" -> "Assuming \"${word}\" is ${desc1}. Use as \ ${desc2} instead", "count" -> "2", "Values" -> {{ "name" -> "Country", "desc" -> "a country", "input" -> "*C.USA-_*Country-"}, { "name" -> "FileFormat", "desc" -> "a file format", "input" -> "*C.USA-_*FileFormat-"}}}}, Typeset`assumptions$$ = {}, Typeset`open$$ = {1, 2}, Typeset`querystate$$ = { "Online" -> True, "Allowed" -> True, "mparse.jsp" -> 0.373096`6.02336558644664, "Messages" -> {}}}, DynamicBox[ToBoxes[ AlphaIntegration`LinguisticAssistantBoxes["", 4, Automatic, Dynamic[Typeset`query$$], Dynamic[Typeset`boxes$$], Dynamic[Typeset`allassumptions$$], Dynamic[Typeset`assumptions$$], Dynamic[Typeset`open$$], Dynamic[Typeset`querystate$$]], StandardForm], ImageSizeCache->{197., {7., 16.}}, TrackedSymbols:>{ Typeset`query$$, Typeset`boxes$$, Typeset`allassumptions$$, Typeset`assumptions$$, Typeset`open$$, Typeset`querystate$$}], DynamicModuleValues:>{}, UndoTrackedVariables:>{Typeset`open$$}], BaseStyle->{"Deploy"}, DeleteWithContents->True, Editable->False, SelectWithContents->True]\), "Volcano"]]]

In Version 11.3, we’ve added GeoSmoothHistogram :

✕ GeoSmoothHistogram[GeoPosition[GeoEntities[\!\(\* NamespaceBox["LinguisticAssistant", DynamicModuleBox[{Typeset`query$$ = "USA", Typeset`boxes$$ = TemplateBox[{"\"United States\"", RowBox[{"Entity", "[", RowBox[{"\"Country\"", ",", "\"UnitedStates\""}], "]"}], "\"Entity[\\\"Country\\\", \\\"UnitedStates\\\"]\"", "\"country\""}, "Entity"], Typeset`allassumptions$$ = {{ "type" -> "Clash", "word" -> "USA", "template" -> "Assuming \"${word}\" is ${desc1}. Use as \ ${desc2} instead", "count" -> "2", "Values" -> {{ "name" -> "Country", "desc" -> "a country", "input" -> "*C.USA-_*Country-"}, { "name" -> "FileFormat", "desc" -> "a file format", "input" -> "*C.USA-_*FileFormat-"}}}}, Typeset`assumptions$$ = {}, Typeset`open$$ = {1, 2}, Typeset`querystate$$ = { "Online" -> True, "Allowed" -> True, "mparse.jsp" -> 0.373096`6.02336558644664, "Messages" -> {}}}, DynamicBox[ToBoxes[ AlphaIntegration`LinguisticAssistantBoxes["", 4, Automatic, Dynamic[Typeset`query$$], Dynamic[Typeset`boxes$$], Dynamic[Typeset`allassumptions$$], Dynamic[Typeset`assumptions$$], Dynamic[Typeset`open$$], Dynamic[Typeset`querystate$$]], StandardForm], ImageSizeCache->{197., {7., 16.}}, TrackedSymbols:>{ Typeset`query$$, Typeset`boxes$$, Typeset`allassumptions$$, Typeset`assumptions$$, Typeset`open$$, Typeset`querystate$$}], DynamicModuleValues:>{}, UndoTrackedVariables:>{Typeset`open$$}], BaseStyle->{"Deploy"}, DeleteWithContents->True, Editable->False, SelectWithContents->True]\), "Volcano"]]]

Also new in Version 11.3 are callouts in 3D plots, here random words labeling random points (but note how the words are positioned to avoid each other):

✕ ListPointPlot3D[Table[Callout[RandomReal[10, 3], RandomWord[]], 25]]

We can make a slightly more meaningful plot of words in 3D by using the new machine-learning-based FeatureSpacePlot3D (notice for example that “vocalizing” and “crooning” appropriately end up close together):

✕ FeatureSpacePlot3D[RandomWord[20]]

Text Reading

Talking of machine learning, Version 11.3 continues our aggressive development of automated machine learning, building both general tools, and specific functions that make use of machine learning.

An interesting example of a new function is FindTextualAnswer , which takes a piece of text, and tries to find answers to textual questions. Here we’re using the Wikipedia article on “rhinoceros”, asking how much a rhino weighs:

✕ FindTextualAnswer[ WikipediaData["rhinoceros"], "How much does a rhino weigh?"]

It almost seems like magic. Of course it doesn’t always work, and it can do things that we humans would consider pretty stupid. But it’s using very state-of-the-art machine learning methodology, together with a lot of unique training data based on Wolfram|Alpha. We can see a little more of what it does if we ask not just for its top answer about rhino weights, but for its top 5:

✕ FindTextualAnswer[ WikipediaData["rhinoceros"], "How much does a rhino weigh?", 5]

Hmmm. So what’s a more definitive answer? Well, for that we can use our actual curated knowledgebase:

✕ \!\( NamespaceBox["LinguisticAssistant", DynamicModuleBox[{Typeset`query$$ = "rhino weight", Typeset`boxes$$ = RowBox[{ TemplateBox[{"\"rhinoceroses\"", RowBox[{"Entity", "[", RowBox[{"\"Species\"", ",", "\"Family:Rhinocerotidae\""}], "]"}], "\"Entity[\\\"Species\\\", \\\"Family:Rhinocerotidae\\\"]\"", "\"species specification\""}, "Entity"], "[", TemplateBox[{"\"weight\"", RowBox[{"EntityProperty", "[", RowBox[{"\"Species\"", ",", "\"Weight\""}], "]"}], "\"EntityProperty[\\\"Species\\\", \\\"Weight\\\"]\""}, "EntityProperty"], "]"}], Typeset`allassumptions$$ = {{ "type" -> "MultiClash", "word" -> "", "template" -> "Assuming ${word1} is referring to ${desc1}. Use \ \"${word2}\" as ${desc2}. Use \"${word3}\" as ${desc3}.", "count" -> "3", "Values" -> {{ "name" -> "Species", "word" -> "rhino", "desc" -> "a species specification", "input" -> "*MC.%7E-_*Species-"}, { "name" -> "Person", "word" -> "rhino", "desc" -> "a person", "input" -> "*MC.%7E-_*Person-"}, { "name" -> "Formula", "word" -> "", "desc" -> "a formula", "input" -> "*MC.%7E-_*Formula-"}}}}, Typeset`assumptions$$ = {}, Typeset`open$$ = {1}, Typeset`querystate$$ = { "Online" -> True, "Allowed" -> True, "mparse.jsp" -> 0.812573`6.361407381082941, "Messages" -> {}}}, DynamicBox[ToBoxes[ AlphaIntegration`LinguisticAssistantBoxes["", 4, Automatic, Dynamic[Typeset`query$$], Dynamic[Typeset`boxes$$], Dynamic[Typeset`allassumptions$$], Dynamic[Typeset`assumptions$$], Dynamic[Typeset`open$$], Dynamic[Typeset`querystate$$]], StandardForm], ImageSizeCache->{96., {7., 16.}}, TrackedSymbols:>{ Typeset`query$$, Typeset`boxes$$, Typeset`allassumptions$$, Typeset`assumptions$$, Typeset`open$$, Typeset`querystate$$}], DynamicModuleValues:>{}, UndoTrackedVariables:>{Typeset`open$$}], BaseStyle->{"Deploy"}, DeleteWithContents->True, Editable->False, SelectWithContents->True]\)

Or in tons:

✕ UnitConvert[%, \!\(\* NamespaceBox["LinguisticAssistant", DynamicModuleBox[{Typeset`query$$ = "tons", Typeset`boxes$$ = TemplateBox[{ InterpretationBox[" ", 1], "\"sh tn\"", "short tons", "\"ShortTons\""}, "Quantity", SyntaxForm -> Mod], Typeset`allassumptions$$ = {{ "type" -> "Clash", "word" -> "tons", "template" -> "Assuming \"${word}\" is ${desc1}. Use as \ ${desc2} instead", "count" -> "2", "Values" -> {{ "name" -> "Unit", "desc" -> "a unit", "input" -> "*C.tons-_*Unit-"}, { "name" -> "Word", "desc" -> "a word", "input" -> "*C.tons-_*Word-"}}}, { "type" -> "Unit", "word" -> "tons", "template" -> "Assuming ${desc1} for \"${word}\". Use ${desc2} \ instead", "count" -> "10", "Values" -> {{ "name" -> "ShortTons", "desc" -> "short tons", "input" -> "UnitClash_*tons.*ShortTons--"}, { "name" -> "LongTons", "desc" -> "long tons", "input" -> "UnitClash_*tons.*LongTons--"}, { "name" -> "MetricTons", "desc" -> "metric tons", "input" -> "UnitClash_*tons.*MetricTons--"}, { "name" -> "ShortTonsForce", "desc" -> "short tons-force", "input" -> "UnitClash_*tons.*ShortTonsForce--"}, { "name" -> "TonsOfTNT", "desc" -> "tons of TNT", "input" -> "UnitClash_*tons.*TonsOfTNT--"}, { "name" -> "DisplacementTons", "desc" -> "displacement tons", "input" -> "UnitClash_*tons.*DisplacementTons--"}, { "name" -> "LongTonsForce", "desc" -> "long tons-force", "input" -> "UnitClash_*tons.*LongTonsForce--"}, { "name" -> "MetricTonsForce", "desc" -> "metric tons-force", "input" -> "UnitClash_*tons.*MetricTonsForce--"}, { "name" -> "TonsOfRefrigerationUS", "desc" -> "US commercial tons of refrigeration", "input" -> "UnitClash_*tons.*TonsOfRefrigerationUS--"}, { "name" -> "TonsOfRefrigerationUKCommercial", "desc" -> "UK commercial tons of refrigeration (power)", "input" -> "UnitClash_*tons.*\ TonsOfRefrigerationUKCommercial--"}}}}, Typeset`assumptions$$ = {}, Typeset`open$$ = {1}, Typeset`querystate$$ = { "Online" -> True, "Allowed" -> True, "mparse.jsp" -> 0.303144`5.933193970346431, "Messages" -> {}}}, DynamicBox[ToBoxes[ AlphaIntegration`LinguisticAssistantBoxes["", 4, Automatic, Dynamic[Typeset`query$$], Dynamic[Typeset`boxes$$], Dynamic[Typeset`allassumptions$$], Dynamic[Typeset`assumptions$$], Dynamic[Typeset`open$$], Dynamic[Typeset`querystate$$]], StandardForm], ImageSizeCache->{47., {7., 16.}}, TrackedSymbols:>{ Typeset`query$$, Typeset`boxes$$, Typeset`allassumptions$$, Typeset`assumptions$$, Typeset`open$$, Typeset`querystate$$}], DynamicModuleValues:>{}, UndoTrackedVariables:>{Typeset`open$$}], BaseStyle->{"Deploy"}, DeleteWithContents->True, Editable->False, SelectWithContents->True]\)]

FindTextualAnswer is no substitute for our whole data curation and computable data strategy. But it’s useful as a way to quickly get a first guess of an answer, even from completely unstructured text. And, yes, it should do well at critical reading exercises, and could probably be made to do well at Jeopardy! too.

Face Computation

We humans respond a lot to human faces, and with modern machine learning it’s possible to do all sorts of face-related computations—and in Version 11.3 we’ve added systematic functions for this. Here FindFaces pulls out faces (of famous physicists) from a photograph:

✕ FindFaces[CloudGet["https://wolfr.am/sWoDYqbb"], "Image"]

FacialFeatures uses machine learning methods to estimate various attributes of faces (such as the apparent age, apparent gender and emotional state):

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

These features can for example be used as criteria in FindFaces , here picking out physicists who appear to be under 40:

✕ FindFaces[CloudGet["https://wolfr.am/sWoDYqbb"], #Age < 40 &, "Image"]

Neural Networks

There are now all sorts of functions in the Wolfram Language (like FacialFeatures ) that use neural networks inside. But for several years we’ve also been energetically building a whole subsystem in the Wolfram Language to let people work directly with neural networks. We’ve been building on top of low-level libraries (particularly MXNet, to which we’ve been big contributors), so we can make use of all the latest GPU and other optimizations. But our goal is to build a high-level symbolic layer that makes it as easy as possible to actually set up neural net computations. [Livestreamed design discussions 1, 2 and 3.]

There are many parts to this. Setting up automatic encoding and decoding to standard Wolfram Language constructs for text, images, audio and so on. Automatically being able to knit together individual neural net operations, particularly ones that deal with things like sequences. Being able to automate training as much as possible, including automatically doing hyperparameter optimization.

But there’s something perhaps even more important too: having a large library of existing, trained (and untrained) neural nets, that can both be used directly for computations, and can be used for transfer learning, or as feature extractors. And to achieve this, we’ve been building our Neural Net Repository:

There are networks here that do all sorts of remarkable things. And we’re adding new networks every week. Each network has its own page, that includes examples and detailed information. The networks are stored in the cloud. But all you have to do to pull them into your computation is to use NetModel :

✕ NetModel["3D Face Alignment Net Trained on 300W Large Pose Data"]

Here’s the actual network used by FindTextualAnswer :

✕ NetModel["Wolfram FindTextualAnswer Net for WL 11.3"]

One thing that’s new in Version 11.3 is the iconic representation we’re using for networks. We’ve optimized it to give you a good overall view of the structure of net graphs, but then to allow interactive drilldown to any level of detail. And when you train a neural network, the interactive panels that come up have some spiffy new features—and with NetTrainResultsObject , we’ve now made the actual training process itself computable.

Version 11.3 has some new layer types like CTCLossLayer (particularly to support audio), as well as lots of updates and enhancements to existing layer types (10x faster LSTMs on GPUs, automatic variable-length convolutions, extensions of many layers to support arbitrary-dimension inputs, etc.). In Version 11.3 we’ve had a particular focus on recurrent networks and sequence generation. And to support this, we’ve introduced things like NetStateObject —that basically allows a network to have a persistent state that’s updated as a result of input data the network receives.

In developing our symbolic neural net framework we’re really going in two directions. The first is to make everything more and more automated, so it’s easier and easier to set up neural net systems. But the second is to be able to readily handle more and more neural net structures. And in Version 11.3 we’re adding a whole collection of “network surgery” functions—like NetTake , NetJoin and NetFlatten —to let you go in and tweak and hack neural nets however you want. Of course, our system is designed so that even if you do this, our whole automated system—with training and so on—still works just fine.

Asymptotic Analysis

For more than 30 years, we’ve been on a mission to make as much mathematics as possible computational. And in Version 11.3 we’ve finally started to crack an important holdout area: asymptotic analysis.

Here’s a simple example: find an approximate solution to a differential equation near x = 0:

✕ AsymptoticDSolveValue[x^2 y'[x] + (x^2 + 1) y[x] == 0, y[x], {x, 0, 10}]

At first, this might just look like a power series solution. But look more carefully: there’s an e(1/x) factor that would just give infinity at every order as a power series in x. But with Version 11.3, we’ve now got asymptotic analysis functions that handle all sorts of scales of growth and oscillation, not just powers.

Back when I made my living as a physicist, it always seemed like some of the most powerful dark arts centered around perturbation methods. There were regular perturbations and singular perturbations. There were things like the WKB method, and the boundary layer method. The point was always to compute an expansion in some small parameter, but it seemed to always require different trickery in different cases to achieve it. But now, after a few decades of work, we finally in Version 11.3 have a systematic way to solve these problems. Like here’s a differential equation where we’re looking for the solution for small ε:

✕ AsymptoticDSolveValue[{\[Epsilon] y''[x] + (x + 1) y[x] == 0, y[0] == 1, y[1] == 0}, y[x], x, {\[Epsilon], 0, 2}]

Back in Version 11.2, we added a lot of capabilities for dealing with more sophisticated limits. But with our asymptotic analysis techniques we’re now also able to do something else, that’s highly relevant for all sorts of problems in areas like number theory and computational complexity theory, which is to compare asymptotic growth rates.

This is asking: is 2nk asymptotically less than (nm)! as n->∞? The result: yes, subject to certain conditions:

✕ AsymptoticLess[ 2^n^k, (n^m)!, n -> \[Infinity]]

“Elementary” Algebra

One of the features of Wolfram|Alpha popular among students is its “Show Steps” functionality, in which it synthesizes “on-the-fly tutorials” showing how to derive answers it gives. But what actually are the steps, in, say, a Show Steps result for algebra? Well, they’re “elementary operations” like “add the corresponding sides of two equations”. And in Version 11.3, we’re including functions to just directly do things like this:

✕ AddSides[a == b, c == d]

✕ MultiplySides[a == b, c == d]

And, OK, it seems like these are really trivial functions, that basically just operate on the structure of equations. And that’s actually what I thought when I said we should implement them. But as our Algebra R&D team quickly pointed out, there are all sorts of gotchas (“what if b is negative?”, etc.), that are what students often get wrong—but that with all of the algorithmic infrastructure in the Wolfram Language it’s easy for us to get right:

✕ MultiplySides[x/b > 7, b]

Proofs

The Wolfram Language is mostly about computing results. But given a result, one can also ask why it’s correct: one can ask for some kind of proof that demonstrates that it’s correct. And for more than 20 years I’ve been wondering how to find and represent general proofs in a useful and computable way in the Wolfram Language. And I’m excited that finally in Version 11.3 the function FindEquationalProof provides an example—which we’ll be generalizing and building on in future versions. [Livestreamed design discussion.]

My all-time favorite success story for automated theorem proving is the tiny (and in fact provably simplest) axiom system for Boolean algebra that I found in 2000. It’s just a single axiom, with a single operator that one can think of as corresponding to the Nand operation. For 11 years, FullSimplify has actually been able to use automated theorem-proving methods inside, to be able to compute things. So here it’s starting from my axiom for Boolean algebra, then computing that Nand is commutative:

✕ FullSimplify[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]

But this just tells us the result; it doesn’t give any kind of proof. Well, in Version 11.3, we can now get a proof:

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]

What is the proof object? We can see from the summary that the proof takes 102 steps. Then we can ask for a “proof graph”. The green arrow at the top represents the original axiom; the red square at the bottom represents the thing being proved. All the nodes in the middle are intermediate lemmas, proved from each other according to the connections shown.

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]; proof["ProofGraph"]

What’s actually in the proof? Well, it’s complicated. But here’s a dataset that gives all the details:

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]; proof["ProofDataset"]

You can get a somewhat more narrative form as a notebook too:

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]; proof["ProofNotebook"]

And then you can also get a “proof function”, which is a piece of code that can be executed to verify the result:

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]; proof["ProofFunction"]

Unsurprisingly, and unexcitingly, it gives True if you run it:

✕ proof = FindEquationalProof[nand[p, q] == nand[q, p], ForAll[{a, b, c}, nand[nand[nand[a, b], c], nand[a, nand[nand[a, c], a]]] == c]]; proof["ProofFunction"][]

Now that we can actually generate symbolic proof structures in the Wolfram Language, there’s a lot of empirical metamathematics to do—as I’ll discuss in a future post. But given that FindEquationalProof works on arbitrary “equation-like” symbolic relations, it can actually be applied to lots of things—like verifying protocols and policies, for example in popular areas like blockchain.

The Growing Knowledgebase

The Wolfram Knowledgebase grows every single day—partly through systematic data feeds, and partly through new curated data and domains being explicitly added. If one asks what happens to have been added between Version 11.2 and Version 11.3, it’s a slightly strange grab bag. There are 150+ new properties about public companies. There are 900 new named features on Pluto and Mercury. There are 16,000 new anatomical structures, such as nerve pathways. There are nearly 500 new “notable graphs”. There are thousands of new mountains, islands, notable buildings, and other geo-related features. There are lots of new properties of foods, and new connections to diseases. And much more.

But in terms of typical everyday use of the Wolfram Knowledgebase the most important new feature in Version 11.3 is the entity prefetching system. The knowledgebase is obviously big, and it’s stored in the cloud. But if you’re using a desktop system, the data you need is “magically” downloaded for you.

Well, in Version 11.3, the magic got considerably stronger. Because now when you ask for one particular item, the system will try to figure out what you’re likely to ask for next, and it’ll automatically start asynchronously prefetching it, so when you actually ask for it, it’ll already be there on your computer—and you won’t have to wait for it to download from the cloud. (If you want to do the prefetching “by hand”, there’s the function EntityPrefetch to do it. Note that if you’re using the Wolfram Language in the cloud, the knowledgebase is already “right there”, so there’s no downloading or prefetching to do.)

The whole prefetching mechanism is applied quite generally. So, for example, if you use Interpreter to interpret some input (say, US state abbreviations), information about how to do the interpretations will also get prefetched—so if you’re using the desktop, the interpretations can be done locally without having to communicate with the cloud.

Messages and Mail

You’ve been able to send email from the Wolfram Language (using SendMail ) for a decade. But starting in Version 11.3, it can use full HTML formatting, and you can embed lots of things in it—not just graphics and images, but also cloud objects, datasets, audio and so on. [Livestreamed design discussion.]

Version 11.3 also introduces the ability to send text messages (SMS and MMS) using SendMessage . For security reasons, though, you can only send to your own mobile number, as given by the value of $MobilePhone (and, yes, obviously, the number gets validated).

The Wolfram Language has been able to import mail messages and mailboxes for a long time, and with MailReceiverFunction it’s also able to respond to incoming mail. But in Version 11.3 something new that’s been added is the capability to deal with live mailboxes.

First, connect to an (IMAP, for now) mail server (I’m not showing the authentication dialog that comes up):

✕ mail = MailServerConnect[]

Then you can basically use the Wolfram Language as a programmable mail client. This gives you a dataset of current unread messages in your mailbox:

✕ MailSearch[ "fahim"|>]

Now we can pick out one of these messages, and we get a symbolic MailItem object, that for example we can delete:

✕ MailSearch[ "fahim"|>][[1]]

✕ MailExecute["Delete", %%["MailItem"]]

Systems-Level Operations

Version 11.3 supports a lot of new systems-level operations. Let’s start with a simple but useful one: remote program execution. The function RemoteRun is basically like Unix rsh: you give it a host name (or IP address) and it runs a command there. The Authentication option lets you specify a username and password. If you want to run a persistent program remotely, you can now do that with RemoteRunProcess , which is the remote analog of the local RunProcess .

In dealing with remote computer systems, authentication is always an issue—and for several years we’ve been building a progressively more sophisticated symbolic authentication framework in the Wolfram Language. In Version 11.3 there’s a new AuthenticationDialog function, which pops up a whole variety of appropriately configured authentication dialogs. Then there’s GenerateSecuredAuthenticationKey —which generates OAuth SecuredAuthenticationKey objects that people can use to authenticate calls into the Wolfram Cloud from the outside.

Also at a systems level, there are some new import/export formats, like BSON (JSON-like binary serialization format) and WARC (web archive format). There are also HTTPResponse and HTTPRequest formats, that (among many other things) you can use to basically write a web server in the Wolfram Language in a couple of lines.

We introduced ByteArray objects into the Wolfram Language quite a few years ago—and we’ve been steadily growing support for them. In Version 11.3, there are BaseEncode and BaseDecode for converting between byte arrays and Base64 strings. Version 11.3 also extends Hash (which, among other things, works on byte arrays), adding various types of hashing (such as double SHA-256 and RIPEMD) that are used for modern blockchain and cryptocurrency purposes.

We’re always adding more kinds of data that we can make computable in the Wolfram Language, and in Version 11.3 one addition is system process data, of the sort that you might get from a Unix ps command:

✕ SystemProcessData[]

Needless to say, you can do very detailed searches for processes with specific properties. You can also use SystemProcesses to get an explicit list of ProcessObject symbolic objects, which you can interrogate and manipulate (for example, by using KillProcess ).

✕ RandomSample[SystemProcesses[], 3]

Of course, because everything is computable, it’s easy to do things like make plots of the start times of processes running on your computer (and, yes, I last rebooted a few days ago):

✕ TimelinePlot[SystemProcessData[][All, "StartTime"]]

If you want to understand what’s going on around your computer, Version 11.3 provides another powerful tool: NetworkPacketRecording . You may have to do some permissions setup, but then this function can record network packets going through any network interface on your computer.

Here’s just 0.1 seconds of packets going in and out of my computer as I quietly sit here writing this post:

✕ NetworkPacketRecording[.1]

You can drill down to look at each packet; here’s the first one that was recorded:

✕ NetworkPacketRecording[.1][[1]]

Why is this interesting? Well, I expect to use it for debugging quite regularly—and it’s also useful for studying computer security, not least because you can immediately feed everything into standard Wolfram Language visualization, machine learning and other functionality.

What Has Not Been Mentioned

This is already a long post—but there are lots of other things in 11.3 that I haven’t even mentioned. For example, there’ve been all sorts of updates for importing and exporting. Like much more efficient and robust XLS , CSV , and TSV import. Or export of animated PNG s. Or support for metadata in sound formats like MP3 and WAV . Or more sophisticated color quantization in GIF , TIFF , etc. [Livestreamed design discussions 1 and 2.]

We introduced symbolic Audio objects in 11.0, and we’ve been energetically developing audio functionality ever since. Version 11.3 has made audio capture more robust (and supported it for the first time on Linux). It’s also introduced functions like AudioPlay , AudioPause and AudioStop that control open AudioStream objects.

Also new is AudioDistance , which supports various distance measures for audio. Meanwhile, AudioIntervals can now automatically break audio into sections that are separated by silence. And, in a somewhat different area, $VoiceStyles gives the list of possible voices available for SpeechSynthesize .

Here’s a little new math function—that in this case gives a sequence of 0s and 1s in which every length-4 block appears exactly once:

✕ DeBruijnSequence[{0, 1}, 4]

The Wolfram Language now has sophisticated support for quantities and units—both explicit quantities (like 2.5 kg) and symbolic “quantity variables” (“p which has units of pressure”). But once you’re inside, doing something like solving an equation, you typically want to “factor the units out”. And in 11.3 there’s now a function that systematically does this: NondimensionalizationTransform . There’s also a new mechanism in 11.3 for introducing new kinds of quantities, using IndependentPhysicalQuantity .

Much of the built-in Wolfram Knowledgebase is ultimately represented in terms of entity stores, and in Version 11 we introduced an explicit EntityStore construct for defining new entity stores. Version 11.3 introduces the function EntityRegister , which lets you register an entity store, so that you can refer to the types of entities it contains just like you would refer to built-in types of entities (like cities or chemicals).

Another thing that’s being introduced as an experiment in Version 11.3 is the MongoLink package, which supports connection to external MongoDB databases. We use MongoLink ourselves to manage terabyte-and-beyond datasets for things like machine learning training. And in fact MongoLink is part of our large-scale development effort—whose results will be seen in future versions—to seamlessly support extremely large amounts of externally stored data.

In Version 11.2 we introduced ExternalEvaluate to run code in external languages like Python. In Version 11.3 we’re experimenting with generalizing ExternalEvaluate to control web browsers, by setting up a WebDriver framework. You can give all sorts of commands, both ones that have the same effect as clicking around an actual web browser, and ones that extract things you can see on the page.

Here’s how you can use Chrome (we support both it and Firefox) to open a webpage, then capture it:

✕ ExternalEvaluate["WebDriver-Chrome", {"OpenWebPage" -> "https://www.wolfram.com", "CaptureWebPage"}]//Last

Well, this post is getting long, but there’s certainly more I could say. Here’s a more complete list of functions that are new or updated in Version 11.3:

But to me it’s remarkable how much there is that’s in a .1 release of the Wolfram Language—and that’s emerged in just the few months since the last .1 release. It’s a satisfying indication of the volume of R&D that we’re managing to complete—by building on the whole Wolfram Language technology stack that we’ve created. And, yes, even in 11.3 there are a great many new corners to explore. And I hope that lots of people will do this, and will use the latest tools we’ve created to discover and invent all sorts of new and important things in the world.

To comment, please visit the copy of this post at the Wolfram Blog »