DEV Community

loading...

Build Your Next Project with Wolfram Alpha API and Python

martinheinz profile image Martin Heinz Originally published at martinheinz.dev ・11 min read

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:

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).
Enter fullscreen mode Exit fullscreen mode

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:

{'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}}
Enter fullscreen mode Exit fullscreen mode

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:

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'.
Enter fullscreen mode Exit fullscreen mode

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:

{'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'}]}}
Enter fullscreen mode Exit fullscreen mode

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

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:

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
Enter fullscreen mode Exit fullscreen mode

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 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 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!

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>
Enter fullscreen mode Exit fullscreen mode

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:

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%
Enter fullscreen mode Exit fullscreen mode

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:

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}.")
Enter fullscreen mode Exit fullscreen mode

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

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:

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)
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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.'
Enter fullscreen mode Exit fullscreen mode

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 which shows all the different topics that might give you some idea as to what you could build with it. 😉

Discussion (0)

pic
Editor guide