Build Your Next Project with Wolfram Alpha API and Python
Tags: python, tutorial, api, webdev
Published: 2020-11-02 17:28:53


Anybody who at some point struggled with math knows Wolfram Alpha and was probably saved by its ability to solve any equation, plot any function or visualize logic circuits. Wolfram Alpha can; however, do much more than that, including chemistry, linguistics or history and most importantly - it can give you all the answers using its public API. So, in this article we will explore how you can use it to answer simple questions, solve mathematical problems, render plots or even describe DNA sequences!

## Setting Up

Wolfram Alpha API is free (for non-commercial usage), but we still need to get API key (AppID) to perform queries against the API endpoints. To get API key we will first create Wolfram ID at <https://account.wolfram.com/login/create>. When account is created we can navigate to <https://developer.wolframalpha.com/portal/myapps/index.html>, click on the _Sign up to get your first AppID_ button, fill out _Get a New AppID_ dialog. After that we will be presented with API key which is called _AppID_ here, which we can test out with following sample query:

```python
from pprint import pprint
import requests
import os
import urllib.parse

appid = os.getenv('WA_APPID', '...')

query = urllib.parse.quote_plus("lifespan of a mosquito")
query_url = f"http://api.wolframalpha.com/v2/query?" \
             f"appid={appid}" \
             f"&input={query}" \
             f"&format=plaintext" \
             f"&output=json"

r = requests.get(query_url).json()

data = r["queryresult"]["pods"][0]["subpods"][0]
datasource = ", ".join(data["datasources"]["datasource"])
microsource = data["microsources"]["microsource"]
plaintext = data["plaintext"]

print(f"Result: '{plaintext}' from {datasource} ({microsource}).")
# Result: '(9.2 to 11, 52 to 60) days' from AmazingNumbers, TheWikimediaFoundationIncWikipedia (SpeciesData).
```

The above code uses _Wolfram|Alpha Full Results API_ to find out what is lifespan of mosquito. To do so, it makes `GET` request to `http://api.wolframalpha.com/v2/query` with parameters specifying our AppID for authentication, question in the `input` field, format parameter as `plaintext` (instead of image) and finally, output type which is JSON (instead of default XML).

We traverse the result as dictionary, looking for interesting fields to print out. The returned JSON (or XML) can be pretty complex and really the easiest way to parse it, is to print it and look for any useful fields in there. Trimmed down example of response for above query would look something like this: 

```python
{'queryresult': {'error': False,
                 'pods': [{'error': False,
                           'expressiontypes': {'name': 'Default'},
                           'id': 'Result',
                           'numsubpods': 1,
                           'position': 100,
                           'primary': True,
                           'scanner': 'Data',
                           'subpods': [{'datasources': {'datasource': ['AmazingNumbers',
                                                                       'TheWikimediaFoundationIncWikipedia']},
                                        'microsources': {'microsource': 'SpeciesData'},
                                        'plaintext': '(9.2 to 11, 52 to 60) '
                                                     'days',
                                        'title': ''}],
                           'title': 'Result'}],
                 'success': True}}
```

## Doing Math

The most useful part of Wolfram Alpha (in my opinion) is the ability to solve complex mathematical problems. So, let's try using the API to do some math:
    
```python
equation = "7 + 2x = 12 - 3x"
query = urllib.parse.quote_plus(f"solve {equation}")
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input={query}" \
            f"&includepodid=Result" \
            f"&output=json"

r = requests.get(query_url).json()

data = r["queryresult"]["pods"][0]["subpods"][0]
plaintext = data["plaintext"]

print(f"Result of {equation} is '{plaintext}'.")
# Result of 7 + 2x = 12 - 3x is 'x = 1'.
```

The _Full Results API_ shown in previous section doesn't just answer some curious questions, but it also does math (and really almost everything). To make the API aware that we want to do math - in this case - solve equation, we need to prepend the actual equation with word `solve`. Apart from the input itself, the query looks very similar to the one in previous section. One extra thing that's added to this one is `includepodid` parameter, which tells the API that in this case we only want `Result` pod to be included in the response. _"What is pod, though?"_, you may ask.

Every result from Wolfram Alpha (both website and API), generally includes multiple categories of data. This can be for example graph, image, step-by-step solution or table. Each of these belongs to its own section called _pod_, which in turn includes _subpods_ which hold individual pieces of data. Considering that each response can sometimes include even 10 or so pods, it's desirable to describe what we want to receive from the API. To do that - `podtitle` and more robust `includepodid` - parameters can be used, as shown above.

The filtered response for above query would therefore look like so:

```python
{'queryresult': {'pods': [{'error': False,
                           'expressiontypes': {'name': 'Default'},
                           'id': 'Result',
                           'numsubpods': 1,
                           'position': 100,
                           'primary': True,
                           'scanner': 'Solve',
                           'states': [{'input': 'Result__Step-by-step solution',
                                       'name': 'Step-by-step solution',
                                       'stepbystep': True}],
                           'subpods': [{'img': {'alt': 'x = 1',
                                                'src': 'https://www5a.wolframalpha.com/Calculate/MSP/MSP31791g2156fih7b062ii00002a9h8g45379f9127?MSPStoreType=image/gif&s=50',
                                                'themes': '1,2,3,4,5,6,7,8,9,10,11,12',
                                                'title': 'x = 1'},
                                        'plaintext': 'x = 1',
                                        'title': ''}],
                           'title': 'Result'}]}}
```

And for comparison same (but graphical) result, but with all the pods would look like this:

![Result with Input, Result, Plot and Number line pods](https://dev-to-uploads.s3.amazonaws.com/i/j9f9odrbypun8vleqqwn.png)
    
Wolfram Alpha can be great learning resource thanks to ability to show not just results, but all the computation steps and API can do that too. To get step-by-step solution for a query, we need to include another parameter called `podstate`, which specifies pod state change. This will replace the original pod with modified version, which can be - for example - more digits for decimal number like Pi (`DecimalApproximation`), extended weather data (more days/months/years) or as in our case - steps to equation solution:

```python
equation = "7 + 2x = 12 - 3x"
query = urllib.parse.quote_plus(f"solve {equation}")
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input={query}" \
            f"&scanner=Solve" \
            f"&podstate=Result__Step-by-step+solution" \
            "&format=plaintext" \
            f"&output=json"

r = requests.get(query_url).json()

data = r["queryresult"]["pods"][0]["subpods"]
result = data[0]["plaintext"]
steps = data[1]["plaintext"]

print(f"Result of {equation} is '{result}'.\n")
print(f"Possible steps to solution:\n\n{steps}")

# Result of 7 + 2x = 12 - 3x is 'x = 1'.
#
# Possible steps to solution:
#
# Solve for x:
# 2 x + 7 = 12 - 3 x
# Add 3 x to both sides:
# 3 x + 2 x + 7 = (3 x - 3 x) + 12
# 3 x - 3 x = 0:
# 3 x + 2 x + 7 = 12
# Grouping like terms, 3 x + 2 x + 7 = (2 x + 3 x) + 7:
# (2 x + 3 x) + 7 = 12
# 2 x + 3 x = 5 x:
# 5 x + 7 = 12
# Subtract 7 from both sides:
# 5 x + (7 - 7) = 12 - 7
# 7 - 7 = 0:
# 5 x = 12 - 7
# 12 - 7 = 5:
# 5 x = 5
# Divide both sides of 5 x = 5 by 5:
# (5 x)/5 = 5/5
# 5/5 = 1:
# x = 5/5
# 5/5 = 1:
# Answer: x = 1
```

## Rendering Mathematical Markdown

One neat trick you can use Wolfram Alpha API for is to render MathML. If you're not familiar with MathML, then here's quick rundown - [MathML](https://www.w3.org/Math/whatIsMathML.html) stands for _Mathematical Markup Language_, it's an XML based format for rendering _LaTeX_-like mathematical expressions in web browsers. Even though this format has been around for a long time, it's only really supported by Firefox, which might make it seem like a bad choice, but JavaScript display engine called [MathJax.js](https://github.com/mathjax/MathJax) makes it possible to render MathML in any browser, which makes it a good choice if you want to show really nice looking complex formulas in _LaTeX_ format in your web application or blog. With that said, let's query it!

```python
equation = "7 + 2x = 12 - 3x"
query = urllib.parse.quote_plus(f"solve {equation}")
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input={query}" \
            f"&scanner=Solve" \
            f"&podstate=Result__Step-by-step+solution" \
            "&format=mathml" \
            f"&output=json"

r = requests.get(query_url).json()

data = r["queryresult"]["pods"][0]["subpods"]
result = data[0]["mathml"]
steps = data[1]["mathml"]

print(f"MathML result of {equation} is:\n")
print(f"{result}")
print(f"Possible steps to solution:\n\n{steps}")

# MathML result of 7 + 2x = 12 - 3x is:
#
# <math xmlns='http://www.w3.org/1998/Math/MathML'
#     mathematica:form='StandardForm'
#     xmlns:mathematica='http://www.wolfram.com/XML/'>
#  <mrow>
#   <mi>x</mi>
#   <mo>=</mo>
#   <mn>1</mn>
#  </mrow>
# </math>
#
# Possible steps to solution:
# <math xmlns='http://www.w3.org/1998/Math/MathML'
#     mathematica:form='StandardForm'
#     xmlns:mathematica='http://www.wolfram.com/XML/'>
#  <mtable displaystyle='true'>
#   <mtr>
#     ...
#   </mtr>
#  </mtable>
# </math>
```

We again use the example from previous section for querying equation with step-by-step solution, but substituting `format=plaintext` for `format=mathml` and similarly replacing all other occurrences of `plaintext` with `mathml`, e.g. `result = data[0]["mathml"]`. Above you can see little bit of the MathML output that was given by the API, it's very trimmed as the full output is veeery long, but I would encourage you to try running the query yourself, pass it through MathJax.js and enjoy the beautiful _LaTeX_ math expressions.

## Solving Boolean Algebra

Another pretty common use case for Wolfram Alpha is solving boolean algebra. Same as with basic equations, all we need to do is pass the formula to the API and this is what we will get back:

```python
formula = "((P AND (Q IMPLIES R)) OR S) AND T"
query = urllib.parse.quote_plus(f"solve {formula}")
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input=solve {formula}" \
            f"&output=json" \
            f"&includepodid=Input" \
            f"&includepodid=MinimalForms" \
            f"&includepodid=TruthDensity"

r = requests.get(query_url).json()

pods = r["queryresult"]["pods"]
expression = pods[0]["subpods"][0]["plaintext"]
min_forms = "\n".join(pods[1]["subpods"][0]["plaintext"].split("\n")[:-1])
truth_density = pods[2]["subpods"][0]["plaintext"].split("=")

print(f"Expression {expression}: \n")
print(f"{min_forms}\n")
print(f"Truth density equals {truth_density[0]} which is {truth_density[1]}")

# Expression ((P ∧ (Q implies R)) ∨ S) ∧ T:
#
# DNF  | (P ∧ ¬Q ∧ T) ∨ (P ∧ R ∧ T) ∨ (S ∧ T)
# CNF  | (P ∨ S) ∧ (¬Q ∨ R ∨ S) ∧ T
# ANF  | (P ∧ T) ⊻ (S ∧ T) ⊻ (P ∧ Q ∧ T) ⊻ (P ∧ S ∧ T) ⊻ (P ∧ Q ∧ R ∧ T) ⊻ (P ∧ Q ∧ S ∧ T) ⊻ (P ∧ Q ∧ R ∧ S ∧ T)
# NOR  | (P ⊽ S) ⊽ (¬Q ⊽ R ⊽ S) ⊽ ¬T
# NAND | (P ⊼ ¬Q ⊼ T) ⊼ (P ⊼ R ⊼ T) ⊼ (S ⊼ T)
# AND  | ¬(¬P ∧ ¬S) ∧ ¬(Q ∧ ¬R ∧ ¬S) ∧ T
# OR   | ¬(¬P ∨ Q ∨ ¬T) ∨ ¬(¬P ∨ ¬R ∨ ¬T) ∨ ¬(¬S ∨ ¬T)
#
# Truth density equals 11/32 which is 34.375%
```

The query above doesn't really show anything new except for specific pods we selected - `Input`, `MinimalForms` and `TruthDensity`. After parsing data in these 3 pods, we can see the output which includes nicer form of submitted input formula, it's other computed forms (CNF, DNF...) as well as truth density both as fraction and as percentage.

## Rendering & Downloading Plots

One of my favourite things to do with Wolfram Alpha has always been rendering complicated plots. Implementing this doesn't even require much of a code change, the only real change is selection of pods and their fields:

```python
function = "sin x cos y"
query = f"plot {function}"
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input={query}" \
            f"&output=json" \
            f"&includepodid=3DPlot" \
            f"&includepodid=ContourPlot"

r = requests.get(query_url).json()

pods = r["queryresult"]["pods"]
plot_3d_url = pods[0]["subpods"][0]["img"]["src"]
plot_contour_url = pods[1]["subpods"][0]["img"]["src"]

img_name = "3d_plot.jpg"
img_data = requests.get(plot_3d_url).content
with open(img_name, 'wb') as handler:
    handler.write(img_data)
    print(f"3D Plot Image Saved to {img_name}.")
```

In this case we use `includepodid` parameter to select `3DPlot` and `ContourPlot` pods which hold URLs to images of the respective plots in their `img.src` fields. These plots can be then downloaded and written in binary mode producing following image:

![3D Plot](https://dev-to-uploads.s3.amazonaws.com/i/kiznhyy50pvm46gbsb9t.jpg)

## Not Just Math

At this point, I think we've seen enough applications for Wolfram Alpha and math. So, what else we can do? As a simple example, let's explore some DNA sequence:

```python
query = "AAGCTAGCTAGC"
query_url = f"http://api.wolframalpha.com/v2/query?" \
            f"appid={appid}" \
            f"&input={query}" \
            f"&scanner=Genome" \
            f"&output=json" \
            f"&includepodid=Length" \
            f"&includepodid=AminoAcidSequence" \
            f"&includepodid=MeltingTemperature" \

r = requests.get(query_url).json()

pods = r["queryresult"]["pods"]

length = {
    "title": pods[0]["title"],
    "value": pods[0]["subpods"][0]["plaintext"]
}
amino_sequence = {
    "title": pods[1]["title"],
    "value": pods[1]["subpods"][0]["plaintext"].replace(") ", ")\n")
}
melting_temp = {
    "title": pods[2]["title"],
    "value": pods[2]["subpods"][0]["plaintext"]
}

print(f"{length['title']}: {length['value']}\n")
print(f"{amino_sequence['title']}:\n {amino_sequence['value']}\n")
print(f"{melting_temp['title']}: {melting_temp['value']}")

# Length: 12 base pairs
#
# Amino acid sequence:
#  (5'-3' frame 1)
# | AAG | CUA | GCU | AGC
#    ↓  |  ↓  |  ↓  |  ↓
#   Lys | Leu | Ala | Ser
#
# Oligonucleotide melting temperature: 48.5 °C (degrees Celsius)
# (under standard PCR conditions)
```

We're again using same API endpoint, but this time we submit string representing DNA sequence. You might notice that we didn't need to include keyword before the query like earlier with `solve` or `plot`. Well, this time instead of using keyword, we added `scanner` parameter, which specifies the subject area of search query, which in this case is `Genome`. To find which scanner will return relevant data for some query, it's easiest to run the query without `scanner` parameter and look for the `scanner` attribute of each pod that has the expected data.

DNA sequences are obviously not the only cool thing that you can search for. Some of the other interesting topics can be for example history, chemistry, transportation or music - just to name a few.

## Natural Language (Spoken) Answers

If you only look for information, then the examples and APIs in previous examples are sufficient. However, if you want/need more natural sounding answer to your queries, then you can use _Spoken Results API_:
    
```python
question = "what is the most spoken language in the world?"
query_url = f"http://api.wolframalpha.com/v1/spoken?" \
            f"appid={appid}" \
            f"&i={question}" \

r = requests.get(query_url)

print(r.text)
# The most spoken language by total number of speakers is English. The total number of English speakers is about 1.3 billion people
```

Queries for the `v1/spoken` endpoint are very simple. All we need to pass in is an AppID and our questions in the `i` parameter. This produces response in form of a full sentence rather than plain fact. This can be quite useful when building clever chat bots or maybe as an input for text-to-speech engine.

## Chatting with Conversation API

As kind of an extension to _Spoken Results API_, Wolfram Alpha also provides _Conversational API_, which allows you to ask follow up questions and therefore have conversation with the API. So, let's try it out and ask Wolfram Alpha something:

```python
question = "Where are Falkland Islands?"
location = "47.01,16.93"
query_url = f"http://api.wolframalpha.com/v1/conversation.jsp?" \
            f"appid={appid}" \
            f"&geolocation={appid}" \
            f"&i={question}" \

r = requests.get(query_url).json()
answer = r["result"]
conversation_id = r["conversationID"]
host = r["host"]

print(f"{question}: '{answer}'")

followup_question = "How far is it from here?"
query_url = f"http://{host}/api/v1/conversation.jsp?" \
            f"appid={appid}" \
            f"&conversationID={conversation_id}" \
            f"&i={followup_question}" \

r = requests.get(query_url).json()
answer = r["result"]
print(f"{followup_question}: '{answer}'")


# Where are Falkland Islands?: 'The Falkland Islands is in South America.'
# How far is it from here?: 'The answer is about 13217 kilometers.'
```

Considering that we want to ask multiple questions, we also have to make multiple queries. First of them is directed at `v1/conversation` endpoint and includes a questions in the `i` parameter. We also specify our location with `geolocation` parameter - this is one of the optional values (other are `ip` and `units`) that can provide context for the questions. This first request pretty much does the same thing as _Spoken Results API_, meaning that it returns information in form of full sentence.

The fun starts when we ask followup questions. To do so, we make another query, this time however, we send it to the host that was provided as part of the response to first query (`host = r["host"]`). The first response also included `conversationID` which we also have to pass in for API to know what was said prior to that.

The second query then returns same type of result as the first one, which allows us to keep on asking more questions using provided `conversationID` and `host`.

One last thing I want to highlight here is how the the questions and answers can nicely flow and use context. In this particular example, we used _"How far is it from here?"_ as followup question, without actually specifying what _"it"_ or _"here"_ really is. These information were automatically inferred from previous question and `geolocation` parameter.

## Conclusion

This article is really just a tip of the iceberg as far as information available from Wolfram Alpha goes. These APIs can be used for much more than just some math and I think it's great starting point for building cool bots, smart devices or voice-based applications. Even though it's not free for commercial use, you still get 2000 requests per month which is plenty to start some cool project. I recommend checking out <https://products.wolframalpha.com/api/> for documentation for each API endpoint as well as [Wolfram Alpha landing page](https://www.wolframalpha.com/) which shows all the different topics that might give you some idea as to what you could build with it. &#128521;