{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "view-in-github",
"slideshow": {
"slide_type": "skip"
},
"tags": [
"no-tex"
]
},
"source": [
""
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "JoW4C_OkOMhe",
"outputId": "9699f781-e0d4-4fad-a0a0-7ea90ff91392",
"slideshow": {
"slide_type": "skip"
},
"tags": [
"remove-cell"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install -U -q gtbook"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"id": "10-snNDwOSuC",
"slideshow": {
"slide_type": "skip"
},
"tags": [
"remove-cell"
]
},
"outputs": [],
"source": [
"import numpy as np\n",
"import gtsam\n",
"\n",
"import plotly.express as px\n",
"try:\n",
" import google.colab\n",
"except:\n",
" import plotly.io as pio\n",
" pio.renderers.default = \"png\"\n",
"\n",
"import gtbook\n",
"from gtbook.discrete import Variables\n",
"from gtbook.display import show\n",
"\n",
"def pretty(obj): \n",
" return gtbook.display.pretty(obj, VARIABLES)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "nAvx4-UCNzt2",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"```{index} state; discrete state\n",
"```\n",
"\n",
"# Modeling the World State\n",
"\n",
"> The physical properties of a piece of trash comprise all of the information needed by the robot.\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "nAvx4-UCNzt2",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For our simple trash sorting robot, the only thing that matters at a given moment is the category\n",
"of the item of trash on the conveyor belt. Remember that items of trash are presented individually to the robot,\n",
"so there is no clutter, and no circumstance in which multiple pieces of trash are simultaneously in the workspace.\n",
"Therefore, it is natural to define the world state explicitly in terms of the category of the current\n",
"item of trash.\n",
"\n",
"We consider five possible categories:\n",
"- cardboard\n",
"- paper\n",
"- cans\n",
"- scrap metal\n",
"- bottle"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "notes"
}
},
"source": [
"For simplicity, we assume here that there will never be a piece of trash that does not belong to one of these categories. We do not, however, assume that the category of an item can be reliably determined with 100% accuracy. \n",
"Instead, we use probability theory to quantify the uncertainty associated to an object's categorization."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KcHJG7C-cBeO",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Using Probability to Model Uncertainty\n",
"\n",
"> Probability theory provides a rigorous methodology for reasoning about uncertainty."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KcHJG7C-cBeO",
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We will use probability theory to model uncertainty.\n",
"While a comprehensive coverage of probability theory is beyond the scope of this book,\n",
"we introduce key concepts and methods throughout the text, as needed,\n",
"to deal with various kinds of uncertainty that occur in robotics applications.\n",
"Rigorous introductions can be found in many textbooks, including [Probability for Data Science](https://probability4datascience.com/index.html) (which is available online)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"```{index} sample space",
"```",
"The starting point for reasoning with uncertainty is to define the set of outcomes that might occur.\n",
"The set of all possible outcomes is called the **sample space**, often denoted by $\\Omega.$\n",
"In our example, when an item of trash arrives on the conveyor belt,\n",
"there are five possible outcomes,\n",
"\n",
"$\\Omega = \\{ \\rm{cardboard, paper, cans, scrap \\; metal, bottle}\\}.$"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KcHJG7C-cBeO",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"```{index} event, probability distribution",
"```",
"## Probability Distributions\n",
"A subset of the sample space $\\Omega$ is called an **event**. A **probability distribution**, $P$, assigns a probability $0 \\leq P(A) \\leq 1$ to each event $A \\subseteq \\Omega$, with $P(\\emptyset) = 0$ and $P(\\Omega)=1$. \n",
"In addition, for disjoint events, $A_i \\cap A_j = \\emptyset$, we have\n",
"$P(A_i \\cup A_j) = P(A_i) + P(A_j)$.\n",
"Using this property, it is a simple matter to compute the probability for any $A \\subseteq \\Omega$\n",
"if we are provided with the probabilities of the individual outcomes.\n",
"Further, since $P(\\Omega)=1$, it follows immediately that \n",
"\n",
"$$P(\\Omega) = \\sum_{\\omega \\in \\Omega} P(\\{\\omega\\}) = 1$$\n",
"\n",
"i.e., that the probabilities of the individual outcomes sum to unity.\n",
"As a slight abuse of notation, for singleton events, we will often write $P(\\omega)$ rather than $P(\\{\\omega\\})$\n",
"to simplify notation."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KcHJG7C-cBeO",
"slideshow": {
"slide_type": "notes"
}
},
"source": [
"In robotics applications, the probability assigned to an outcome reflects our certainty in that outcome.\n",
"These probabilities can change based on the arrival of new evidence.\n",
"In robotics, this can occur when the robot acts in the world, or based on sensor data.\n",
"How evidence affects the propagation of probability values is a recurring topic in this book."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rkFiTuQacBeO",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Prior Probability Distributions\n",
"> A *prior* that describes our beliefs before any sensor data is obtained.\n",
"\n",
"Once we have enumerated the set of possible outcomes, we confront a fundamental question: *Where do the\n",
"probability values come from?* In this section we explicitly consider the notion of prior knowledge that is available in a particular application. High-quality \"priors\" can make a big difference in performance, especially when measurements are few or unreliable.\n",
"\n",
"In some cases, we merely assume that all outcomes are equally likely, for example, when rolling a die or tossing coin.\n",
"In such cases, the probability of any outcome is merely $P(\\omega) = 1/N$ for each $\\omega \\in \\Omega$, where $N =| \\Omega |$.\n",
"This leads to $P(\\mathrm{heads}) = P(\\mathrm{tails}) = 0.5$ when tossing a fair coin, \n",
"where $\\Omega = \\{ \\mathrm{heads, tails} \\}$.\n",
"\n",
"In other cases, we can estimate probabilities using data.\n",
"Suppose, for example, that the owner of the trash-sorting facility has told us (or we have kept statistics over time) that for every 1000 pieces of trash, the observed category counts are approximately as follows:\n",
"- cardboard: 200\n",
"- paper: 300\n",
"- cans: 250\n",
"- scrap metal: 200\n",
"- bottle: 50"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rkFiTuQacBeO",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"It is common to assume that outcomes occur in proportion to their probability (there are a number of\n",
"technical conditions that underlie this assumption, such as the condition that outcomes are independent,\n",
"but we will not address these here). Thus, from the above observed frequencies, we might estimate that the probability of seeing a piece of cardboard in the work cell is given by\n",
"\n",
"$$P(\\mathrm{cardboard}) \\approx 200/1000 = 0.2$$\n",
"\n",
"Using the same logic, we can do the same for all categories, yielding:\n",
"\n",
"| *Category (C)* | *P(C)* |\n",
"|----------------|:------:|\n",
"| cardboard | 0.20 |\n",
"| paper | 0.30 |\n",
"| can | 0.25 |\n",
"| scrap metal | 0.20 |\n",
"| bottle | 0.05 |"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rkFiTuQacBeO",
"slideshow": {
"slide_type": "notes"
}
},
"source": [
"```{index} prior",
"```",
"We call this type of probabilistic knowledge about the state of the world, in the absence of any other information, a **prior**, because it represents our belief *before* any evidence (e.g., sensor data) has been acquired.\n",
"\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Kc_UeQX7cBeP",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Probability Distributions in Python\n",
"> We represent probability distributions using the `DiscreteDistribution` class in GTSAM.\n",
"\n",
"The GTSAM toolbox (GTSAM stands for “Georgia Tech Smoothing and\n",
"Mapping”) toolbox is a BSD-licensed C++ library based on factor graphs,\n",
"first developed at the Georgia Institute of Technology. \n",
"It provides state of the art solutions to important problems in robotics,\n",
"such as the Simultaneous Localization and Mapping (SLAM) and Structure from Motion (SfM) problems,\n",
"but can also be used to model and solve both\n",
"simpler and more complex estimation problems. More information is\n",
"available at .\n",
"\n",
"GTSAM also provides both a MATLAB and a python interface,\n",
"enabling rapid prototype development, visualization, and user interaction.\n",
"The python library can be imported directly into a Google colab via\n",
"“import gtsam”. A large subset of the GTSAM functionality can be\n",
"accessed through wrapped classes from within python. \n",
"To not interrupt the flow of the book too much we do not always fully explain the code throughout the text, but rather include a \"GTSAM 101\" sub-section at the end that elaborates on the types and functions we used. \n",
"\n",
"The code below illustrates the use of GTSAM. First we create a `Variables` data structure that will be used to obtain more informative output from other code below:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"id": "zIqYIln4P9Ou",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"VARIABLES = Variables()\n",
"categories = [\"cardboard\", \"paper\", \"can\", \"scrap metal\", \"bottle\"]\n",
"Category = VARIABLES.discrete(\"Category\", categories)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Conceptually, the Variables class keeps track of the names of variables and what values each variable can take on. For example in the above, we need the variable `Category`, and it can take on the values `cardboard`, `paper`, `can`, `scrap metal`, and `bottle`. We do this so that later when we print, it can show us a nicely rendered outputs."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1gEeQUJQP9Ou",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can now create a prior probability $P(Category)$ on the category using a `DiscreteDistribution` constructor:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"id": "vuv1JUsFP9Ov",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"category_prior = gtsam.DiscreteDistribution(Category, \"200/300/250/200/50\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "wzrhXDkzP9Ov",
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The constructor automatically normalizes the numbers given to it to a proper probability distribution, i.e., it makes the probabilities sum to one. It is rendered in notebook as a table below, where we can verify this:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 175
},
"id": "qH0uUczsP9Ow",
"outputId": "c8706115-a1e8-430e-f99b-e31d82e37350",
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"
P(Category):
\n",
"
\n",
"
\n",
" \n",
"
Category
value
\n",
" \n",
" \n",
"
cardboard
0.2
\n",
"
paper
0.3
\n",
"
can
0.25
\n",
"
scrap metal
0.2
\n",
"
bottle
0.05
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pretty(category_prior)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "FjA0nXaSP9Ow",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can evaluate the prior for any category value, e.g., \"can\", but that function does take integer indices, not srings. Hence, we use the built-in python function `index` to obtain that integer (2 in this case):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "r1yKMGu9P9Ow",
"outputId": "a9817da7-938b-49a5-cfd1-9c1debc9f65e",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"P('can') = 0.25\n"
]
}
],
"source": [
"index = categories.index('can') # we still have to use an integer value\n",
"P_can = category_prior(index)\n",
"print(f\"P('can') = {P_can}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jP9H_6aEP9Ow",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can also recover all values in the probability distribution at once, using the `pmf` method:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "51BH3wF4P9Ow",
"outputId": "786197c4-d1c7-4fd1-87e8-9a4326a10996",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[0.2, 0.3, 0.25, 0.2, 0.05]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"PMF = category_prior.pmf()\n",
"PMF"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MrcIXHKac07Q",
"slideshow": {
"slide_type": "notes"
}
},
"source": [
"Here \"pmf\" is short for \"probability mass function\", which we define more precisely in Section 2.2. Note that the ordering of the array was fixed when we defined `categories` above. It is your responsibility\n",
"to maintain consistency when using arrays to store values associated to a collection of variables.\n",
"\n",
"We can display probability distributions in various ways, including as a bar graph, as shown below."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 542
},
"id": "BNJf2OBlP9Ow",
"outputId": "495376af-aae7-4c01-d35d-4a958aa67866",
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"image/png": "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"
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#| caption: A discrete probability distribution as a bar graph.\n",
"#| label: fig:discrete-distribution\n",
"px.bar(y=PMF, x=categories)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Simulation by Sampling\n",
"\n",
"> We can simulate our trash sorting cell by sampling from the prior."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xstYfXw9cBeQ",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Suppose we wish to simulate our trash sorting system such that the behavior of the simulation matches, \n",
"in some statistical sense, the behavior of the actual system.\n",
"In our case, this amounts to generating samples from the probability distribution\n",
"on trash categories.\n",
"In particular, we would like to generate a sequence of categories,\n",
"$\\omega_1, \\omega_2, \\dots, \\omega_n$ such that \n",
"$\\omega_i = \\mathrm{cardboard}$ approximately 25% of the time,\n",
"$\\omega_i = \\mathrm{paper}$ approximately 20% of the time, etc.\n",
"How can we write a computer program to do this?\n",
"\n",
"While most programming libraries do not include functions to generate samples from an arbitrary distribution,\n",
"almost all include a random number generator that will generate a random number from the unit interval.\n",
"We denote by $U(a,b)$ the uniform probability distribution on the interval $[a,b]$.\n",
"In numpy, the function *np.random.rand()* generates a sample $x \\sim U(0,1)$.\n",
"How can we use this result to generate a sample from an arbitrary probability distribution?"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Cumulative Distribution Function\n",
"We begin by introducing the Cumulative Distribution Function (CDF) for a random variable $X$.\n",
"We will more carefully introduce the notion of a discrete random variable in Section 2.2, but for now it\n",
"is sufficient to know that a discrete random variable takes a value from a countable set,\n",
"each of which is assigned a probability value.\n",
"For a random variable $X$, the CDF for $X$ is denoted by $F_X$, and is defined as\n",
"\n",
"$$\n",
"F_X(\\alpha) = P(X \\leq \\alpha)\n",
"$$\n",
"It follows immediately that $0 \\leq F_X(\\alpha) \\leq 1$,\n",
"since $F_X(\\alpha)$ is itself a probability.\n",
"In the case of discrete random variables,\n",
"say $X \\in \\{ x_0, \\dots x_{n-1}\\}$, we can compute the CDF\n",
"$F_X(\\alpha)$ by summing the probabilities assigned\n",
"to all $x_i \\leq \\alpha$\n",
"\n",
"$$\n",
"F_X(\\alpha) = \\sum_{x_i \\leq \\alpha} P(x_i) = \\sum_{i=0}^{k-1} P(x_i)\n",
"$$\n",
"in which the rightmost summation follows if we choose $k$ such that $x_{k-1} \\leq \\alpha < x_k$.\n",
"The terminology *Cumulative Distribution Function* is due to the fact that $F_X(\\alpha)$\n",
"is the accumulated probability assigned to all outcomes less than or equal to $\\alpha$,\n",
"which is apparent in these summation expressions."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"But what does this have to do with generating samples from our distribution on categories?\n",
"The idea is simple: we can generate $x\\sim U(0,1)$, and a CDF takes on values in the\n",
"interval $[0,1]$. For a discrete random variable $X \\in \\{ x_0, \\dots x_{n-1}\\}$\n",
"the probability that our sample $x$ corresponds to category $k$\n",
"is exactly equal to $F_X(x_k) - F_X(x_{k-1})$, and we define $F_X(x_k)=0$ for $k < 0$.\n",
"\n",
"To see this, we impose an ordering on our categories,\n",
"- $c_0 = \\mathrm{cardboard}$\n",
"- $c_1 = \\mathrm{paper}$\n",
"- $c_2 = \\mathrm{can}$\n",
"- $c_3 = \\mathrm{scrap \\; metal}$\n",
"- $c_4 = \\mathrm{bottle}$\n",
"\n",
"and we define the random variable $X \\in \\{ 0,1,2,3,4\\}$ to be the index of the chosen category.\n",
"The CDF for $X$ is given by:\n",
"\n",
"| $k$ | $x_k$ | $F_X$($x_k$) |\n",
"|----|----------------|:------:|\n",
"| 0 | cardboard | 0.20 |\n",
"| 1 | paper | 0.50 |\n",
"| 2 | can | 0.75 |\n",
"| 3 | scrap metal | 0.95 |\n",
"| 4 | bottle | 1.00 |"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Example\n",
"Some numpy code to generate the CDF:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2kUyKEO6c07R",
"outputId": "b310dfef-21f8-48ba-e5d8-088c94cdb978",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0.2 0.5 0.75 0.95 1. ]\n"
]
}
],
"source": [
"CDF = np.cumsum(PMF)\n",
"print(CDF)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mNOqcKEec07R",
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"\n",
"Now, suppose we generate a random sample $x \\sim U(0,1)$,\n",
"and use this $x$ to choose category $k$ such that $F_X(x_{k-1}) < x \\leq F_X(x_k)$.\n",
"For example, we choose category 4 if $0.95 < x \\leq 1.0$.\n",
"In this case, what is the probability of choosing category 4?\n",
"The answer follows from the fact that,\n",
"for the uniform distribution on the unit interval, $P(X \\in [a,b]) = b-a$.\n",
"Therefore, the probability that our sample lies in the interval $[0.95,1.0]$ is\n",
"$0.05$, which happens to be exactly the prior probability assigned to category 4!\n",
"Likewise, if our sample $x$ satisfies $0.2 < x \\leq 0.5$,\n",
"we choose category 1, and the probability that our sample lies in the interval\n",
"$[0.2,0.5]$ is $0.3$, which is, as expected, exactly the prior probability assigned\n",
"to category 1.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "wHwPuUArcBeR",
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The code to accomplish sampling is:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 542
},
"id": "8vDBsvNEcBeR",
"outputId": "55ffca22-626f-4563-a60b-d525bfa56484",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def sample():\n",
" u = np.random.rand()\n",
" for category in range(5):\n",
" if u The GTSAM concepts used in this section, explained."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Above we created an instance of the `gtsam.DiscreteDistribution` class. As with any GTSAM class, you can type\n",
"\n",
"```python\n",
"help(gtsam.DiscreteDistribution)\n",
"```\n",
"\n",
"to get documentation on its constructors and methods. In particular, we called the constructor\n",
"\n",
"```python\n",
" __init__(self: gtsam.DiscreteDistribution, key: Tuple[int, int], spec: str) -> None\n",
" ```\n",
"\n",
"which expects two arguments (besides `self`, which you can ignore):\n",
"- `key`: Many GTSAM objects take a *key* to indicate which variable is involved. In the case of a DiscreteDistribution, the key is actually a tuple of ints:\n",
" - the first int is a 64-bit identifier for the variable;\n",
" - the second int is the *cardinality* of the variable.\n",
"- `spec`: The `DiscreteDistribution` class specifies a PMF (remember: probability mass function) which is given as a string of numbers, separated by `/`.\n",
"\n",
"Let's look at an example below:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"
P(42):
\n",
"
\n",
"
\n",
" \n",
"
42
value
\n",
" \n",
" \n",
"
0
0.4
\n",
"
1
0.1
\n",
"
2
0.5
\n",
" \n",
"
\n",
"
"
],
"text/markdown": [
" *P(42):*\n",
"\n",
"|42|value|\n",
"|:-:|:-:|\n",
"|0|0.4|\n",
"|1|0.1|\n",
"|2|0.5|\n"
],
"text/plain": [
"Discrete Prior\n",
" P( 42 ):\n",
" Choice(42) \n",
" 0 Leaf 0.4\n",
" 1 Leaf 0.1\n",
" 2 Leaf 0.5\n"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prior = gtsam.DiscreteDistribution((42, 3), \"0.4/0.1/0.5\")\n",
"prior\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```{index} decision tree",
"```",
"As you can see, this is a PMF on the variable with id $42$, and it indeed has probabilities (that add up to one) for values `0..2`. Internally, GTSAM *actually* represents a PMF as a small **decision tree**, which you can reveal using `show`:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#| caption: The decision tree representation for a discrete probability distribution.\n",
"#| label: fig:discrete-decision-tree\n",
"show(prior)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, it would be much nicer if we could print out these PMFs in a more readable format, that shows us a name for each variable as well as a pretty name for each. This is where the `Variables` class comes to the rescue. We actually defined a global variable at the top of this notebook, like so:\n",
"\n",
"```python\n",
"VARIABLES = Variables()\n",
"```\n",
"\n",
"which then allows us to give a name to a variable. It will also pick a unique ID for our variable. We can do this with the `discrete` method, which takes a name and a set of value names:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"id": "zIqYIln4P9Ou",
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"key = (1, 3)\n"
]
}
],
"source": [
"T = VARIABLES.discrete(\"TresCommas\", [\"one\", \"two\", \"three\"])\n",
"print(f\"key = {T}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, the id for the variable is 1 (Category, defined above, took id 0), and the cardinality was inferred to be three from the length of the list given as second argument.\n",
"\n",
"The `Variables` class can tell us about any discrete variable so defined, using the two methods `name` and `domain`:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"key = (1, 3), name = TresCommas, domain = ['one', 'two', 'three']\n"
]
}
],
"source": [
"print(f\"key = {T}, name = {VARIABLES.name(T)}, domain = {VARIABLES.domain(T)}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Conceptually, the Variables class keeps track of the names of variables and what values each variable can take on. We do this so that later when we print, it can show us a beautiful outputs. Of course, this only works if we actually use the key returned to use by `Variables.discrete`. Let's demonstrate this next:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"
P(TresCommas):
\n",
"
\n",
"
\n",
" \n",
"
TresCommas
value
\n",
" \n",
" \n",
"
one
0.25
\n",
"
two
0.5
\n",
"
three
0.25
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prior_on_tres_commas = gtsam.DiscreteDistribution(T, \"2/4/2\")\n",
"pretty(prior_on_tres_commas)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The above also illustrates once again that the `spec` string does not need to contain normalized probabilities. The constructor will do the normalization for us!\n",
"\n",
"Note that the function `pretty(*)` above is just a shortcut for `gtbook.discrete.pretty(*, VARIABLES)`, and is also defined alongside VARIABLES."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "NS-dhUF4P9Ox",
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Finally, let us also look at the other `DiscreteDistribution` methods we called above:\n",
"\n",
"- `sample(self: gtsam.DiscreteConditional) -> int`: The `sample` method samples according to the PMF, returning the integer index of the sampled value, in $0\\dots cardinality-1$.\n",
"- `pmf(self: gtsam.DiscreteDistribution) -> List[float]`: The `pmf` method will simply return all probability values, in order.\n",
"- `__call__(self: gtsam.DiscreteDistribution, arg0: int) -> float`: The call operator: when given an integer value, will return just the one corresponding probability value.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We have illustrated the use of all of these in the text, already."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we can also inspect the `VARIABLES`, as it has an HTML representation:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"
\n",
" \n",
"
Variable
Domain
\n",
" \n",
" \n",
"
Category
cardboard, paper, can, scrap metal, bottle
\n",
"
TresCommas
one, two, three
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"VARIABLES"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The table shows that we have defined two variables so far, `Category` and `TresCommas`, with respectively six and three possible values, shown in the \"Domain\" column."
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"include_colab_link": true,
"name": "S21_sorter_state.ipynb",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3.8.12 ('gtbook')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
},
"latex_metadata": {
"affiliation": "Georgia Institute of Technology",
"author": "Frank Dellaert and Seth Hutchinson",
"title": "Introduction to Robotics"
},
"vscode": {
"interpreter": {
"hash": "9f7376ced4243bb13dfcffa8a3ba834e0602aa8334cd3a1d8ba8d285f4628083"
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}