diff --git a/docs/conf.py b/docs/conf.py
index 169414e6c..4083653fa 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,3 +1,4 @@
+
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
@@ -28,7 +29,6 @@
 import projectq.setups.default
 import projectq.setups.grid
 import projectq.setups.ibm
-import projectq.setups.ibm16
 import projectq.setups.linear
 import projectq.setups.restrictedgateset
 import projectq.setups.decompositions
diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py
index 108e224fa..96c001ffe 100755
--- a/examples/bellpair_circuit.py
+++ b/examples/bellpair_circuit.py
@@ -1,13 +1,20 @@
+import matplotlib.pyplot as plt
+
 from projectq import MainEngine
 from projectq.backends import CircuitDrawer
+from projectq.setups.default import get_engine_list
+from projectq.libs.hist import histogram
 
 from teleport import create_bell_pair
 
 # create a main compiler engine
 drawing_engine = CircuitDrawer()
-eng = MainEngine(drawing_engine)
+eng = MainEngine(engine_list = get_engine_list() + [drawing_engine])
 
-create_bell_pair(eng)
+qb0, qb1 = create_bell_pair(eng)
 
 eng.flush()
 print(drawing_engine.get_latex())
+
+histogram(eng.backend, [qb0, qb1])
+plt.show()
diff --git a/examples/ibm.py b/examples/ibm.py
index 11a81a832..eafd50b80 100755
--- a/examples/ibm.py
+++ b/examples/ibm.py
@@ -1,8 +1,11 @@
-import projectq.setups.ibm
+import matplotlib.pyplot as plt
+import getpass
+
+from projectq import MainEngine
 from projectq.backends import IBMBackend
+from projectq.libs.hist import histogram
 from projectq.ops import Measure, Entangle, All
-from projectq import MainEngine
-import getpass
+import projectq.setups.ibm
 
 
 def run_entangle(eng, num_qubits=3):
@@ -29,9 +32,12 @@ def run_entangle(eng, num_qubits=3):
     eng.flush()
 
     # access the probabilities via the back-end:
-    results = eng.backend.get_probabilities(qureg)
-    for state in results:
-        print("Measured {} with p = {}.".format(state, results[state]))
+    # results = eng.backend.get_probabilities(qureg)
+    # for state in results:
+    #     print("Measured {} with p = {}.".format(state, results[state]))
+    # or plot them directly:
+    histogram(eng.backend, qureg)
+    plt.show()
 
     # return one (random) measurement outcome.
     return [int(q) for q in qureg]
@@ -39,11 +45,11 @@ def run_entangle(eng, num_qubits=3):
 
 if __name__ == "__main__":
     #devices commonly available :
-    #ibmq_16_melbourne (15 qubit)
-    #ibmq_essex (5 qubit)
-    #ibmq_qasm_simulator (32 qubits)
-    device = None #replace by the IBM device name you want to use
-    token = None  #replace by the token given by IBMQ
+    # ibmq_16_melbourne (15 qubit)
+    # ibmq_essex (5 qubit)
+    # ibmq_qasm_simulator (32 qubits)
+    device = None # replace by the IBM device name you want to use
+    token = None  # replace by the token given by IBMQ
     if token is None:
         token = getpass.getpass(prompt='IBM Q token > ')
     if device is None:
diff --git a/examples/ibmq_tutorial.ipynb b/examples/ibmq_tutorial.ipynb
index 464612ca5..358bdcf9e 100644
--- a/examples/ibmq_tutorial.ipynb
+++ b/examples/ibmq_tutorial.ipynb
@@ -2,14 +2,18 @@
  "cells": [
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "# Running ProjectQ code on IBM Q devices"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "In this tutorial, we will see how to run code on IBM Q devices directly from within ProjectQ. All that is needed is an IBM Q Experience user account. To sign up, visit https://quantumexperience.ng.bluemix.net/.\n",
     "\n",
@@ -19,10 +23,24 @@
     "First, we import all necessary operations (`Entangle`, measurement), the back-end (`IBMBackend`), and the main compiler engine (`MainEngine`). The Entangle operation is defined as a Hadamard gate on the first qubit (creates an equal superposition of |0> and |1>), followed by controlled NOT gates acting on all other qubits controlled on the first."
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "%matplotlib inline\n",
+    "import matplotlib.pyplot as plt"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [],
    "source": [
     "import projectq.setups.ibm\n",
@@ -33,7 +51,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "Next, we instantiate a main compiler engine using the IBM Q back-end and the predefined compiler engines which take care of the qubit placement, translation of operations, etc.:"
    ]
@@ -41,7 +61,9 @@
   {
    "cell_type": "code",
    "execution_count": 2,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [],
    "source": [
     "eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,\n",
@@ -51,7 +73,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "If `use_hardware` is set to `False`, it will use the IBM Q simulator instead. `num_runs` specifies the number of samples to collect for statistics, `verbose=True` would output additional information which may be helpful for debugging, and the device parameter lets users choose between the two devices (\"ibmqx4\" and \"ibmqx5\").\n",
     "\n",
@@ -61,7 +85,9 @@
   {
    "cell_type": "code",
    "execution_count": 3,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -105,9 +131,12 @@
     "    eng.flush()\n",
     "\n",
     "    # access the probabilities via the back-end:\n",
-    "    results = eng.backend.get_probabilities(qureg)\n",
-    "    for state in results:\n",
-    "        print(\"Measured {} with p = {}.\".format(state, results[state]))\n",
+    "    # results = eng.backend.get_probabilities(qureg)\n",
+    "    # for state in results:\n",
+    "    #     print(\"Measured {} with p = {}.\".format(state, results[state]))\n",
+    "    # or plot them directly:\n",
+    "    histogram(eng.backend, qureg)\n",
+    "    plt.show()\n",
     "\n",
     "    # return one (random) measurement outcome.\n",
     "    return [int(q) for q in qureg]\n",
@@ -117,7 +146,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "## Retrieving a timed-out execution\n",
     "Sometimes, the queue is very long and the waiting times may exceed the limit of 5 minutes. In this case, ProjectQ will raise an exception which contains the job ID, as could be seen above, where the job ID was `5b557df2306393003b746da2`."
@@ -125,7 +156,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "In order to still retrieve all results at a later point in time, one can simply re-run the entire program using a slightly modified back-end:"
    ]
@@ -133,7 +166,9 @@
   {
    "cell_type": "code",
    "execution_count": 4,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -193,7 +228,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "## Entangling more qubits: Using ibmqx5\n",
     "\n",
@@ -209,7 +246,9 @@
   {
    "cell_type": "code",
    "execution_count": 5,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [],
    "source": [
     "import projectq.setups.ibm16  # import setup which contains the grid mapper\n",
@@ -220,7 +259,9 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "source": [
     "and then re-run the example from before via `run_entangle(eng, num_qubits)`. If an execution times out, it can also be retrieved at a later point by providing the additional `retrieve_execution=\"execution_id\"` parameter to the IBMBackend (but this time with `device='ibmqx5'`)."
    ]
@@ -228,7 +269,9 @@
   {
    "cell_type": "code",
    "execution_count": 6,
-   "metadata": {},
+   "metadata": {
+    "collapsed": false
+   },
    "outputs": [
     {
      "name": "stdout",
@@ -488,8 +531,18 @@
  ],
  "metadata": {
   "kernelspec": {
+   "argv": [
+    "python",
+    "-m",
+    "ipykernel_launcher",
+    "-f",
+    "{connection_file}"
+   ],
    "display_name": "Python 3",
+   "env": null,
+   "interrupt_mode": "signal",
    "language": "python",
+   "metadata": null,
    "name": "python3"
   },
   "language_info": {
@@ -503,7 +556,8 @@
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
    "version": "3.6.5"
-  }
+  },
+  "name": "ibmq_tutorial.ipynb"
  },
  "nbformat": 4,
  "nbformat_minor": 2
diff --git a/projectq/libs/hist/__init__.py b/projectq/libs/hist/__init__.py
new file mode 100644
index 000000000..088766263
--- /dev/null
+++ b/projectq/libs/hist/__init__.py
@@ -0,0 +1,20 @@
+#   Copyright 2020 ProjectQ-Framework (www.projectq.ch)
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+"""
+contains a function to plot measurement outcome probabilities
+as a histogram for the simulator
+"""
+
+from ._histogram import histogram
diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py
new file mode 100644
index 000000000..77b0d2c20
--- /dev/null
+++ b/projectq/libs/hist/_histogram.py
@@ -0,0 +1,78 @@
+#   Copyright 2020 ProjectQ-Framework (www.projectq.ch)
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from __future__ import print_function
+import matplotlib.pyplot as plt
+
+from projectq.backends import Simulator
+
+
+def histogram(backend, qureg):
+    """
+    Make a measurement outcome probability histogram for the given qubits.
+
+    Args:
+        backend (BasicEngine): A ProjectQ backend
+        qureg (list of qubits and/or quregs): The qubits,
+            for which to make the histogram
+
+    Returns:
+        A tuple (fig, axes, probabilities), where:
+        fig: The histogram as figure
+        axes: The axes of the histogram
+        probabilities (dict): A dictionary mapping outcomes as string
+            to their probabilities
+
+    Note:
+        Don't forget to call eng.flush() before using this function.
+    """
+    qubit_list = []
+    for q in qureg:
+        if isinstance(q, list):
+            qubit_list.extend(q)
+        else:
+            qubit_list.append(q)
+
+    if len(qubit_list) > 5:
+        print('Warning: For {0} qubits there are 2^{0} different outcomes'.
+              format(len(qubit_list)))
+        print("The resulting histogram may look bad and/or take too long.")
+        print("Consider calling histogram() with a sublist of the qubits.")
+
+    if hasattr(backend, 'get_probabilities'):
+        probabilities = backend.get_probabilities(qureg)
+    elif isinstance(backend, Simulator):
+        outcome = [0] * len(qubit_list)
+        n_outcomes = (1 << len(qubit_list))
+        probabilities = {}
+        for i in range(n_outcomes):
+            for pos in range(len(qubit_list)):
+                if (1 << pos) & i:
+                    outcome[pos] = 1
+                else:
+                    outcome[pos] = 0
+            probabilities[''.join([str(bit) for bit in outcome
+                                   ])] = backend.get_probability(
+                                       outcome, qubit_list)
+    else:
+        raise RuntimeError('Unable to retrieve probabilities from backend')
+
+    # Empirical figure size for up to 5 qubits
+    fig, axes = plt.subplots(figsize=(min(21.2, 2
+                                          + 0.6 * (1 << len(qubit_list))), 7))
+    names = list(probabilities.keys())
+    values = list(probabilities.values())
+    axes.bar(names, values)
+    fig.suptitle('Measurement Probabilities')
+    return (fig, axes, probabilities)
diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py
new file mode 100644
index 000000000..1f38573b0
--- /dev/null
+++ b/projectq/libs/hist/_histogram_test.py
@@ -0,0 +1,135 @@
+#   Copyright 2020 ProjectQ-Framework (www.projectq.ch)
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pytest
+import matplotlib
+import matplotlib.pyplot as plt
+
+from projectq import MainEngine
+from projectq.ops import H, C, X, Measure, All, AllocateQubitGate, FlushGate
+from projectq.cengines import DummyEngine, BasicEngine
+from projectq.backends import Simulator
+from projectq.libs.hist import histogram
+
+
+@pytest.fixture(scope="module")
+def matplotlib_setup():
+    old_backend = matplotlib.get_backend()
+    matplotlib.use('agg')  # avoid showing the histogram plots
+    yield
+    matplotlib.use(old_backend)
+
+
+def test_invalid_backend(matplotlib_setup):
+    eng = MainEngine(backend=DummyEngine())
+    qubit = eng.allocate_qubit()
+    eng.flush()
+
+    with pytest.raises(RuntimeError):
+        histogram(eng.backend, qubit)
+
+
+def test_backend_get_probabilities_method(matplotlib_setup):
+    class MyBackend(BasicEngine):
+        def get_probabilities(self, qureg):
+            return {'000': 0.5, '111': 0.5}
+
+        def is_available(self, cmd):
+            return True
+
+        def receive(self, command_list):
+            for cmd in command_list:
+                if not isinstance(cmd.gate, FlushGate):
+                    assert isinstance(cmd.gate, AllocateQubitGate)
+
+    eng = MainEngine(backend=MyBackend(), verbose=True)
+    qureg = eng.allocate_qureg(3)
+    eng.flush()
+    _, _, prob = histogram(eng.backend, qureg)
+    assert prob['000'] == 0.5
+    assert prob['111'] == 0.5
+
+
+def test_qubit(matplotlib_setup):
+    sim = Simulator()
+    eng = MainEngine(sim)
+    qubit = eng.allocate_qubit()
+    eng.flush()
+    _, _, prob = histogram(sim, qubit)
+    assert prob["0"] == pytest.approx(1)
+    assert prob["1"] == pytest.approx(0)
+    H | qubit
+    eng.flush()
+    _, _, prob = histogram(sim, qubit)
+    assert prob["0"] == pytest.approx(0.5)
+    Measure | qubit
+    eng.flush()
+    _, _, prob = histogram(sim, qubit)
+    assert prob["0"] == pytest.approx(1) or prob["1"] == pytest.approx(1)
+
+
+def test_qureg(matplotlib_setup):
+    sim = Simulator()
+    eng = MainEngine(sim)
+    qureg = eng.allocate_qureg(3)
+    eng.flush()
+    _, _, prob = histogram(sim, qureg)
+    assert prob["000"] == pytest.approx(1)
+    assert prob["110"] == pytest.approx(0)
+    H | qureg[0]
+    C(X, 1) | (qureg[0], qureg[1])
+    H | qureg[2]
+    eng.flush()
+    _, _, prob = histogram(sim, qureg)
+    assert prob["110"] == pytest.approx(0.25)
+    assert prob["100"] == pytest.approx(0)
+    All(Measure) | qureg
+    eng.flush()
+    _, _, prob = histogram(sim, qureg)
+    assert prob["000"] == pytest.approx(1) or prob["001"] == pytest.approx(1) \
+        or prob["110"] == pytest.approx(1) or prob["111"] == pytest.approx(1)
+    assert prob["000"] + prob["001"] + prob["110"] + prob[
+        "111"] == pytest.approx(1)
+
+
+def test_combination(matplotlib_setup):
+    sim = Simulator()
+    eng = MainEngine(sim)
+    qureg = eng.allocate_qureg(2)
+    qubit = eng.allocate_qubit()
+    eng.flush()
+    _, _, prob = histogram(sim, [qureg, qubit])
+    assert prob["000"] == pytest.approx(1)
+    H | qureg[0]
+    C(X, 1) | (qureg[0], qureg[1])
+    H | qubit
+    Measure | qureg[0]
+    eng.flush()
+    _, _, prob = histogram(sim, [qureg, qubit])
+    assert (prob["000"] == pytest.approx(0.5) and prob["001"] == pytest.approx(0.5)) \
+        or (prob["110"] == pytest.approx(0.5) and prob["111"] == pytest.approx(0.5))
+    assert prob["100"] == pytest.approx(0)
+    Measure | qubit
+
+
+def test_too_many_qubits(matplotlib_setup, capsys):
+    sim = Simulator()
+    eng = MainEngine(sim)
+    qureg = eng.allocate_qureg(6)
+    eng.flush()
+    l_ref = len(capsys.readouterr().out)
+    _, _, prob = histogram(sim, qureg)
+    assert len(capsys.readouterr().out) > l_ref
+    assert prob["000000"] == pytest.approx(1)
+    All(Measure)
diff --git a/pytest.ini b/pytest.ini
index fab634b12..d17d4ce2e 100755
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,3 +4,4 @@ testpaths = projectq
 filterwarnings =
     error
     ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning
+    ignore:Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.
