diff --git a/S23_sorter_sensing.ipynb b/S23_sorter_sensing.ipynb index 3ba680fd..99cd17fc 100644 --- a/S23_sorter_sensing.ipynb +++ b/S23_sorter_sensing.ipynb @@ -217,7 +217,7 @@ "id": "1SqwIzxjWfu6", "metadata": {}, "source": [ - "As an example, in Figure [1](fig:category_prior) we define a CPT for our binary sensor example and pretty-print it. Note the rows add up to 1.0, as each row is a valid probability mass function (PMF)." + "As an example, in Figure [1](#fig:category_prior) we define a CPT for our binary sensor example and pretty-print it. Note the rows add up to 1.0, as each row is a valid probability mass function (PMF)." ] }, { diff --git a/S34_vacuum_perception.ipynb b/S34_vacuum_perception.ipynb index d87291a4..da0e6b3f 100644 --- a/S34_vacuum_perception.ipynb +++ b/S34_vacuum_perception.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "E5rsQom9hatQ", "metadata": { "tags": [ @@ -40,12 +40,12 @@ } ], "source": [ - "%pip install -U -q gtbook\n" + "%pip install -U -q gtbook" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "OFkNEfdX_CLe", "metadata": { "tags": [ @@ -74,12 +74,12 @@ " import google.colab\n", "except:\n", " import plotly.io as pio\n", - " pio.renderers.default = \"png\"\n" + " pio.renderers.default = \"png\"" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "gIariOyXd8PE", "metadata": { "tags": [ @@ -104,7 +104,7 @@ "N = 3\n", "X = VARIABLES.discrete_series(\"X\", range(1, N+1), vacuum.rooms)\n", "A = VARIABLES.discrete_series(\"A\", range(1, N), vacuum.action_space)\n", - "Z = VARIABLES.discrete_series(\"Z\", range(1, N+1), vacuum.light_levels)\n" + "Z = VARIABLES.discrete_series(\"Z\", range(1, N+1), vacuum.light_levels)" ] }, { @@ -458,7 +458,7 @@ "
An HMM for three time steps, represented as a Bayes net.
\n", "\n", "\n", - "Figure [3.9](#fig:unrolledHMM) shows an example of an HMM for three time steps, i.e., \n", + "Figure [2](#fig:unrolledHMM) shows an example of an HMM for three time steps, i.e., \n", "$\\mathcal{X}=\\{X_1, X_2, X_3\\}$ and\n", "$\\mathcal{Z}=\\{Z_1, Z_2, Z_3\\}$. As discussed above, in a Bayes net\n", "each node is associated with a conditional distribution: the Markov\n", @@ -750,7 +750,7 @@ "we only represent the *hidden* variables $X_1$, $X_2$, and $X_3$, \n", "connected to factors that encode probabilistic information. For\n", "our example with three hidden states, the corresponding factor graph is\n", - "shown in Figure [3.25](#fig:HMM-FG).\n", + "shown in Figure [4](#fig:HMM-FG).\n", "It should be clear from the figure that the connectivity of a factor\n", "graph encodes, for each factor $\\phi_{i}$, which subset of variables\n", "$\\mathcal{X}_{i}$ it depends on. We write:\n", @@ -793,9 +793,7 @@ "In other words, the independence relationships are encoded by the edges\n", "$e_{ij}$ of the factor graph, with each factor $\\phi_{i}$ a function of\n", "*only* the variables $\\mathcal{X}_{i}$ in its adjacency set. As example, \n", - "for the factor graph in Figure\n", - "2\n", - "we have: \n", + "for the factor graph in Figure [4](#fig:HMM-FG) we have: \n", "\\begin{equation}\n", "\\begin{aligned}\n", "\\mathcal{X}_1 & =\\{X_1\\}\\\\\n", @@ -1161,9 +1159,7 @@ "Given an HMM factor graph of size $n$, the **max-product algorithm** is an $O(n)$ algorithm\n", "to find the MAP estimate, which is used by GTSAM under the hood.\n", "\n", - "Let us use the example from Figure\n", - "2\n", - "to understand the main idea behind it. To find the MAP estimate for $\\mathcal{X}$ we need to\n", + "Let us use the example from Figure [4](#fig:HMM-FG) to understand the main idea behind it. To find the MAP estimate for $\\mathcal{X}$ we need to\n", "*maximize* the product\n", "\\begin{equation}\n", "\\phi(X_1, X_2, X_3)=\\prod\\phi_{i}(\\mathcal{X}_{i})\n", diff --git a/S44_logistics_perception.ipynb b/S44_logistics_perception.ipynb index cb5117c1..74a70893 100644 --- a/S44_logistics_perception.ipynb +++ b/S44_logistics_perception.ipynb @@ -1191,7 +1191,7 @@ "\\begin{equation}\n", "\\Phi(X)=\\sum_i \\phi(X_i) = \\frac{1}{2} \\sum_i \\|A_i X_i-b_i\\|^2.\n", "\\end{equation}\n", - "In the continuous case we use *minimization* of the log-likelihood rather than maximization over the probabilities. The main reason is because then inference becomes a linear least squares problem." + "In the continuous case we use *minimization* of the log-likelihood rather than maximization over the probabilities. The main reason is because then inference becomes a linear least-squares problem." ] }, { @@ -1320,7 +1320,7 @@ "id": "pDcm35cDLftk", "metadata": {}, "source": [ - "### Sparse Least-Squares\n", + "### Sparse Least Squares\n", "\n", "In practice we use *sparse factorization methods* to solve for $X^*$. In particular, *sparse Cholesky* factorization can efficiently decompose the sparse Hessian $Q$ into its matrix square root $R$\n", "\\begin{equation}\n", diff --git a/S54_diffdrive_perception.ipynb b/S54_diffdrive_perception.ipynb index eabfd851..0841d9e3 100644 --- a/S54_diffdrive_perception.ipynb +++ b/S54_diffdrive_perception.ipynb @@ -305,12 +305,10 @@ "For example, the first pixel in the edge image has the value 2, which is calculated from the values \n", "$\\begin{bmatrix}3 & 3 & 5\\end{bmatrix}$, as highlighted below:\n", "\\begin{equation}\n", - "\\begin{align}\n", "\\begin{bmatrix}\n", "3 & \\textbf{3} & \\textbf{3} & \\textbf{5} & 5 & 5 & 5 & 2 & 2 & 2 \\\\\n", "3 & 0 & \\textbf{2} & 2 & 0 & 0 & -3 & -3 & 0 & -2\n", "\\end{bmatrix}\n", - "\\end{align}\n", "\\end{equation}\n", "The \"recipe\" to calculate the edge value is just taking a weighted sum,\n", "where the weights are defined by our filter:\n", @@ -343,12 +341,10 @@ "```\n", "Let us examine the input and output again:\n", "\\begin{equation}\n", - "\\begin{align}\n", "\\begin{bmatrix}\n", "3 & 3 & 3 & 5 & 5 & 5 & 5 & 2 & 2 & 2 \\\\\n", "3 & 0 & 2 & 2 & 0 & 0 & -3 & -3 & 0 & -2\n", "\\end{bmatrix}\n", - "\\end{align}\n", "\\end{equation}\n", "We already understand the first $2$. The output pixel next to it *also* has the value $2$, as you can verify using the formula. You might object to the fact that the edge seems to be \"doubly wide\", and that we could do better with the simpler filter $\\begin{bmatrix}-1 & 1\\end{bmatrix}$, which people also use. However, making a $1\\times 3$ filter with a zero in the middle ensures that the edges do not \"shift\". The resulting simple filter is widely used and known a **Sobel filter**.\n", "\n", @@ -394,15 +390,11 @@ "\n", "Armed with this formula, we can now understand the edge detection above. For each output pixel $h[i,j]$, we do a pointwise multiplication of the $1 \\times 3$ filter \n", "\\begin{equation}\n", - "\\begin{align}\n", "\\begin{pmatrix}g[0,-1] & g[0,0] & g[0,1]\\end{pmatrix} = \\begin{pmatrix}-1 & 0 & 1\\end{pmatrix}\n", - "\\end{align}\n", "\\end{equation}\n", "with the $1 \\times 3$ window \n", "\\begin{equation}\n", - "\\begin{align}\n", "\\begin{pmatrix}f[i,j-1] & f[i,j+0] & f[i,j+1]\\end{pmatrix}\n", - "\\end{align}\n", "\\end{equation}\n", "in the input image $f$.\n", "\n", diff --git a/S56_diffdrive_learning.ipynb b/S56_diffdrive_learning.ipynb index fbe7d040..6d7a8d63 100644 --- a/S56_diffdrive_learning.ipynb +++ b/S56_diffdrive_learning.ipynb @@ -421,7 +421,7 @@ "source": [ "We can then use the PyTorch training code below, which is a standard way of training any differentiable function, including our `LineGrid` class. That is because all the operations inside the `LineGrid` class are differentiable, so gradient descent will just work.\n", "\n", - "Inside the training loop below, you'll find the typical sequence of operations: zeroing gradients, performing a forward pass to get predictions, computing the loss, and doing a backward pass to update the model's parameters. Try to understand the code, as this same training loop is at the core of most deep learning architectures. Now, let's take a closer look at the code itself, which is extensively documented for clarity, and listed in Figure [2](#train_gd)." + "Inside the training loop below, you'll find the typical sequence of operations: zeroing gradients, performing a forward pass to get predictions, computing the loss, and doing a backward pass to update the model's parameters. Try to understand the code, as this same training loop is at the core of most deep learning architectures. Now, let's take a closer look at the code itself, which is extensively documented for clarity, and listed in Figure [2](#code:train_gd)." ] }, { diff --git a/S60_driving_intro.ipynb b/S60_driving_intro.ipynb index 854ef902..1bfa3e94 100644 --- a/S60_driving_intro.ipynb +++ b/S60_driving_intro.ipynb @@ -8,6 +8,8 @@ "source": [ "# Autonomous Vehicles\n", "\n", + "```{index} self-driving cars\n", + "```\n", "> Self-driving cars can be thought of as large-scale wheeled mobile robots that navigate in the real world based on sensor data.\n", "\n", "\"Splash\n" @@ -18,13 +20,27 @@ "id": "YhpQ6vC4mBFg", "metadata": {}, "source": [ + "```{index} autonomous driving\n", + "```\n", "In this chapter we look at some of the basic concepts involved in autonomous driving. Needless to say, the topic of autonomous vehicles is rather large, and we only cover a small selection in this chapter. \n", "\n", + "```{index} SO(2), SE(2), Ackermann steering\n", + "```\n", "We begin by becoming a bit more serious about movement in the plane, first introducing the matrix group SO(2) to represent rotation in the plane, and then extending this to the matrix group SE(2), which can be used to represent both rotation and translation in the plane. We then introduce kinematics in the form of Ackermann steering, which is common in automobiles. \n", "\n", + "```{index} LIDAR, Pose SLAM\n", + "```\n", + "```{index} pair: iterative closest points; ICP\n", + "```\n", + "```{index} pair: simultaneous localization and mapping; SLAM\n", + "```\n", "In addition to cameras, a very popular sensor in autonomous driving is the LIDAR sensor. We develop the basic geometry of LIDAR sensors, and then present the iterative closest points (ICP) algorithm as a way to obtain relative pose measurements from successive LIDAR scans. This leads naturally to the problem of simultaneous localization and mapping or SLAM, a very popular topic in robotics. Here we cover the most basic version, *Pose SLAM*, which only needs relative pose measurements. \n", "\n", - "In section 5 we look at motion primitives to do some motion planning on the road. Finally, in section 6, we discuss the basics of deep reinforcement learning." + "```{index} motion primitives\n", + "```\n", + "```{index} pair: deep reinforcement learning; DRL\n", + "```\n", + "We then look at motion primitives to do motion planning on the road, alongside polynomial and spline-based path planning. Finally, we discuss the basics of deep reinforcement learning with an autonomous driving example." ] } ], diff --git a/S61_driving_state.ipynb b/S61_driving_state.ipynb index 4cc97653..3523d64d 100644 --- a/S61_driving_state.ipynb +++ b/S61_driving_state.ipynb @@ -40,12 +40,12 @@ } ], "source": [ - "%pip install -q -U gtbook\n" + "%pip install -q -U gtbook" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "nX9_52OZokbY", "metadata": { "tags": [ @@ -55,7 +55,7 @@ "outputs": [], "source": [ "import math\n", - "import gtsam\n" + "import gtsam" ] }, { @@ -113,16 +113,18 @@ "\n", "> Rotation in the plane can be represented using $2\\times 2$ rotation matrices.\n", "\n", + "```{index} singularity\n", + "```\n", "In the previous chapter, we represented the orientation of the robot simply by $\\theta \\in [0,2\\pi)$.\n", "There are two main drawbacks to this choice.\n", "First, the mapping from the robot's orientation to the parameter $\\theta$ is not continuous.\n", "If the robot's orientation is $\\theta = 2\\pi - \\epsilon$, for example, and we rotate the robot counterclockwise\n", "(decreasing $\\epsilon$ to zero), we will encounter a discontinuity when $\\theta$ goes from very nearly $2\\pi$ to zero. This will occur regardless of the initial\n", "value we choose for $\\epsilon$: when $\\epsilon$ reaches zero, there will be a jump in the value of $\\theta$.\n", - "We call this jump a $singularity$.\n", + "We call this jump a **singularity**.\n", "The second drawback is that parameterizing orientation by a single angle $\\theta$ does not generalize in a straightforward way to representing orientation in 3D.\n", "Further, even if we go to the effort to make this generalization work, the problems with singularities\n", - "are complex for 3-dimensional rotations, and can cause serious problems if not handled properly." + "are complex in 3D and can cause serious problems if not handled properly." ] }, { @@ -142,9 +144,9 @@ "metadata": {}, "source": [ "Both difficulties above stem from the use of a minimal representation of orientation.\n", - "To solve these problems, we'll introduce a somewhat redundant representation that explicity\n", + "To solve these problems, we'll introduce a somewhat redundant representation that explicitly\n", "encodes the orientation of the axes of a target coordinate frame relative to a reference coordinate frame.\n", - "Consider the two coordinate frames shown in the figure above.\n", + "Consider the two coordinate frames shown in figure [1](#fig:2drotation).\n", "These two frames share a common origin, and Frame 1 is obtained by rotating by an angle $\\theta$ w.r.t.\n", "frame 0.\n", "We can express the $x$-axis of Frame 1 in coordinates relative to Frame 0\n", @@ -210,12 +212,14 @@ "id": "MkXku186lYnk", "metadata": {}, "source": [ - "```{index} special orthogonal group of order 2\n", - "```\n", "## The Special Orthogonal Group of Order 2, aka SO(2)\n", "\n", + "```{index} group\n", + "```\n", "> Rotation matrices form a group.\n", "\n", + "```{index} pair: special orthogonal group of order 2; SO(2)\n", + "```\n", "The set of $2\\times2$ rotation matrices, together with matrix multiplication as the\n", "composition operator, form a *group* called the **special orthogonal group of order 2**,\n", "denoted by $SO(2)$.\n", @@ -257,7 +261,9 @@ "source": [ "## Coordinate Transformations for Pure Rotations\n", "\n", - "> The rotation group acts on 2D points.\n", + "```{index} group action\n", + "```\n", + "> The rotation group *acts* on 2D points.\n", "\n", "
\n", "\"\"\n", @@ -265,8 +271,8 @@ "
\n", "\n", "We can use rotation matrices to perform coordinate transformations under pure rotation.\n", - "Consider a point $P$ that is rigidly attached to Frame 1 and, as illustrated in the Figures above, whose coordinates w.r.t. Frame 1\n", - "are given by\n", + "Consider a point $P$ that is rigidly attached to Frame 1 and, as illustrated in Figures [2](#fig:2drotation-trans),\n", + "whose coordinates w.r.t. Frame 1 are given by\n", "\\begin{equation}\n", "P_{}^{1}=\\begin{bmatrix}\n", "p_x \\\\\n", @@ -286,7 +292,7 @@ "id": "-jgWZAluSJzW", "metadata": {}, "source": [ - "To compute the coordinates $P^0$ of $P$ w.r.t. Frame 0, we merely project $P$ onto the $x$- and $y$- axes\n", + "To compute the coordinates $P^0$ of $P$ w.r.t. Frame 0, we merely project $P$ onto the $x$- and $y$-axes\n", "of Frame 0. Since the vectors $x_0$ and $y_0$ are unit vectors, this projection can again be accomplished\n", "using a the dot product:\n", "\n", @@ -329,16 +335,16 @@ "id": "cC4Xn1P76VXW", "metadata": {}, "source": [ - "```{index} group\n", + "```{index} group, group axioms\n", "```\n", "## The Group $SO(2)$ in GTSAM\n", "\n", "> `Rot2` is a group, and acts on `Point2`.\n", "\n", - "$SO(2)$ is a *group*, in the mathematical sense. \n", - "A **group** consists of a set (in our case, a set of 2D rotations) along\n", - "with a group operator (in our case, composition).\n", - "All groups posses certain properties, sometimes called the *group axioms*.\n", + "$SO(2)$ is a **group**, in the mathematical sense. \n", + "A group consists of a set (in our case, a set of 2D rotations) along\n", + "with a group operator (in our case, composition, implemented as matrix multiplication).\n", + "All groups posses certain properties, sometimes called the **group axioms**.\n", "For the group, $SO(2)$ these properties are as follows:\n", "\n", "1. *Closure*: For all rotations $R, R' \\in SO(2)$, their product is also in $SO(2)$, i.e., $RR' \\in SO(2)$.\n", @@ -347,17 +353,19 @@ "3. *Inverse*: For every $R \\in SO(2)$ there exists $R^{-1} \\in SO(2)$ such that $R^{-1}R = RR^{-1} = I$.\n", "4. *Associativity*: For all $R_1, R_2, R_3 \\in SO(2)$, $(R_1 R_2) R_3 = R_1 (R_2 R_3)$.\n", "\n", + "{raw:tex}`\\noindent`\n", "In addition (only in 2D!) rotations in $SO(2)$ *commute*:\n", "\n", "5. *Commutativity*: For all $R_1, R_2 \\in SO(2)$, $R_1 R_2 = R_2 R_1$.\n", "\n", - "\n", - "It will not surprise anyone by now that all of this is built into GTSAM. In particular, the 2D rotations are represented by the type `Rot2`:" + "{raw:tex}`\\noindent`\n", + "It will not surprise anyone by now that all of this is built into GTSAM.\n", + "In particular, the 2D rotations are represented by the type `Rot2`:" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "IE-sisGcx97t", "metadata": {}, "outputs": [ @@ -373,7 +381,7 @@ "source": [ "theta = math.radians(30)\n", "R = gtsam.Rot2(theta)\n", - "print(R.matrix())\n" + "print(R.matrix())" ] }, { @@ -381,12 +389,15 @@ "id": "7eHQrxtGa5TV", "metadata": {}, "source": [ - "Rotations in 2D form a *commutative* group, as demonstrated here in code:" + "```{index} commutative group\n", + "```\n", + "{raw:tex}`\\noindent`\n", + "Rotations in 2D form a **commutative group**, as demonstrated here in code:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "yf8DUljJXAB_", "metadata": {}, "outputs": [ @@ -409,7 +420,7 @@ "print((R * R.inverse()).equals(I2, 1e-9)) # inverse\n", "R1, R2, R3 = gtsam.Rot2(1), gtsam.Rot2(2), gtsam.Rot2(3)\n", "print(((R1 * R2)* R3).equals(R1 * (R2 * R3), 1e-9)) # associative\n", - "print((R1 * R2).equals(R2 * R1, 1e-9)) # commutative\n" + "print((R1 * R2).equals(R2 * R1, 1e-9)) # commutative" ] }, { @@ -417,12 +428,13 @@ "id": "rWz3FKacarkO", "metadata": {}, "source": [ - "Finally, rotations can act on points, which we can do using matrix multiplication, or using the `Rot2.rotate` method:" + "{raw:tex}`\\noindent`\n", + "Finally, rotations can act on 2D points, which we can do using matrix multiplication, or using the `Rot2.rotate` method:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "WnvoPVCTsOZv", "metadata": {}, "outputs": [ @@ -439,7 +451,7 @@ "R01 = gtsam.Rot2(math.radians(20))\n", "P1 = gtsam.Point2(4,3)\n", "print(f\"P0 = {R01.matrix() @ P1}\")\n", - "print(f\"P0 = {R01.rotate(P1)}\")\n" + "print(f\"P0 = {R01.rotate(P1)}\")" ] }, { @@ -447,17 +459,14 @@ "id": "3J8zJMktFgEG", "metadata": {}, "source": [ - "```{index} 2D rigid transforms, pose, transformation\n", - "```\n", - "## 2D Rigid Transforms aka SE(2)\n", + "## 2D Rigid Transforms, aka SE(2)\n", "\n", "> From $SO(2)$ to $SE(2)$.\n", "\n", - "**2D rigid transforms** combine rotation with translation, and are used in two *different* ways in robotics:\n", - "\n", - "\n", + "```{index} 2D rigid transformations, pose, transformation\n", + "```\n", + "**2D rigid transformations** combine rotation with translation, and are used in two *different* ways in robotics:\n", "* to specify the **pose** of a robot, which consists of a translation and an orientation\n", - "\n", "* to talk about relative pose or the **transformation** between two different robot poses\n", "\n", "We can use the same mathematical object for both use cases, i.e., 2D rigid transforms $T\\in SE(2)$, which combine translation and orientation in a $3 \\times 3$ matrix, as explained below." @@ -475,10 +484,10 @@ "\n", "Let us start by assuming as before that the point $P$ is rigidly attached to a frame that is rotated\n", "by angle $\\theta$ w.r.t. Frame 0.\n", - "If we now translate that frame according to a vector $d$, as shown in the figure above, the point $P$ undergoes\n", - "the same translation as the frame.\n", + "If we now translate that frame according to a vector $d$, as shown in figure [3](#fig:2drotation-trans2),\n", + "the point $P$ undergoes the same translation as the frame.\n", "To calculate the coordinates of $P$ relative to Frame 0, we merely\n", - "perform the rotational coordinate transformation as above,\n", + "perform the rotational coordinate transformation as in the figure,\n", "and then add the translation from the origin of Frame 0 to the origin of Frame 1:\n", "\\begin{equation}\n", "P^0 = R^0_1 P^1 + d^0_1\n", @@ -490,7 +499,9 @@ "id": "7CSYNx3ok5Ze", "metadata": {}, "source": [ - "```{index} special Euclidean group of order 2\n", + "```{index} pair: special Euclidean group of order 2; SE(2)\n", + "```\n", + "```{index} homogeneous coordinates, homogeneous coordinate transformation\n", "```\n", "We can write the above coordinate transformation as a matrix equation:\n", "\n", @@ -514,7 +525,6 @@ "\\begin{bmatrix} p_x \\\\ p_y \\\\ 1 \\end{bmatrix}\n", "\\end{equation}\n", "which holds for any $p_x, p_y$.\n", - "\n", "It is convenient to define the 3-vector $\\tilde{P}$ as\n", "\\begin{equation}\n", "\\tilde{P} =\n", @@ -522,7 +532,7 @@ "P \\\\ 1\n", "\\end{bmatrix}\n", "\\end{equation}\n", - "which we call the homogeneous coordinates of the point $P$. \n", + "which we call the **homogeneous coordinates** of the point $P$. \n", "Using this convention, we can write the *homogeneous coordinate transformation* as\n", "\\begin{equation}\n", "\\tilde{P}^0 = T^0_1 \\tilde{P}^1\n", @@ -556,11 +566,11 @@ "\n", "Consider a mobile robot that begins at some initial configuration, moves to a new location, reorients itself and then moves to another location. How can we determine the final configuration of the robot if we are given\n", "only the individual motions in the sequence?\n", - "The answer is suprisingly simple.\n", - "Suppose the point $P$ is rigidly attached to the robot.\n", + "The answer is surprisingly simple.\n", + "Suppose the point $P$ is rigidly attached to the robot, and the situation is as sketched in Figure [4](#fig:TransformComposition).\n", "Using the coordinate transformation equation $\\tilde{P}^i = T^i_j \\tilde{P}^j$, we have the following:\n", "\\begin{equation}\n", - "\\tilde{P}^0 = T^0_1 \\tilde{P}^1, ~~~~~ \\tilde{P}^1 = T^1_2 \\tilde{P}^2, ~~~~~ \\tilde{P}^0 = T^0_2 \\tilde{P}^2\n", + "\\tilde{P}^0 = T^0_1 \\tilde{P}^1, \\,\\,\\,\\,\\, \\tilde{P}^1 = T^1_2 \\tilde{P}^2, \\,\\,\\,\\,\\, \\tilde{P}^0 = T^0_2 \\tilde{P}^2\n", "\\end{equation} \n", "\n", "Combining the first two of these gives\n", @@ -587,7 +597,9 @@ "source": [ "## The Group $SE(2)$ in GTSAM\n", "\n", - "> `Pose2` is a *non-commutative* group, and also acts on `Point2`.\n", + "```{index} noncommutative group, SE(2)\n", + "```\n", + "> `Pose2` is a *noncommutative* group, and also acts on `Point2`.\n", "\n", "Unsurprisingly, $SE(2)$ is also a group, as the following properties all hold:\n", "\n", @@ -609,7 +621,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "ELfMAOeXZkMN", "metadata": {}, "outputs": [ @@ -628,7 +640,7 @@ "source": [ "theta = math.radians(30)\n", "T = gtsam.Pose2(3, -2, theta)\n", - "print(f\"2D Pose (x, y, theta) = {T}corresponding to transformation matrix:\\n{T.matrix()}\")\n" + "print(f\"2D Pose (x, y, theta) = {T}corresponding to transformation matrix:\\n{T.matrix()}\")" ] }, { @@ -636,12 +648,13 @@ "id": "CIU4bgF4edpW", "metadata": {}, "source": [ + "{raw:tex}`\\noindent`\n", "2D transforms form a *non-commutative* group, as demonstrated here in code:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "P9tC1AneiuDq", "metadata": {}, "outputs": [ @@ -664,7 +677,7 @@ "print((T * T.inverse()).equals(I3, 1e-9)) # inverse\n", "T1, T2, T3 = gtsam.Pose2(1,2,3), gtsam.Pose2(4,5,6), gtsam.Pose2(7,8,9)\n", "print(((T1 * T2)* T3).equals(T1 * (T2 * T3), 1e-9)) # associative\n", - "print((T1 * T2).equals(T2 * T1, 1e-9)) # NOT commutative\n" + "print((T1 * T2).equals(T2 * T1, 1e-9)) # NOT commutative" ] }, { @@ -672,12 +685,13 @@ "id": "J4s37-R7DUc8", "metadata": {}, "source": [ + "{raw:tex}`\\noindent`\n", "Finally, 2D transforms can act on points, which we can do using matrix multiplication, or using the `Pose2.transformFrom` method:" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "xJbNs0f36QEW", "metadata": {}, "outputs": [ @@ -694,7 +708,7 @@ "T01 = gtsam.Pose2(1, 2, math.radians(20))\n", "P1 = gtsam.Point2(4,3)\n", "print(f\"P0 = {T01.matrix() @ [4, 3, 1]}\") # need to make P0 homogeneous\n", - "print(f\"P0 = {T01.transformFrom(P1)}\")\n" + "print(f\"P0 = {T01.transformFrom(P1)}\")" ] }, { @@ -702,10 +716,10 @@ "id": "ulH889W6ROOn", "metadata": {}, "source": [ - "```{index} manifold\n", - "```\n", "## Lie Groups*\n", "\n", + "```{index} manifold\n", + "```\n", "Groups can be either discrete or continuous.\n", "For example, the set of integers along with addition as the group operation and zero as the identity forms a discrete\n", "group.\n", @@ -734,14 +748,14 @@ "source": [ "```{index} Lie group\n", "```\n", - "In addition to being manifolds, both $SO(2)$ and $SE(2)$ posses the additional property of being differentiable.\n", + "In addition to being manifolds, both $SO(2)$ and $SE(2)$ posses the additional property of being *differentiable*.\n", "Such groups are known as **Lie groups** (pronounced \"Lee groups\").\n", "While again beyond the scope of this book,\n", "we can understand this by considering the parameterizations for $SO(2)$ and $SE(2)$ described above.\n", "For each of these parameterizations, we can differentiate the elements of $SO(2)$ and $SE(2)$,\n", "which implies the existence of linear and angular velocities.\n", "In fact, in this chapter, we will define the actions of the car using these velocities as\n", - "the inputs to the system.\n" + "the inputs to the system." ] }, { @@ -754,7 +768,7 @@ "> A deeper dive in the GTSAM concepts used above.\n", "\n", "The types `Point2` and `Rot2` are used for 2D position and rotation.\n", - "While `Point2` is really just an alias for a 3-dimensional numpy array, the `Rot2` type is a wrapper around a c++ GTSAM type that represents $SO(2)$ using just a cosine and sine value, i.e., a point on the unit circle." + "While `Point2` is really just an alias for a two-dimensional `numpy` array, the `Rot2` type is a wrapper around a C++ GTSAM type that represents $SO(2)$ using just a cosine and sine value, i.e., a point on the unit circle." ] }, { @@ -762,7 +776,9 @@ "id": "919LyWTuLA6u", "metadata": {}, "source": [ - "Using a 2D position and a 2D rotation we can create a 2D pose, represented by a `Pose2`.As always, you can execute `help(gtsam.Pose2)` to get the full documentation of a class. Below is an excerpt with some useful methods. We have several constructors:\n", + "Using a 2D position and a 2D rotation we can create a 2D pose, represented by a `Pose2`.\n", + "As always, you can execute `help(gtsam.Pose2)` to get the full documentation of a class.\n", + "Below is an excerpt with some useful methods. We have several constructors:\n", "\n", "```python\n", "__init__(...)\n", @@ -782,7 +798,8 @@ " 6. __init__(self: Pose2, v: numpy.ndarray[numpy.float64[m, 1]]) -> None\n", "```\n", "\n", - "where `t` above is just a `Point2`, and `v` is supposed to be a 3-vector with x, y, and $\\theta$, in that order. We also have `Pose2.transformFrom` and `Pose2.transformTo` methods, which act on 2D points:\n", + "{raw:tex}`\\noindent`\n", + "where `t` above is just a `Point2`, and `v` is a 3-vector with x, y, and $\\theta$, in that order. We also have `Pose2.transformFrom` and `Pose2.transformTo` methods, which act on 2D points:\n", "\n", "```python\n", " transformFrom(...)\n", @@ -800,7 +817,10 @@ " ....\n", "```\n", "\n", - "Both have them have three overloads, but we show only the most commonly used one above. When using code, it is often a good idea to use indices to keep the different coordinate frames apart. For example, for a pose $T^a_b$ the method `transformFrom` implements the following \n", + "Both these methods have three overloads, but we show only the most commonly used one above.\n", + "\n", + "When using code, it is often a good idea to use indices to keep the different coordinate frames apart.\n", + "For example, for a pose $T^a_b$ the method `transformFrom` implements the following \n", "\\begin{equation}\n", "P^a = T^a_b P^b\n", "\\end{equation}\n", @@ -809,12 +829,6 @@ "P^b = (T^a_b)^{-1} P^a\n", "\\end{equation}" ] - }, - { - "cell_type": "markdown", - "id": "NzadLSgxqncZ", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/S62_driving_actions.ipynb b/S62_driving_actions.ipynb index 8c741aa9..3bed0b17 100644 --- a/S62_driving_actions.ipynb +++ b/S62_driving_actions.ipynb @@ -65,44 +65,36 @@ "id": "uGpC5i9XP2O5", "metadata": {}, "source": [ - "```{index} Ackermann steering\n", - "```\n", - "\n", "## Car kinematics\n", "\n", + "
\n", + "\"\"\n", + "
Coordinate frame for a car-like vehicle.
\n", + "
\n", + "\n", "We will assume that our car has front-wheel steering, and that forward velocity is achieved by actuation\n", "of the rear wheels.\n", "We assign a body-attached frame to the car with origin at the midpoint of the rear axle and $x$-axis pointing forward,\n", - "as shown in the figure below.\n", + "as shown in Figure [1](#fig:TransformComposition2).\n", "If the tires roll without slipping, the instantaneous velocity of the car is always in the direction of\n", "the body-attached $x$-axis.\n", "We denote the forward speed by $v_\\mathrm{car}$ (note that $v_\\mathrm{car}$ denotes the *scalar* speed of the car,\n", "and not the car velocity, which is a *vector* quantity)." ] }, - { - "cell_type": "markdown", - "id": "Uxx4kjwgZkwx", - "metadata": {}, - "source": [ - "\n", - "
\n", - "\"\"\n", - "
Coordinate frame for a car-like vehicle.
\n", - "
\n" - ] - }, { "cell_type": "markdown", "id": "lMT1wpCGycZ1", "metadata": {}, "source": [ - "At first glance, it may appear that the two front wheels have the same orientation w.r.t. the car frame.\n", + "```{index} turning circle\n", + "```\n", + "At first glance, it may appear that the two front wheels have the same orientation with respect to the car frame.\n", "This is not the case, however.\n", "As the origin of the car frame traces out a curve in the plane, in order for all wheels to roll without\n", "slipping, their instantaneous velocities must be tangent to a set of circles that share a common origin,\n", - "called the center of the turning circle.\n", - "For the rear wheels, because they share a common axle and maintain a fixed orientation w.r.t. the car frame,\n", + "called the center of the **turning circle**.\n", + "For the rear wheels, because they share a common axle and maintain a fixed orientation with respect to the car frame,\n", "the axle coincides with the radius of concentric circles,\n", "and each of the rear wheels has instantaneous velocity that is perpendicular to this radius.\n", "For the front wheels, the inner wheel follows a circle with smaller radius than the outside wheel." @@ -110,36 +102,24 @@ }, { "cell_type": "markdown", - "id": "UgKK8wcmM6qM", + "id": "oWf1tsXNCh5O", "metadata": {}, "source": [ "
\n", "\"\"\n", "
Ackermann steering.
\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "oWf1tsXNCh5O", - "metadata": {}, - "source": [ + "\n", + "\n", "```{index} Ackermann steering\n", "```\n", - "This kind of steering for the front wheels is called **Ackermann steering**, as illustrated in the figure above.\n", + "This kind of steering for the front wheels is called **Ackermann steering**, as illustrated in Figure [2](#fig:AckermannSteering1).\n", "The physical mechanism required to implement Ackermann steering is slightly complex,\n", "but happily we can model the system by using a single *virtual wheel* placed\n", "at the midpoint between the two front wheels, rolling in a direction perpendicular to the line from\n", "the center of the turning circle to this midpoint.\n", "We denote by $\\phi$ the angle from the car $x$-axis to the forward direction of this virtual wheel,\n", - "as shown in the figure below." - ] - }, - { - "cell_type": "markdown", - "id": "CQZ7nCg9Lw5o", - "metadata": {}, - "source": [ + "as shown in Figure [3](#fig:AckermannSteering2).\n", + "\n", "
\n", "\"\"\n", "
Virtual wheel used to model Ackermann steering.
\n", @@ -151,7 +131,8 @@ "id": "YGNC2u0eBKBo", "metadata": {}, "source": [ - "The configuration of the car can be represented as $q = (x,y,\\theta, \\phi)$, in which $x,y$ denotes the position\n", + "The configuration of the car, shown in Figure [4](#fig:CarConfigurationParams),\n", + "can be represented as $q = (x,y,\\theta, \\phi)$, in which $x,y$ denotes the position\n", "of the origin of the car frame, $\\theta$ denotes the orientation of car frame (the angle from the world $x$-axis\n", "to the car frame $x$-axis), and $\\phi$ denotes the steering angle.\n", "The inputs to the car are\n", @@ -164,7 +145,6 @@ "id": "oHuDTOK7GdCp", "metadata": {}, "source": [ - "\n", "
\n", "\"\"\n", "
Configuration parameters for the car.
\n", @@ -203,7 +183,7 @@ "metadata": {}, "source": [ "The equation for $\\dot{\\theta}$ is slightly more complex.\n", - "From the figure below, we observe the following\n", + "From the Figure [5](#fig:CarRelevantVelocities), we observe the following\n", "\\begin{equation}\n", "\\begin{aligned}\n", "v_\\mathrm{wheel} \\cos \\phi &= v_\\mathrm{car} \\\\\n", @@ -226,10 +206,9 @@ "id": "akYxPUgoL7HM", "metadata": {}, "source": [ - "\n", "
\n", "\"\"\n", - "
Relevant velocities for computing the differential kinamatics of the car.
\n", + "
Relevant velocities for computing the differential kinematics of the car.
\n", "
" ] }, diff --git a/S63_driving_sensing.ipynb b/S63_driving_sensing.ipynb index cbcb02fd..15b7f6e9 100644 --- a/S63_driving_sensing.ipynb +++ b/S63_driving_sensing.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "N-LdA3ldBKBS", "metadata": { "tags": [ @@ -40,12 +40,12 @@ } ], "source": [ - "%pip install -q -U gtbook\n" + "%pip install -q -U gtbook" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "bIZpayf8Ac8M", "metadata": { "tags": [ @@ -70,7 +70,7 @@ " pio.renderers.default = \"png\"\n", "\n", "import gtsam\n", - "from gtbook import driving\n" + "from gtbook import driving" ] }, { @@ -102,10 +102,12 @@ "id": "rJ_JdxykTTph", "metadata": {}, "source": [ - "```{index} Time of Flight, ToF, direct ToF, Indirect ToF, 2D LIDAR, 3D LIDAR\n", - "```\n", "## LIDAR\n", "\n", + "```{index} pair: LIDAR; LIght raDAR\n", + "```\n", + "```{index} Time of Flight, ToF, direct ToF, Indirect ToF\n", + "```\n", "LIDAR (LIght raDAR) is a technology that measures distance to an object by using laser light and the **Time of Flight** or **ToF** principle. There are several variants in use, and the simplest to explain is the **direct ToF** sensor, which sends out a short pulse and measures the elapsed time $\\Delta t$ for the light to bounce off an object and return to a detector collocated with the laser pulse emitter. If the object is situated at a distance $d$ from the emitter-detector pair, we have\n", "\\begin{equation}\n", "\\Delta t = \\frac{2 d}{c} \n", @@ -121,9 +123,17 @@ "In a *scanning LIDAR*, there is typically one detector, whereas in a *flash LIDAR* a single pulse is emitted in a wide field of view, and an *array* of detectors, akin to a CCD sensor, is used to detect the returning light pulses in multiple directions at once.\n", "\n", "In practice, *indirect* time of flight sensors are more prevalent in robotics and autonomous driving applications than direct ToF sensors. There are multiple reasons for this: measuring elapsed times at the nano-second scale is difficult and expensive, and the amount of light energy that needs to be emitted for direct ToF can also be a problem from an eye-safety perspective.\n", - "**Indirect ToF** is an attractive alternative, where the light is emitted with a waveform, e.g., a sine wave, and the returned light is correlated with the amplitude of the emitted light to calculate a phase shift. The elapsed time $\\Delta t$ and distance $d$ can then be calculated from the phase shift.\n", - "\n", - "Two common scanning LIDAR technologies are in use for robotics: **2D LIDAR**, which consists of a single laser beam that is rotated around a fixed axis, and **3D LIDAR**, which has multiple laser/detector pairs rotated at different inclinations. 2D LIDARs are also often deployed on aircraft to create highly detailed digital elevation maps, where the third dimension is provided by the aircraft's forward motion. LIDAR altimeters are even deployed from satellites in orbit around Earth or [other planets](https://pgda.gsfc.nasa.gov/products/62)." + "**Indirect ToF** is an attractive alternative, where the light is emitted as a waveform, e.g., a sine wave, and the returned light is correlated with the amplitude of the emitted light to calculate a phase shift. The elapsed time $\\Delta t$ and distance $d$ can then be calculated from the phase shift." + ] + }, + { + "cell_type": "markdown", + "id": "fa906a17", + "metadata": {}, + "source": [ + "```{index} 2D LIDAR, 3D LIDAR\n", + "```\n", + "Two common scanning LIDAR technologies are in use for robotics: **2D LIDAR**, which consists of a single laser beam that is rotated around a fixed axis, and **3D LIDAR**, which has multiple laser/detector pairs rotated at different inclinations. 2D LIDAR is also often deployed on aircraft to create highly detailed digital elevation maps, where the third dimension is provided by the aircraft's forward motion. LIDAR altimeters are even deployed from satellites in orbit around Earth or [other planets](https://pgda.gsfc.nasa.gov/products/62)." ] }, { @@ -137,7 +147,7 @@ "\n", "> Intersecting rays is as easy as computing a dot product.\n", "\n", - "To perform inference about the environment with LIDAR, we have to model how LIDAR beams interact with it, which in the case of polygonal objects comes down to line-line or line-plane intersections.\n", + "To perform inference about the environment using LIDAR, we have to model how LIDAR beams interact with the environment, which in the case of polygonal objects comes down to line-line or line-plane intersections.\n", "\n", "We examine the 2D line-line intersection case first. Assume we have a line in 2D with equation $\\hat{n} \\cdot p = d$, where $\\hat{n}$ is a normal vector and $d>0$ is the distance of the line to the origin. The hat notation signifies that the normal vector $\\hat{n}$ is normalized to length 1. Then if we have a ray of points $p = \\hat{r} s$ where $\\hat{r}$ is the **ray direction** and $s>0$ is a scalar, we can find the intersection by plugging the ray equation into the line equation\n", "\\begin{equation}\n", @@ -156,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "eAJhhpqrjbLr", "metadata": {}, "outputs": [], @@ -166,7 +176,7 @@ " cos = np.dot(n,r)\n", " return d / cos if cos>0 else float('nan')\n", "\n", - "assert intersect(n=gtsam.Point2(1,0), d=5, r=gtsam.Point2(1,0)) == 5\n" + "assert intersect(n=gtsam.Point2(1,0), d=5, r=gtsam.Point2(1,0)) == 5" ] }, { @@ -174,18 +184,19 @@ "id": "W7LmYyTeTz56", "metadata": {}, "source": [ + "{raw:tex}`\\noindent`\n", "The story above generalizes *completely* to 3D, where with $\\hat{n}\\in\\mathbb{R}^3$ and $p\\in\\mathbb{R}^3$.\n", "In this case, the equation $\\hat{n} \\cdot p = d$ defines a *plane* in 3D:" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "elgDdjOQPJ8L", "metadata": {}, "outputs": [], "source": [ - "assert intersect(n=gtsam.Point3(1,0,0), d=5, r=gtsam.Point3(1,0,0)) == 5\n" + "assert intersect(n=gtsam.Point3(1,0,0), d=5, r=gtsam.Point3(1,0,0)) == 5" ] }, { @@ -204,14 +215,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "rcdEJSZvqSeN", "metadata": {}, "outputs": [], "source": [ "north = gtsam.Point2(0,1), 2.5\n", "east = gtsam.Point2(1,0), 8\n", - "south = gtsam.Point2(0,-1), 2.5\n" + "south = gtsam.Point2(0,-1), 2.5" ] }, { @@ -219,12 +230,13 @@ "id": "-nAxP1nY9Njj", "metadata": {}, "source": [ - "Then we simply create 200 rays, ranging from -100 degrees to 100 degrees, with 0 facing due east:" + "{raw:tex}`\\noindent`\n", + "Then, in Figure [1](#fig:lidar_rays_200) we simply create 200 rays, ranging from -100 degrees to 100 degrees, with 0 facing due east." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "x7mXdGTm5_xe", "metadata": {}, "outputs": [ @@ -237,12 +249,12 @@ } ], "source": [ - "#| caption: 200 \"LIDAR\" rays at different angles\n", + "#| caption: 200 LIDAR \"rays\" at different angles\n", "#| label: fig:lidar_rays_200\n", "angles = np.linspace(-100, 100, 200)\n", "rays = np.array([gtsam.Rot2(math.radians(angle)).matrix()[0,:] for angle in angles])\n", "fig = px.scatter(x=rays[:,0],y=rays[:,1])\n", - "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()\n" + "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()" ] }, { @@ -250,27 +262,12 @@ "id": "FYj5L2oKRK2x", "metadata": {}, "source": [ - "Finally, we do the intersection:" + "Finally, we compute the intersection with the environment in Figure [2](#fig:lidar_scan_200)." ] }, { "cell_type": "code", - "execution_count": 7, - "id": "1W7qolhDaqxw", - "metadata": {}, - "outputs": [], - "source": [ - "scan = []\n", - "for r in rays:\n", - " ranges = [intersect(n,d,r) for n, d in [north, east, south]]\n", - " _range = np.nanmin(ranges)\n", - " intersection = _range * r\n", - " scan.append(intersection)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "tWO026Wc1n9E", "metadata": {}, "outputs": [ @@ -285,9 +282,15 @@ "source": [ "#| caption: A simulated scan by intersecting 200 rays with the environment.\n", "#| label: fig:lidar_scan_200\n", + "scan = []\n", + "for r in rays:\n", + " ranges = [intersect(n,d,r) for n, d in [north, east, south]]\n", + " _range = np.nanmin(ranges)\n", + " intersection = _range * r\n", + " scan.append(intersection)\n", "scan_x, scan_y = zip(*scan)\n", "fig = px.scatter(x=scan_x, y=scan_y)\n", - "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()\n" + "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()" ] }, { @@ -305,7 +308,8 @@ "source": [ "## Geometry of a Moving LIDAR\n", "\n", - "Above we assumed that the ray is situated at the *world* origin, but we can also generalize to the case where the rays are defined in a *body coordinate frame* $(R^w_b,t^w_b)$. In this case, it is convenient to transform the plane to the body frame. We can do this by expressing a point $p^w$ in world coordinates as a function of a point in body coordinates: $p^w = R^w_b p^b + t^w_b$ and plugging that into the plane equation:\n", + "Above we assumed that the ray is situated at the *world* origin, but we can also generalize to the case where the rays are defined in a *body coordinate frame* $(R^w_b,t^w_b)$.\n", + "In this case, it is convenient to transform the plane to the body frame. We can do this by expressing a point $p^w$ in world coordinates as a function of a point in body coordinates, $p^w = R^w_b p^b + t^w_b$, and plugging that into the plane equation:\n", "\\begin{equation}\n", "\\begin{aligned}\n", "\\hat{n}^w \\cdot p^w &= d^w \\\\\n", @@ -319,12 +323,12 @@ "\\end{equation}\n", "with transformed plane parameters $\\hat{n}^b \\doteq (R^w_b)^T \\hat{n}^w$ and $d^b \\doteq d^w - \\hat{n}^w \\cdot t^w_b$.\n", "\n", - "We can use a `Pose2` or `Pose3` object to specify the body frame, respectively in 2D or 3D, and then use it to transform plane coordinates:" + "We can use a `Pose2` or `Pose3` object to specify the body frame, respectively in 2D or 3D, and then use it to transform plane coordinates:" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "Bg9fgCE0s_a_", "metadata": {}, "outputs": [], @@ -333,7 +337,7 @@ " \"\"\"Transform line/plane (n,d) to body coordinate frame\"\"\"\n", " wRb = wTb.rotation()\n", " wtb = wTb.translation()\n", - " return wRb.matrix().T @ n, d - np.dot(n, wtb)\n" + " return wRb.matrix().T @ n, d - np.dot(n, wtb)" ] }, { @@ -341,12 +345,13 @@ "id": "qEBaMCsjeOXz", "metadata": {}, "source": [ + "{raw:tex}`\\noindent`\n", "We can do this for lines in 2D" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "Mk5MQUh3GgZ-", "metadata": {}, "outputs": [ @@ -360,7 +365,7 @@ ], "source": [ "wTb = gtsam.Pose2(r=gtsam.Rot2(math.radians(20)), t=[2,1])\n", - "print(transform_to(gtsam.Point2(1,0), 5, wTb))\n" + "print(transform_to(gtsam.Point2(1,0), 5, wTb))" ] }, { @@ -368,12 +373,13 @@ "id": "NB9XuYKbgDLh", "metadata": {}, "source": [ + "{raw:tex}`\\noindent`\n", "and for planes in 3D:" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "sCNTpjPf_uXN", "metadata": {}, "outputs": [ @@ -387,7 +393,7 @@ ], "source": [ "wTb3 = gtsam.Pose3(r=gtsam.Rot3.Yaw(math.radians(45)), t=[1,2,3])\n", - "print(transform_to(gtsam.Point3(1,0,0), 5, wTb3))\n" + "print(transform_to(gtsam.Point3(1,0,0), 5, wTb3))" ] }, { @@ -395,28 +401,12 @@ "id": "LIglTrV9SbHZ", "metadata": {}, "source": [ - "With this new functionality we can transform the world model and predict what the scan will look like, in body coordinates." + "With this new functionality we can transform the world model and predict what the scan will look like, in body coordinates, as shown in Figure [3](#fig:lidar_scan_200_2)." ] }, { "cell_type": "code", - "execution_count": 12, - "id": "6pFjGjqrC2Nq", - "metadata": {}, - "outputs": [], - "source": [ - "scan2 = []\n", - "transformed = [transform_to(n, d, wTb) for n, d in [north, east, south]]\n", - "for r in rays:\n", - " ranges = [intersect(n,d,r) for n, d in transformed]\n", - " _range = np.nanmin(ranges)\n", - " intersection = _range * r\n", - " scan2.append(intersection)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "atWShGt1jiPU", "metadata": {}, "outputs": [ @@ -431,9 +421,16 @@ "source": [ "#| caption: Simulated scan from a different location in the same environment.\n", "#| label: fig:lidar_scan_200_2\n", + "scan2 = []\n", + "transformed = [transform_to(n, d, wTb) for n, d in [north, east, south]]\n", + "for r in rays:\n", + " ranges = [intersect(n,d,r) for n, d in transformed]\n", + " _range = np.nanmin(ranges)\n", + " intersection = _range * r\n", + " scan2.append(intersection)\n", "scan2_x, scan2_y = zip(*scan2)\n", "fig = px.scatter(x=scan2_x, y=scan2_y)\n", - "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()\n" + "fig.update_yaxes(scaleanchor = \"x\", scaleratio = 1); fig.show()" ] }, { @@ -441,7 +438,7 @@ "id": "98YN0qUSY-zJ", "metadata": {}, "source": [ - "As you can see above, when the robot rotates 20 degrees *counter-clockwise*, the resulting scan in body coordinates seems to be rotated 20 degrees in the *opposite* direction. This makes sense! The forward direction for the robot is along the horizontal X-axis, and as you can see in the scan, the robot seems to be \"looking\" at the correct (upper-right) corner of our little hallway example. This perhaps counter-intuitive behavior is something to always keep in mind when looking at animations of LIDAR scans." + "As you can see above, when the robot rotates 20 degrees *counter-clockwise*, the resulting scan in body coordinates seems to be rotated 20 degrees in the *opposite* direction. This makes sense! The forward direction for the robot is along the horizontal x-axis, and as you can see in the scan, the robot seems to be \"looking\" at the correct (upper-right) corner of our little hallway example. This perhaps counter-intuitive behavior is something to always keep in mind when looking at animations of LIDAR scans." ] }, { @@ -453,9 +450,9 @@ "\n", "> From theory to practice.\n", "\n", - "3D LIDAR sensors have been used in many autonomous driving efforts, e.g., by Waymo, Cruise, Argo, etc. Below we explore some real scans from the [Argoverse 2 Lidar Dataset](https://www.argoverse.org/av2.html).\n", + "3D LIDAR sensors have been used in many autonomous driving efforts, e.g., by Waymo, Cruise, Argo, etc. Below we explore some real scans from the [Argoverse 2 Lidar Dataset](https://www.argoverse.org/av2.html) {cite:p}`Wilson21_Argoverse2`.\n", "\n", - "First, let us look at a single scan, which was acquired using a [Velodyne](https://velodynelidar.com/products/ultra-puck/) VLP-32C LIDAR sensor. \n", + "First, let us look at a single scan, shown in Figure [4](#fig:lidar_scan_real), which was acquired using a [Velodyne](https://velodynelidar.com/products/ultra-puck/) VLP-32C LIDAR sensor. \n", "The `32` in the model name reflects the fact that, for this particular sensor, there are 32 separate laser beams at different inclinations, that spin around for a full 360 degree field of view. In this case, the inclination angles are uniformly sampled between -25 and +15 degrees with respect to horizontal, but of course this depends on the application/sensor model.\n", "\n", "Several things worth noting when looking at the single scan below, taken when the Argo test car is turning at an intersection:\n", @@ -467,7 +464,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "0piXkefs3ZxI", "metadata": {}, "outputs": [ @@ -480,10 +477,10 @@ } ], "source": [ - "#| caption: An actual 3D Velodyne LIDAR scan from an autonomous vehicle\n", + "#| caption: An actual 3D Velodyne LIDAR scan from an autonomous vehicle.\n", "#| label: fig:lidar_scan_real\n", "real_scans = {0:driving.read_lidar_points('Figures6/lidar/PC_315967795019746000.ply')}\n", - "driving.visualize_cloud(real_scans[0], show_grid_lines=True)\n" + "driving.visualize_cloud(real_scans[0], show_grid_lines=True)" ] }, { @@ -491,13 +488,14 @@ "id": "BhdwOuecH7VY", "metadata": {}, "source": [ - "We can also learn some things from looking at two successive scans below. \n", - "The scans are slightly rotated and translated from each other, and this will be exactly how we can infer the ego-motion of the car in the next section." + "We can also learn some things from looking at two successive scans in Figure [5](#fig:lidar_scan_real_2). \n", + "The scans are slightly rotated and translated from each other,\n", + "and this will be exactly how we can infer the motion of the car in the next section." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "9DfYnNNXJ9b8", "metadata": {}, "outputs": [ @@ -513,7 +511,7 @@ "#| caption: Two successive LIDAR scans from an autonomous vehicle.\n", "#| label: fig:lidar_scan_real_2\n", "real_scans[1] = driving.read_lidar_points('Figures6/lidar/PC_315967795520065000.ply')\n", - "driving.visualize_clouds([real_scans[0],real_scans[1]], show_grid_lines=True)\n" + "driving.visualize_clouds([real_scans[0],real_scans[1]], show_grid_lines=True)" ] }, { @@ -521,12 +519,12 @@ "id": "4D8hMIhgIxvx", "metadata": {}, "source": [ - "Finally, let us look at scans that are taken a bit further apart, in this case there are 8 scans that we skipped:" + "Finally, let us look - in Figure [6](#fig:lidar_scan_real_8) - at scans that are taken a bit further apart, in this case there are 8 scans that we skipped." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "bv3PFaZ7tYIH", "metadata": {}, "outputs": [ @@ -542,7 +540,7 @@ "#| caption: Two LIDAR scans from an autonomous vehicle that are eight frames apart.\n", "#| label: fig:lidar_scan_real_8\n", "real_scans[9] = driving.read_lidar_points('Figures6/lidar/PC_315967795919523000.ply')\n", - "driving.visualize_clouds([real_scans[0],real_scans[9]], show_grid_lines=True)\n" + "driving.visualize_clouds([real_scans[0],real_scans[9]], show_grid_lines=True)" ] }, { @@ -558,22 +556,22 @@ "id": "LY0PLxeYIhQb", "metadata": {}, "source": [ - "```{index} point cloud map\n", - "```\n", "## Creating 3D Maps\n", "\n", "> Point clouds can be used to represent the 3D world.\n", "\n", + "```{index} point cloud map\n", + "```\n", "If we know the exact pose at which a LIDAR scan was taken, we can transform the points in the scan into absolute coordinates, and create an extended **point cloud map** of the environment. In math, suppose we *know* that a scan was taken at $T^w_b$, then we can transform all LIDAR points $P^b$, which are given in body coordinates, to world coordinates:\n", "\\begin{equation}\n", "P^w = \\phi(T^w_b, P^b)\n", "\\end{equation}\n", - "where $\\phi$ the the action of $SE(3)$ on points in $\\mathbb{R}^3$. Earlier in this chapter we saw this can be done by matrix multiplication, but GTSAM actually implements $\\phi$ directly as `Pose3::transformFrom`. This method can be applied to a single `Point3` (just a $3\\times 1$ vector, really) or on many points simultaneously, by passing in a $3\\times N$ matrix:\n" + "where $\\phi$ the the action of $SE(3)$ on points in $\\mathbb{R}^3$. Earlier in this chapter we saw this can be done by matrix multiplication, but GTSAM actually implements $\\phi$ directly as `Pose3::transformFrom`. This method can be applied to a single `Point3` (just a $3\\times 1$ vector, really) or on many points simultaneously, by passing in a $3\\times N$ matrix:" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "JsTxPYwEC5nl", "metadata": {}, "outputs": [ @@ -587,7 +585,7 @@ ], "source": [ "scan_in_world = wTb3.transformFrom(real_scans[0])\n", - "print(scan_in_world.shape)\n" + "print(scan_in_world.shape)" ] }, { @@ -614,7 +612,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "47YNsbNhQdO0", "metadata": {}, "outputs": [ @@ -631,7 +629,7 @@ "p = gtsam.Point2(3,4)\n", "P = gtsam.Point3(5,6,7)\n", "print(type(p), p)\n", - "print(type(P), P)\n" + "print(type(P), P)" ] }, { @@ -646,7 +644,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "WUHn6CyKIeIw", "metadata": {}, "outputs": [ @@ -662,12 +660,12 @@ ], "source": [ "R1 = gtsam.Rot2(20 * math.pi / 180) # 20 degrees rotation\n", - "print(R1, R1.matrix())\n" + "print(R1, R1.matrix())" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "60ZKsfACPSUt", "metadata": {}, "outputs": [ @@ -688,7 +686,7 @@ ], "source": [ "R2 = gtsam.Rot3()\n", - "print(R2, R2.matrix())\n" + "print(R2, R2.matrix())" ] }, { @@ -696,12 +694,12 @@ "id": "EvQ_XwOaHezI", "metadata": {}, "source": [ - "While there are various methods to create 3D rotation matrices (use help!), above we only use the named constructor `Yaw` , which is a rotation around the Z-axis. Below is an example, which you can compare with the 2D rotation example above..." + "While there are various methods to create 3D rotation matrices (use help!), above we only use the named constructor `Yaw` , which is a rotation around the z-axis. Below is an example, which you can compare with the 2D rotation example above." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "6gMr7iGF9aKE", "metadata": {}, "outputs": [ @@ -722,7 +720,7 @@ ], "source": [ "R3 = gtsam.Rot3.Yaw(20 * math.pi / 180) # 20 degrees rotation around Z\n", - "print(R3, R3.matrix())\n" + "print(R3, R3.matrix())" ] }, { @@ -740,81 +738,7 @@ "source": [ "### Reading and Visualizing LIDAR clouds\n", "\n", - "The utility functions `read_lidar_points` and `visualize_clouds` are not part of GTSAM, but are part of the gtbook library that accompanies this book. On the [gtbook website](https://gtbook.github.io/gtbook/driving.html) you can find much more detailed documentation as well as the source code, but `help` is very comprehensive for those functions:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "7CJmeFWrnGA8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function read_lidar_points in module gtbook.driving:\n", - "\n", - "read_lidar_points(filename: str)\n", - " Read 3D points in LIDAR scan stored as a binary_little_endian .ply file.\n", - " \n", - " Parameters:\n", - " filename: of ply file\n", - " \n", - " Returns:\n", - " A tuple (3,N) numpy array.\n", - "\n" - ] - } - ], - "source": [ - "help(driving.read_lidar_points)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "RG3QiiDS08-U", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function visualize_clouds in module gtbook.driving:\n", - "\n", - "visualize_clouds(clouds: list, show_grid_lines: bool = False, cloud_colors=None, marker_size: int = 1, do_subsampling: bool = True)\n", - " Visualizes cloud(s) in a iterative 3D plot.\n", - " Adapted from code by 3630 TAs Binit Shah and Jerred Chen\n", - " \n", - " Due to browser limitations, rendering above 5 frames requires\n", - " subsampling of the point clouds, which is done automatically.\n", - " \n", - " Example input of arg:\n", - " clouds = [clouda, cloudb, cloudc]\n", - " where each cloud is a numpy array of shape (3, num_points).\n", - " cloud[0] are the x coordinates, cloud[1] is y, and cloud[2] is z.\n", - " \n", - " Args:\n", - " clouds (list): ordered series of point clouds\n", - " show_grid_lines (bool): plots gridlines\n", - " cloud_colors (list): colors for each cloud in the visualization\n", - " marker_size (int): size of each marker\n", - " do_subsampling (bool): whether or not subsampling occurs\n", - "\n" - ] - } - ], - "source": [ - "help(driving.visualize_clouds)\n" - ] - }, - { - "cell_type": "markdown", - "id": "-JuzVChgrl5z", - "metadata": {}, - "source": [ - "While the visualization code is statically rendered in the book, when you open the Colab things should be rendered as interactive plotly scatter plots. Try it!" + "The utility functions `read_lidar_points` and `visualize_clouds` are not part of GTSAM, but are part of the gtbook library that accompanies this book. On the [gtbook website](https://gtbook.github.io/gtbook/driving.html) {cite:p}`gtbook` you can find much more detailed documentation as well as the source code, but `help` is very comprehensive for those functions. While the visualization code is statically rendered in the book, when you open the Colab things should be rendered as interactive `plotly` scatter plots. Try it!" ] } ], diff --git a/S64_driving_perception.ipynb b/S64_driving_perception.ipynb index d08c6502..a497238f 100644 --- a/S64_driving_perception.ipynb +++ b/S64_driving_perception.ipynb @@ -40,7 +40,7 @@ } ], "source": [ - "%pip install -q -U gtbook\n" + "%pip install -q -U gtbook" ] }, { @@ -68,7 +68,7 @@ "import gtsam\n", "import gtsam.utils.plot as gtsam_plot\n", "\n", - "from gtbook.display import show\n" + "from gtbook.display import show" ] }, { @@ -111,7 +111,9 @@ "id": "YHaAIxJ1a63C", "metadata": {}, "source": [ - "In this section we show that this *is* possible, and we expand upon two different SLAM techniques: *PoseSLAM* and *SLAM with landmarks*. In the former we concentrate on estimating the robot's motion from a rich sensor such as LIDAR, and building the map is generated as a side effect. In the latter, we truly optimize for both the location of the vehicle and the location of a observed landmarks, which leads to a sparse map of the environment. Before investigating these two SLAM algorithms, we review the math of vehicle poses in 2D, and discuss ICP, a seminal algorithm for aligning two point clouds, and which we use as a building block in PoseSLAM." + "```{index} PoseSLAM, SLAM with landmarks\n", + "```\n", + "In this section we show that this *is* possible, and we introduce two different SLAM techniques: *PoseSLAM* and *SLAM with landmarks*. In the former we concentrate on estimating the robot's motion from a rich sensor such as LIDAR, and building the map is accomplished as a side effect. In the latter, we truly optimize for both the location of the vehicle and the location of a observed landmarks, which leads to a sparse map of the environment. Before investigating these two SLAM algorithms, we review the math of vehicle poses in 2D, and discuss ICP, a seminal algorithm for aligning two point clouds, and which we use as a building block in PoseSLAM." ] }, { @@ -126,9 +128,8 @@ "In both SLAM variants we need to optimize over the pose of a vehicle.\n", "To keep things simple, we will concentrate on poses in the plane for now. \n", "Recall from Section 6.1 that a 2D pose\n", - "$T\\doteq(x,y,\\theta)$ is an element of the Special Euclidean group $SE(2)$,\n", - "and\n", - "can be represented by a $3\\times3$ matrix of the form\n", + "$T$ is an element of the Special Euclidean group $SE(2)$,\n", + "and can be represented by a $3\\times3$ matrix of the form\n", "\\begin{equation}\n", "T=\\left[\\begin{array}{cc|c}\n", "\\cos\\theta & -\\sin\\theta & x\\\\\n", @@ -141,22 +142,31 @@ "\\end{equation}\n", "with the $2\\times1$ vector $t$\n", "representing the position of the vehicle, and $R$ the $2\\times2$\n", - "rotation matrix representing the vehicle’s orientation in the plane.\n", - "\n", - "Below it is important to understand what coordinate frames are involved, and hence we insist on always annotating the transformation matrices with indices indicating the reference frame as superscript and the frame under consideration using subscript. This convention also\n", - "provides a reminder to how points in one frame are transformed into the other. \n", - "As an example, the following illustrates how points in the frame $b$ are transformed into the reference frame $a$ by referring to the pose $T^a_b$ of frame $b$ in $a$:\n", + "rotation matrix representing the vehicle’s orientation in the plane." + ] + }, + { + "cell_type": "markdown", + "id": "7c76a28f", + "metadata": {}, + "source": [ + "```{index} homogeneous coordinates\n", + "```\n", + "Below it is important to understand what coordinate frames are involved, and hence we insist on always annotating the transformation matrices with indices indicating the reference frame as superscript and the frame under consideration using subscript.\n", + "This convention also provides a reminder of how points in one frame are transformed into the other. \n", + "As an example, the following illustrates how points in the frame $b$ are transformed into the reference frame $a$ by referring to the pose $T^a_b$ of frame $b$ in $a$,\n", "\\begin{equation}\n", - "P^a = \\left[\\begin{array}{cc}\n", + "\\tilde P^a = \\left[\\begin{array}{cc}\n", "R^a_b & t^a_b\\\\\n", "0 & 1\n", - "\\end{array}\\right] P^b = T^a_b P^b \n", + "\\end{array}\\right] \\tilde P^b = T^a_b \\tilde P^b \n", "\\end{equation}\n", - "A good rule to keep in mind, illustrated above, is that the superscript $b$ of the point $P^b$ on the right-hand side is \"canceled\" by the subscript $b$ in the pose $T^a_b$, and so the left-hand side $P^a$ only retains the superscript $a$, indicating it now lives in the reference frame $a$. This rule is quite useful when implementing these types of transformations in code, as well. Also, keep in mind that we are talking about the *same* point $P$ in both cases: it is simply the *coordinates* that change by expressing them in a different frame.\n", + "where as before $\\tilde P$ denotes the *homogeneous coordinates* of the point $P$.\n", + "A good rule to keep in mind, illustrated above, is that the superscript $b$ of the point $\\tilde P^b$ on the right-hand side is \"canceled\" by the subscript $b$ in the pose $T^a_b$, and so the left-hand side $\\tilde P^a$ only retains the superscript $a$, indicating it now lives in the reference frame $a$. This rule is quite useful when implementing these types of transformations in code, as well. Also, keep in mind that we are talking about the *same* point $P$ in both cases: it is simply the *coordinates* that change by expressing them in a different frame.\n", "\n", "Note that this representation generalizes easily to three dimensions,\n", "but of course $t^a_b$ will be a three-vector, and $R^a_b$ will be a $3\\times3$\n", - "rotation matrix representing the 3DOF orientation of the vehicle. The\n", + "rotation matrix representing the 3D orientation of the vehicle. The\n", "latter can be decomposed into roll, pitch, and yaw, if so desired, \n", "but we will not need that until the next chapter." ] @@ -166,10 +176,10 @@ "id": "j3y7TEWrvCfe", "metadata": {}, "source": [ - "```{index} pair: iterative closest points; ICP\n", - "```\n", "## The Iterative Closest Points Algorithm\n", "\n", + "```{index} pair: iterative closest points; ICP\n", + "```\n", "> ICP is a seminal method to align two point clouds.\n", "\n", "**Iterative closest points** or **ICP** is a method to align two point clouds, e.g., two successive LIDAR scans, and it is a crucial component in the PoseSLAM algorithm we will discuss next. Leaning into the notation re-introduced above, we use superscripts $a$ and $b$ to distinguish the two point clouds, and the points therein. Under the assumption that we have a good initial estimate $\\hat{T^a_b}$ for the relative pose $T^a_b$ between the two point clouds, ICP iterates between two steps:\n", @@ -177,7 +187,7 @@ "1. Find closest point correspondences between the two clouds.\n", "2. Use these point correspondences to re-estimate the relative pose $\\hat{T^a_b}$ between the two point clouds.\n", "\n", - "These two steps are iterated until convergence, hence the name *iterated* closest points. Below we explain both steps in more detail." + "These two steps are iterated until convergence, hence the name *iterative* closest points. Below we explain both steps in more detail." ] }, { @@ -185,8 +195,6 @@ "id": "NMPwtW_xDJNv", "metadata": {}, "source": [ - "```{index} nearest neighbor, all nearest neighbors\n", - "```\n", "### Finding Closest Points\n", "\n", "The first step is the easiest: for each point $P^a_j$ in the first point cloud, we define $P^b_j$ to be its closest point in the second point cloud. Stated formally, for each point $P^a_j$, we solve the following minimization problem:\n", @@ -197,8 +205,10 @@ "(a) it can be solved using a linear search over all points $P^b$, \n", "and (b) rather than computing the distance we can save the computation of a square root by minimizing the square, which is just as good as they are monotonically related. \n", "\n", + "```{index} nearest neighbor, all nearest neighbors, KD-tree, Octree\n", + "```\n", "The linear search problem above is known as the **nearest neighbor** problem, and solving this problem for all points is the **all nearest neighbors** problem.\n", - "Iterating over all points in the second cloud can be quite slow, and indeed finding all nearest neighbors that way has quadratic complexity. However, very fast *approximate* nearest neighbor algorithms are available. Many of these use specialized data structures, such as \"KD-trees\" or \"Oct-trees\" (in 3D). While the details are beyond the scope of this book,\n", + "Iterating over all points in the second cloud can be quite slow, and indeed finding all nearest neighbors that way has quadratic complexity. However, very fast *approximate* nearest neighbor algorithms are available. Many of these use specialized data structures, such as *KD-trees* in 2D or Octrees in 3D. While the details are beyond the scope of this book,\n", "intuitively these data structures recursively divide up the point clouds into sub-clouds, such that sub-clouds unlikely to contain the nearest neighbor can be quickly excluded. We build this data structure once for the second cloud, and then use it for all nearest neighbor searches, leading to complexity which is approximately $O(N \\log N$),\n", "for point clouds of size $N$." ] @@ -208,12 +218,14 @@ "id": "tssyVdfvnbu3", "metadata": {}, "source": [ - "```{index} pose alignment\n", - "```\n", "### Estimating the Pairwise Transform\n", "\n", + "```{index} pose alignment\n", + "```\n", "The second step is the more interesting one: given a set of closest point pairs $\\{(P^a_j, P^b_j)\\}$, how can we estimate the relative pose $\\widehat{T^a_b}$ between the two point clouds? This is known as the **pose alignment** problem.\n", "\n", + "```{index} pose alignment\n", + "```\n", "Let us first assume that the two point clouds only differ by a rotation $R^a_b$. When this is the case, and assuming we have corresponding points $P^a$ and $P^b$, then each point $P^a$ in the first cloud can be expressed as a function of a point $P^b$ in the second cloud:\n", "\\begin{equation}\n", "P^a = R^a_b P^b\n", @@ -222,13 +234,16 @@ "\\begin{equation}\n", "R^a_b = P^a (P^b)^{-1}\n", "\\end{equation}\n", - "but, of course, there since $P^b$ is a vector, it does not have an inverse. So this would not work.\n", + "but, of course, since $P^b$ is a vector, it does not have an inverse. So this would not work.\n", "Interestingly, though, if we form the matrix\n", "\\begin{equation}\n", "H = \\sum_j P^a_j (P^b_j)^T\n", "\\end{equation}\n", "by summing over at least 3 point pairs $(P^a_j, P^b_j)$, it turns out that the rotation matrix $\\widehat{R^a_b}$ closest to $H$ in the least squares sense *is* the best possible estimate for the unknown rotation $R^a_b$. In addition, using the *singular value decomposition* from linear algebra, it is *very* easy to compute.\n", - "The singular value decomposition of the matrix $H$ is defined by $H=U\\Lambda V^T$, in which $\\Lambda$ is a diagonal\n", + "\n", + "```{index} singular value decomposition, orthogonal Procrustes problem\n", + "```\n", + "The **singular value decomposition** of the matrix $H$ is defined by $H=U\\Lambda V^T$, in which $\\Lambda$ is a diagonal\n", "matrix of singular values, and both $U$ and $V$ are orthogonal matrices.\n", "The optimal estimate for the rotation matrix is given by\n", "\\begin{equation}\n", @@ -263,8 +278,6 @@ "id": "--KNKw9c7Ia6", "metadata": {}, "source": [ - "```{index} PoseSLAM, pose constraints\n", - "```\n", "## PoseSLAM\n", "\n", "> PoseSLAM is SLAM with pose priors and relative pose constraints only. We can derive those from Iterative Closest Points (ICP).\n", @@ -277,6 +290,8 @@ "*not* know the map a priori, and hence we have to infer the unknown map\n", "while simultaneously estimating the robot's location with respect to the evolving map.\n", "\n", + "```{index} PoseSLAM, pose constraints\n", + "```\n", "**PoseSLAM** is a variant of SLAM that uses pose constraints (using an algorithm such as ICP) as a\n", "basic building block when optimizing over the unknown vehicle\n", "poses. \n", @@ -311,7 +326,7 @@ "\n", "> Factor graphs expose the sparse set of constraints tying absolute poses together.\n", "\n", - "In our factor-graph-based view of the world, a pose constraint is\n", + "In our factor-graph view of the world, a pose constraint is\n", "represented as a factor. As before, the factor graph represent the\n", "posterior distribution over the unknown pose variables\n", "$\\mathcal{T}=\\{X_{1}\\dots X_{5}\\}$ given the known measurements:\n", @@ -326,9 +341,7 @@ "
PoseSLAM factor graph example.
\n", "
\n", "\n", - "An example is shown in Figure\n", - "1.\n", - "The example represents a vehicle driving around, and taking LIDAR scans\n", + "An example is shown in Figure [1](#fig:PoseSLAMFG). The example represents a vehicle driving around, and taking LIDAR scans\n", "at 5 different world poses, represented by $T_{1}$ to $T_{5}$.\n", "The factors $f_{1}$ to $f_{4}$ are binary factors representing the pose\n", "constraints obtained by matching successive LIDAR scans using ICP. " @@ -357,7 +370,7 @@ "id": "0198_knSJ3nD", "metadata": {}, "source": [ - "## MAP Inference via Least-Squares\n", + "## MAP Inference via Least Squares\n", "\n", "> Linear problems with zero-mean Gaussian noise lead to least-squares problems.\n", "\n", @@ -391,27 +404,25 @@ "metadata": {}, "source": [ "An example can help explain this more clearly.\n", - "In particular, if we focus our attention in PoseSLAM on *just the x coordinates*, then we\n", - "predict relative measurements $\\tilde{x}_{ij}$ by\n", + "In particular, if we focus our attention in PoseSLAM on *just the x coordinates*.\n", + "Let us denote the *relative measurements* $\\tilde{x}_{ij}$, predicted by\n", "\\begin{equation}\n", "\\tilde{x}_{ij}\\approx h(x_{i,}x_{j})=x_{j}-x_{i}\n", "\\end{equation}\n", - "and, taking $\\sigma=1$ for now, each factor in\n", - "Figure\n", - "1\n", - "could be written as\n", + "and, taking $\\sigma=1$ for now, each factor in Figure [1](#fig:PoseSLAMFG) could be written as\n", "\\begin{equation}\n", "\\phi(x_{i},x_{j})=\\frac{1}{\\sqrt{2\\pi}}\\exp\\left\\{ -\\frac{1}{2}\\left(x_{j}-x_{i}-\\tilde{x}_{ij}\\right)^{2}\\right\\} ,\n", "\\end{equation}\n", "By taking the negative log,\n", "maximizing the posterior corresponds to minimizing the following sum of\n", - "squares, where them sum ranges over all $(i,j)$ pairs for which we have a\n", + "squares, where the sum ranges over all $(i,j)$ pairs for which we have a\n", "pairwise measurement:\n", "\\begin{equation}\n", "\\mathcal{X}^{*}=\\arg\\min_{\\mathcal{X}}\\sum_{k}\\frac{1}{2}\\left(h(x_{i},x_{j})-\\tilde{x}_{ij}\\right)^{2}=\\arg\\min_{\\mathcal{X}}\\sum_{k}\\frac{1}{2}\\left(x_{j}-x_{i}-\\tilde{x}_{ij}\\right)^{2}.\n", "\\end{equation}\n", - "Linear least squares problems like these are easily solved by numerical\n", - "computing packages like MATLAB or numpy." + "The minimization is over the set of all unknown poses $\\mathcal{X}\\doteq\\{x_i\\}$ and yields the MAP estimate $\\mathcal{X}^{*}$ for the poses.\n", + "Linear least-squares problems like these are easily solved by numerical\n", + "computing packages like MATLAB or `numpy`." ] }, { @@ -428,19 +439,18 @@ "simply linear functions of the poses. Indeed, in PoseSLAM both the\n", "prediction $h(T_{i},T_{j})$ and the measurement $\\tilde{T}_{ij}$\n", "are relative poses. The measurement prediction function $h(\\cdot)$ is given\n", - "by \n", + "by the *relative transformation* $T_ij \\doteq T_{i}^{-1}T_{j}$:\n", "\\begin{equation}\n", - "h(T_{i},T_{j})=T_{i}^{-1}T_{j}\n", + "h(T_{i},T_{j})=T_{i}^{-1}T_{j}.\n", "\\end{equation}\n", - "and the\n", - "measurement error to be minimized is\n", + "Hence, the measurement error to be minimized is\n", "\\begin{equation}\n", "\\frac{1}{2}\\left\\Vert \\log\\left(\\tilde{T}_{ij}^{-1}T_{i}^{-1}T_{j}\\right)\\right\\Vert ^{2}\n", "\\end{equation}\n", "where $\\log:SE(2)\\rightarrow\\mathbb{R}^3$ is a variation of the logarithm function\n", "that maps a pose in $SE(2)$ to a\n", - "three-dimensional local coordinate vector $\\xi$;\n", - "we will describe this in more detail below." + "three-dimensional local coordinate vector $\\xi$.\n", + "We will describe this in more detail below." ] }, { @@ -448,7 +458,7 @@ "id": "_LZULYj4-Fn5", "metadata": {}, "source": [ - "```{index} nonlinear optimization\n", + "```{index} nonlinear optimization, rotation averaging\n", "```\n", "There are two ways to deal with this nonlinear problem. The first is to\n", "realize that the only nonlinearities stem from the $\\sin$ and $\\cos$\n", @@ -480,18 +490,17 @@ "an initial estimate provided using the method in the previous section.\n", "This approach might well succeed in finding matrices $T_i$ that minimize the error,\n", "but, unfortunately, there is no guarantee that $T_i \\in SE(2)$, since\n", - "this approach does not ensure that the upper right submatrix is a valid rotation matrix.\n", + "this approach does not ensure that the upper left submatrix is a valid rotation matrix.\n", "\n", "Instead, we will locally linearize the problem and solve\n", - "the corresponding linear problem using least-squares, and iterate this\n", + "the corresponding linear problem using least squares, and iterate this\n", "until convergence. We do this by, at each iteration, parameterizing a\n", "pose $T$ by \n", "\\begin{equation}\n", "T\\approx\\bar{T}\\Delta(\\xi)\n", "\\end{equation}\n", - "where $\\xi$ are local coordinates\n", - "$\\xi\\doteq(\\delta x,\\delta y,\\delta\\theta)$ and the incremental pose\n", - "$\\Delta(\\xi)\\in SE(2)$ is defined as\n", + "where $\\xi \\doteq (\\delta x,\\delta y,\\delta\\theta)$ is a 3-vector of local coordinates,\n", + "and the incremental pose $\\Delta(\\xi)\\in SE(2)$ is defined as\n", "\\begin{equation}\n", "\\Delta(\\xi)=\\left[\\begin{array}{cc|c}\n", "1 & -\\delta\\theta & \\delta x\\\\\n", @@ -500,9 +509,20 @@ "\\end{array}\\right]\n", "\\end{equation}\n", "which you can recognize as a small angle\n", - "approximation of the $SE(2)$ matrix.\n", - "In 3D the local coordinates $\\xi$ are 6-dimensional, and the small angle\n", + "approximation of the $SE(2)$ matrix, since $\\cos \\theta \\approx 1$ and $\\sin \\theta \\approx \\theta$ for small $\\theta$." + ] + }, + { + "cell_type": "markdown", + "id": "cf787f5c", + "metadata": {}, + "source": [ + "```{index} Jacobian matrix\n", + "```\n", + "In 3D the local coordinate vector $\\xi$ is *6-dimensional*, and the small angle\n", "approximation is defined as \n", + "```{math}\n", + ":label: eq:delta3d\n", "\\begin{equation}\n", "\\Delta(\\xi)=\\left[\\begin{array}{ccc|c}\n", "1 & -\\delta\\theta_{z} & \\delta\\theta_{y} & \\delta x\\\\\n", @@ -511,17 +531,19 @@ "\\hline 0 & 0 & 0 & 1\n", "\\end{array}\\right]\n", "\\end{equation}\n", - "With this new notation, we can approximate the\n", - "nonlinear error by a linear approximation:\n", + "```\n", + "with $\\xi \\doteq (\\theta_{x},\\theta_{y},\\theta_{z},\\delta x,\\delta y,\\delta z)$.\n", + "It is well known that in this case we can also approximate the\n", + "nonlinear error by the following linear approximation:\n", "\\begin{equation}\n", "\\frac{1}{2}\\left\\Vert \\log\\left(\\tilde{T}_{ij}^{-1}T_{i}^{-1}T_{j}\\right)\\right\\Vert ^{2}\\approx\\frac{1}{2}\\left\\Vert A_{i}\\xi_{i}+A_{j}\\xi_{j}-b\\right\\Vert ^{2}.\n", "\\end{equation}\n", - "For $SE(2)$ the matrices $A_{i}$ and $A_{j}$ are the $3\\times3$ **or\n", - "Jacobian matrices** and $b$ is a $3\\times1$ bias term. The above\n", + "For $SE(2)$ the matrices $A_{i}$ and $A_{j}$ are the $3\\times3$\n", + "**Jacobian matrices** and $b$ is a $3\\times1$ bias term. The above\n", "provides a linear approximation of the term within the norm as a\n", "function of the incremental local coordinates $\\xi_{i}$ and $\\xi_{j}$.\n", "Deriving the detailed expressions for these Jacobians is beyond the\n", - "scope of this document, but suffice to say that they exist and not too\n", + "scope of this book, but suffice to say that they exist and are not too\n", "expensive to compute. In three dimensions, the Jacobian matrices are\n", "$6\\times6$ and $16\\times6$, respectively." ] @@ -532,18 +554,22 @@ "metadata": {}, "source": [ "The final optimization will—in each iteration—minimize over the local\n", - "coordinates of all poses by summing over all pose constraints. If we\n", - "index those constraints by $k$, we have the following least squares\n", + "coordinates of all poses by summing over all pose constraints.\n", + "If we index those constraints by $k$, we have the following least-squares\n", "problem:\n", "\\begin{equation}\n", "\\Xi^{*}=\\arg\\min_{\\Xi}\\sum_{k}\\frac{1}{2}\\Vert A_{ki}\\xi_{i}+A_{kj}\\xi_{j}-b_{k}\\Vert ^{2}\n", "\\end{equation}\n", "where $\\Xi\\doteq \\{ \\xi_{i}\\}$, the set\n", "of all incremental pose coordinates.\n", - "\n", "After solving for the incremental updates $\\Xi$, we update all poses\n", - "using the update equation above and check for convergence. \n", - "If the error does not decrease significantly\n", + "using \n", + "\\begin{equation}\n", + "T_i\\approx\\bar{T}_i\\Delta(\\xi_i),\n", + "\\end{equation}\n", + "with $\\Delta(\\xi_i)$ defined as in {eq}`eq:delta3d`.\n", + "\n", + "We then check for convergence: if the error does not decrease significantly\n", "we terminate, otherwise we linearize and solve again, until the error\n", "converges. While this is not guaranteed to converge to a global minimum,\n", "in practice it does so if there are enough relative measurements and a\n", @@ -551,8 +577,14 @@ "a good initial estimate. However, especially in urban environments GPS\n", "can be quite noisy, and it could happen that the map quality suffers by\n", "converging to a bad local minimum. Hence, a good quality control process\n", - "is absolutely necessary in production environments.\n", - "\n", + "is absolutely necessary in production environments." + ] + }, + { + "cell_type": "markdown", + "id": "2dc4990a", + "metadata": {}, + "source": [ "In summary, the algorithm for nonlinear optimization is\n", "\n", "- Start with an initial estimate $\\mathcal{T}^{0}$\n", @@ -562,14 +594,12 @@ " 1. Linearize the factors\n", "$\\frac{1}{2}\\Vert \\log(\\tilde{T}_{ij}^{-1}T_{i}^{-1}T_{j})\\Vert ^{2}\\approx\\frac{1}{2}\\Vert A_{i}\\xi_{i}+A_{j}\\xi_{j}-b\\Vert ^{2}$\n", "\n", - " 2. Solve the least squares problem\n", + " 2. Solve the least-squares problem\n", " $\\Xi^{*}=\\arg\\min_{\\Xi}\\sum_{k}\\frac{1}{2}\\Vert A_{ki}\\xi_{i}+A_{kj}\\xi_{j}-b_{k}\\Vert ^{2}$\n", "\n", " 3. Update $X_{i}^{t+1}\\leftarrow X_{j}^{t}\\Delta(\\xi_{i})$\n", "\n", - "- Until the nonlinear error\n", - "$J(\\mathcal{T})\\doteq\\sum_{k}\\frac{1}{2}\\Vert \\log(\\tilde{T}_{ij}^{-1}T_{i}^{-1}T_{j})\\Vert ^{2}$\n", - " converges." + "- Until the nonlinear error $\\sum_{k}\\frac{1}{2}\\Vert \\log(\\tilde{T}_{ij}^{-1}T_{i}^{-1}T_{j})\\Vert ^{2}$ converges." ] }, { @@ -592,19 +622,31 @@ "to be handled efficiently by direct methods, GTSAM provides iterative\n", "methods that are quite efficient.\n", "\n", - "The following code, included in GTSAM as an example, creates the\n", - "factor graph from Figure\n", - "1\n", - "in code:" + "```{index} loop closure\n", + "```\n", + "The code in Figure [2](#fig:createPoseSLAMFG), included in GTSAM as an example, creates the factor graph from Figure [1](#fig:PoseSLAMFG).\n", + "The first block of code creates a nonlinear factor graph and adds the unary factor\n", + "$f_{0}(T_{1})$. \n", + "As the vehicle travels through the world, it adds\n", + "binary factors $f_{t}(T_{t},T_{t+1})$ corresponding to odometry measurements.\n", + "Note that the final line of code models a different event: a **loop closure**.\n", + "For example,\n", + "the vehicle might recognize the same location using vision or a laser\n", + "range finder, and calculate the geometric pose constraint to when it\n", + "first visited this location. This is illustrated for poses $T_{5}$\n", + "and $T_{2}$, and generates the (red) loop closing factor\n", + "$f_{5}(T_{5},T_{2})$." ] }, { "cell_type": "code", "execution_count": 3, - "id": "U8yJn648mS21", + "id": "f7541eb6", "metadata": {}, "outputs": [], "source": [ + "#| caption: Code to re-create the factor graph from Figure [1](#fig:PoseSLAMFG).\n", + "#| label: fig:createPoseSLAMFG\n", "graph = gtsam.NonlinearFactorGraph()\n", "prior_model = gtsam.noiseModel.Diagonal.Sigmas((0.3, 0.3, 0.1))\n", "graph.add(gtsam.PriorFactorPose2(1, gtsam.Pose2(0, 0, 0), prior_model))\n", @@ -618,27 +660,7 @@ "graph.add(Between(4, 5, gtsam.Pose2(2, 0, np.pi/2), odometry_model))\n", "\n", "# Add the loop closure constraint\n", - "graph.add(Between(5, 2, gtsam.Pose2(2, 0, np.pi/2), odometry_model))\n" - ] - }, - { - "cell_type": "markdown", - "id": "PnmuWtGHBVde", - "metadata": {}, - "source": [ - "```{index} loop closure\n", - "```\n", - "The first block of code creates a nonlinear factor graph and adds the unary factor\n", - "$f_{0}(T_{1})$. \n", - "As the vehicle travels through the world, it adds\n", - "binary factors $f_{t}(T_{t},T_{t+1})$ corresponding to odometry measurements.\n", - "Note that the final line of code models a different event: a **loop closure**.\n", - "For example,\n", - "the vehicle might recognize the same location using vision or a laser\n", - "range finder, and calculate the geometric pose constraint to when it\n", - "first visited this location. This is illustrated for poses $T_{5}$\n", - "and $T_{2}$, and generates the (red) loop closing factor\n", - "$f_{5}(T_{5},T_{2})$.\n" + "graph.add(Between(5, 2, gtsam.Pose2(2, 0, np.pi/2), odometry_model))" ] }, { @@ -646,7 +668,7 @@ "id": "7WvObRdMv2-3", "metadata": {}, "source": [ - "Before we can optimize, we need to create an initial estimate. In GTSAM, this is done via the `Values` type:" + "Before we can optimize, we need to create an initial estimate. In GTSAM, this is done via the `Values` type, as shown in Figure [3](#fig:create-initial-estimate). We can use this initial estimate to `show` the factor graph in Figure [4](#fig:factor_graph_with_loop_closure). It does not look as pretty as the hand-crafted figure, but it has the advantage that it can be programmatically generated." ] }, { @@ -685,22 +707,15 @@ } ], "source": [ - "# Create the initial estimate\n", + "#| caption: Create the initial estimate.\n", + "#| label: fig:create-initial-estimate\n", "initial_estimate = gtsam.Values()\n", "initial_estimate.insert(1, gtsam.Pose2(0.5, 0.0, 0.2))\n", "initial_estimate.insert(2, gtsam.Pose2(2.3, 0.1, -0.2))\n", "initial_estimate.insert(3, gtsam.Pose2(4.1, 0.1, np.pi/2))\n", "initial_estimate.insert(4, gtsam.Pose2(4.0, 2.0, np.pi))\n", "initial_estimate.insert(5, gtsam.Pose2(2.1, 2.1, -np.pi/2))\n", - "print(initial_estimate)\n" - ] - }, - { - "cell_type": "markdown", - "id": "c9uRjxXc4CNB", - "metadata": {}, - "source": [ - "We can use this initial estimate to show the factor graph below:" + "print(initial_estimate)" ] }, { @@ -797,7 +812,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -808,7 +823,7 @@ "source": [ "#| caption: Factor graph with odometry and loop closure constraints.\n", "#| label: fig:factor_graph_with_loop_closure\n", - "show(graph, initial_estimate, binary_edges=True)\n" + "show(graph, initial_estimate, binary_edges=True)" ] }, { @@ -816,7 +831,7 @@ "id": "BuQrLvLe7dCs", "metadata": {}, "source": [ - "Optimization is done using non-linear minimization, as explained above. In GTSAM, this is done via a `NonlinearOptimizer` class. The specific optimizer we use below is `GaussNewtonOptimizer`, which exactly implements the pseudo-code given above, but exploiting sparsity in the factor graph to do this very efficiently. The optimizer only needs a graph and an initial estimate, both of which we already created, and hence the code below is quite simple:" + "Optimization is done using nonlinear minimization, as explained above. In GTSAM, this is done via a `NonlinearOptimizer` class. The specific optimizer we use below is `GaussNewtonOptimizer`, which exactly implements the pseudocode given above, but exploiting sparsity in the factor graph to do this very efficiently. The optimizer only needs a graph and an initial estimate, both of which we already created. Hence the code, shown in Figure [5](#fig:optimize-graph) is quite simple; the final optimized result is shown there as well, albeit as plain text." ] }, { @@ -856,10 +871,11 @@ } ], "source": [ - "# Optimize the initial values using a Gauss-Newton nonlinear optimizer\n", + "#| caption: Optimize the initial values using a Gauss-Newton nonlinear optimizer.\n", + "#| label: fig:optimize-graph\n", "optimizer = gtsam.GaussNewtonOptimizer(graph, initial_estimate)\n", "result = optimizer.optimize()\n", - "print(\"Final Result:\\n{}\".format(result))\n" + "print(\"Final Result:\\n{}\".format(result))" ] }, { @@ -869,9 +885,7 @@ "source": [ "```{index} posterior marginals\n", "```\n", - "We can also inspect the result graphically. Looking at the result as printed above only gets us so far, and more importantly, it only shows us the maximum a posteriori (MAP) solution, but not the uncertainty around it. Luckily, GTSAM can also compute the **posterior marginals**, which show the uncertainty on each recovered pose as a Gaussian density $P(T_i|Z)$, taking into account all the measurements $Z$.\n", - "\n", - "In code, we do this via the `Marginals` object, and we can plot marginals with a special function `plot_pose2`:" + "We can also inspect the result graphically. Looking at the result as printed above only gets us so far, and more importantly, it only shows us the maximum a posteriori (MAP) solution, but not the uncertainty around it. Luckily, GTSAM can also compute the **posterior marginals**, which show the uncertainty on each recovered pose as a conditional Gaussian density $P(T_i|Z)$, taking into account all the measurements $Z$." ] }, { @@ -904,7 +918,7 @@ "for i in range(1, 6):\n", " gtsam_plot.plot_pose2(0, result.atPose2(i), 0.5,\n", " marginals.marginalCovariance(i))\n", - "plt.axis('equal'); plt.show()\n" + "plt.axis('equal'); plt.show()" ] }, { @@ -912,9 +926,7 @@ "id": "p6ho405yBJc9", "metadata": {}, "source": [ - "The result is shown graphically in Figure\n", - "2,\n", - "along with covariance ellipses. These covariance ellipses\n", + "In code, we do this via the `Marginals` object, and we can plot marginals with a special function `plot_pose2`, as shown in Figure [6](#fig:optimized_trajectory_with_covariances), which also shows the result graphically along with covariance ellipses. These covariance ellipses\n", "in 2D indicate the marginal over position, *over all possible\n", "orientations*, and show the area that contains 99% of the probability\n", "mass (in 1D this would correspond to three standard deviations). The graph\n", @@ -934,14 +946,14 @@ } }, "source": [ - "```{index} range, bearing\n", - "```\n", "## SLAM with Landmarks\n", "\n", "> Take PoseSLAM, add landmarks.\n", "\n", "So far we optimized over one type of variable, but often we build a landmark map *simultaneously* with the trajectory, i.e., this is *true* SLAM. In the next chapter, we will more thoroughly examine the full 3D case, whereas here we will model landmarks with 2D points in the plane. That does not mean that they cannot represent real 3D entities in the environment: they can be the location of trees, poles, building corners, the sides of windows, the location of a stop sign in traffic, even moving pedestrians in more advanced applications.\n", "\n", + "```{index} range measurement, bearing measurement, bearing-range measurement\n", + "```\n", "How do we measure such landmarks? The most typical *type* of measurements are either **range** measurements, **bearing** measurements, or **bearing-range** measurements. The details on how to obtain them are typically application-dependent, and below we will abstract away the sensor preprocessing details. For example, in the case of a LIDAR sensor, \n", "bearing-range measurements can be obtained by preprocessing every LIDAR scan, detecting prominent vertical structures for example. A real-life example that we will discuss below involves detecting and measuring the bearing-range to trees.\n", "Radar is another often-used sensor for autonomous driving, and it can often be modeled or idealized to give \n", @@ -970,7 +982,7 @@ "slam_graph = gtsam.NonlinearFactorGraph()\n", "slam_graph.add( gtsam.PriorFactorPose2(1, gtsam.Pose2(0.0, 0.0, 0.0), prior_model))\n", "slam_graph.add(Between(1, 2, gtsam.Pose2(2.0, 0.0, 0.0), odometry_model))\n", - "slam_graph.add(Between(2, 3, gtsam.Pose2(2.0, 0.0, 0.0), odometry_model))\n" + "slam_graph.add(Between(2, 3, gtsam.Pose2(2.0, 0.0, 0.0), odometry_model))" ] }, { @@ -986,7 +998,7 @@ "l = {k:gtsam.symbol('l',k) for k in [1,2]} # name landmark variables\n", "slam_graph.add(BR(1, l[1], gtsam.Rot2.fromDegrees(45),np.sqrt(4.0 + 4.0), measurement_model)) # pose 1 -*- landmark 1\n", "slam_graph.add(BR(2, l[1], gtsam.Rot2.fromDegrees(90), 2.0,measurement_model)) # pose 2 -*- landmark 1\n", - "slam_graph.add(BR(3, l[2], gtsam.Rot2.fromDegrees(90), 2.0,measurement_model)) # pose 3 -*- landmark 2\n" + "slam_graph.add(BR(3, l[2], gtsam.Rot2.fromDegrees(90), 2.0,measurement_model)) # pose 3 -*- landmark 2" ] }, { @@ -998,27 +1010,12 @@ } }, "source": [ - "When we have an initial estimate, we can look at the structure of this factor graph:" + "When we have an initial estimate, we can look at the structure of this factor graph. We create the initial estimate and show the resulting factor graph in Figure [7](#fig:factor_graph_with_range_bearing)." ] }, { "cell_type": "code", "execution_count": 10, - "id": "1f365542", - "metadata": {}, - "outputs": [], - "source": [ - "slam_initial = gtsam.Values()\n", - "slam_initial.insert(1, gtsam.Pose2(-0.25, 0.20, 0.15))\n", - "slam_initial.insert(2, gtsam.Pose2(2.30, 0.10, -0.20))\n", - "slam_initial.insert(3, gtsam.Pose2(4.10, 0.10, 0.10))\n", - "slam_initial.insert(l[1], gtsam.Point2(1.80, 2.10))\n", - "slam_initial.insert(l[2], gtsam.Point2(4.10, 1.80))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, "id": "f10793f5", "metadata": {}, "outputs": [ @@ -1104,10 +1101,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -1115,7 +1112,13 @@ "source": [ "#| caption: Factor graph with odometry and range-bearing constraints.\n", "#| label: fig:factor_graph_with_range_bearing\n", - "show(slam_graph, slam_initial, binary_edges=True)\n" + "slam_initial = gtsam.Values()\n", + "slam_initial.insert(1, gtsam.Pose2(-0.25, 0.20, 0.15))\n", + "slam_initial.insert(2, gtsam.Pose2(2.30, 0.10, -0.20))\n", + "slam_initial.insert(3, gtsam.Pose2(4.10, 0.10, 0.10))\n", + "slam_initial.insert(l[1], gtsam.Point2(1.80, 2.10))\n", + "slam_initial.insert(l[2], gtsam.Point2(4.10, 1.80))\n", + "show(slam_graph, slam_initial, binary_edges=True)" ] }, { @@ -1127,23 +1130,12 @@ } }, "source": [ - "We optimize again using Levenberg Marquardt, and show the marginals on both robot position and landmarks, as before:" + "We optimize again using Levenberg Marquardt, and show the marginals on both robot position and landmarks, as before, in Figure [8](#fig:fig:optimized_trajectory_with_covariances_and_landmarks_br)." ] }, { "cell_type": "code", - "execution_count": 12, - "id": "b16128fd", - "metadata": {}, - "outputs": [], - "source": [ - "optimizer = gtsam.LevenbergMarquardtOptimizer(slam_graph, slam_initial)\n", - "slam_result = optimizer.optimize()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "ad5f9406", "metadata": {}, "outputs": [ @@ -1161,12 +1153,14 @@ "source": [ "#| caption: The optimized trajectory with the estimated covariances, with bearing-range measurements to landmarks.\n", "#| label: fig:optimized_trajectory_with_covariances_and_landmarks_br\n", + "optimizer = gtsam.LevenbergMarquardtOptimizer(slam_graph, slam_initial)\n", + "slam_result = optimizer.optimize()\n", "marginals = gtsam.Marginals(slam_graph, slam_result)\n", "for k in [1,2,3]:\n", " gtsam_plot.plot_pose2(0, slam_result.atPose2(k), 0.5, marginals.marginalCovariance(k))\n", "for j in [1,2]:\n", " gtsam_plot.plot_point2(0, slam_result.atPoint2(l[j]), 'g', P=marginals.marginalCovariance(l[j]))\n", - "plt.axis('equal'); plt.show()\n" + "plt.axis('equal'); plt.show()" ] }, { @@ -1190,24 +1184,12 @@ } }, "source": [ - "Below we optimize a piece of the (old) [Victoria park dataset](http://www-personal.acfr.usyd.edu.au/nebot/victoria_park.htm), which involves a truck driving through a park in Sydney, extracting the position of trees in the park from LIDAR scans, just as we discussed above. The factor graph for this example is created from file and shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "03f76695", - "metadata": {}, - "outputs": [], - "source": [ - "datafile = gtsam.findExampleDataFile('example.graph')\n", - "model = gtsam.noiseModel.Diagonal.Sigmas([0.05,0.05,2*math.pi/180])\n", - "[graph,initial] = gtsam.load2D(datafile, model)" + "Below we optimize a piece of the (old) [Victoria park dataset](http://www-personal.acfr.usyd.edu.au/nebot/victoria_park.htm), which involves a truck driving through a park in Sydney, extracting the position of trees in the park from LIDAR scans, just as we discussed above. The factor graph for this example is created from a file and shown in Figure [9](#fig:factor_graph_real_world)." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "Cr23r56T7G77", "metadata": {}, "outputs": [ @@ -4522,10 +4504,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 15, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -4533,7 +4515,10 @@ "source": [ "#| caption: Factor graph for a more realistic example, derived from a real-world dataset.\n", "#| label: fig:factor_graph_real_world\n", - "show(graph,initial, binary_edges=True)\n" + "datafile = gtsam.findExampleDataFile('example.graph')\n", + "model = gtsam.noiseModel.Diagonal.Sigmas([0.05,0.05,2*math.pi/180])\n", + "[graph,initial] = gtsam.load2D(datafile, model)\n", + "show(graph,initial, binary_edges=True)" ] }, { @@ -4551,12 +4536,13 @@ "- There are about 20 landmarks, some of which are seen briefly, while others are seen for longer periods of time.\n", "- The graph is very sparsely connected, and hence optimization will still be quite fast.\n", "\n", - "Optimizing with `LevenbergMarquardtOptimizer`, again..." + "{raw:tex}`noindent`\n", + "Optimizing with `LevenbergMarquardtOptimizer`, again:" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "id": "b90abaa8", "metadata": {}, "outputs": [], @@ -4565,7 +4551,7 @@ "for i in range(initial_poses.shape[0]):\n", " initial.update(i, initial.atPose2(i).retract(np.random.normal(scale=0.5,size=(3,))))\n", "optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial)\n", - "result = optimizer.optimize()\n" + "result = optimizer.optimize()" ] }, { @@ -4573,33 +4559,18 @@ "id": "jPgMhvI_YUOI", "metadata": {}, "source": [ - "Below we plot both the initial estimate, which was created by adding random noise on top of the ground truth, and the optimized trajectory:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "542c8588", - "metadata": {}, - "outputs": [], - "source": [ - "initial_poses = gtsam.utilities.extractPose2(initial)\n", - "fig = go.Figure()\n", - "fig.add_scatter(x=initial_poses[:,0], y=initial_poses[:,1], name=\"initial\", marker=dict(color='orange'))\n", - "final_poses = gtsam.utilities.extractPose2(result)\n", - "fig.add_scatter(x=final_poses[:,0], y=final_poses[:,1], name=\"optimized\", marker=dict(color='green'))\n", - "fig.update_yaxes(scaleanchor = \"x\",scaleratio = 1);" + "In Figure [10](#fig:initial_and_optimized_trajectories) we plot both the initial estimate, which was created by adding random noise on top of the ground truth, and the optimized trajectory." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "KI7oZZmf1a9x", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAArwAAAH0CAYAAADfWf7fAAAgAElEQVR4XuydCVhVRePGX9lBuCC45EpquZRi9uVWgZUWVmqflVppVm71fVpqZYtLWqntaZ/aopWZWmmWFbZgtgj/XCvXUstUSNxRNtkv/J8ZvDd27p059wCX9zxPTyLzzpzzmxF+DHNm6hUWFhaCFwmQAAmQAAmQAAmQAAm4KYF6FF437Vk+FgmQAAmQAAmQAAmQgCRA4eVAIAESIAESIAESIAEScGsCFF637l4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMCtCVB43bp7+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABtyZA4XXr7uXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABNyaAIXXrbuXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARJwawIUXrfuXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwK0JUHjdunv5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAG3JkDhdevu5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAESIAE3JoAhdetu5cPRwIkQAIkQAIkQAIkQOHlGCABEiABEiABEiABEnBrAhRet+5ePhwJkAAJkAAJkAAJkACFl2OABEiABEiABEiABEjArQlQeN26e/lwJEACJEACJEACJEACFF6OARIgARIgARIgARIgAbcmQOF16+7lw5EACZAACZAACZAACVB4OQZIgARIgARIgARIgATcmgCF1627lw9HAiRAAiRAAiRAAiRA4eUYIAESIAESIAESIAEScGsCFF637l4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMCtCVB43bp7+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABtyZA4XXr7uXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABNyaAIXXrbuXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARJwawIUXrfuXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwK0JUHjdunv5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAG3JkDhdevu5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAESIAE3JoAhdetu5cPRwIkQAIkQAIkQAIkQOHlGCABEiABEiABEiABEnBrAhRet+5ePhwJkAAJkAAJkAAJkACFl2OABEiABEiABEiABEjArQlQeN26e/lwJEACJEACJEACJEACFF6OARIgARIgARIgARIgAbcmQOF16+7lw5EACZAACZAACZAACVB4OQZIgARIgARIgARIgATcmgCF1627lw9HAiRAAiRAAiRAAiRA4eUYIAESIAESIAESIAEScGsCFF637l4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMCtCVB43bp7+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABtyZA4XXr7uXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABNyaAIXXrbuXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARJwawIUXrfuXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwK0JUHjdunv5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAG3JkDhdevu5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAESIAE3JoAhdetu5cPRwIkQAIkQAIkQAIkQOHlGCABEiABEiABEiABEnBrAhRet+5ePhwJkAAJkAAJkAAJkACFl2OABEiABEiABEiABEjArQlQeN26e/lwJEACJEACJEACJEACFF6OARIgARIgARIgARIgAbcmQOF16+7lw5EACZAACZAACZAACVB4OQZIgARIgARIgARIgATcmgCF1627lw9HAiRAAiRAAiRAAiRA4eUYIAESIAESIAESIAEScGsCFF637l4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMCtCVB43bp7+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABtyZA4XXr7uXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABNyaAIXXrbuXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARJwawIUXrfuXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwK0JUHjdunv5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAG3JkDhdevu5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAE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" + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArwAAAH0CAYAAADfWf7fAAAgAElEQVR4XuydB3RVxdqG3/QCKRB6C4hKUUC80tSAChJUUOEiFppUC3BVFFREsQAqFvACegURFfAK6kUBERAUkl+ahaYGVISEGjCQRnr510w4h/TsObPPJtl591ougfN9M3s/30CeTGbPeOTn5+eDFwmQAAmQAAmQAAmQAAnYlIAHhdemleVjkQAJkAAJkAAJkAAJSAIUXg4EEiABEiABEiABEiABWxOg8Nq6vHw4EiABEiABEiABEiABCi/HAAmQAAmQAAmQAAmQgK0JUHhtXV4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMDWBCi8ti4vH44ESIAESIAESIAESIDCyzFAAiRAAiRAAiRAAiRgawIUXluXlw9HAiRAAiRAAiRAAiRA4eUYIAESIAESIAESIAESsDUBCq+ty8uHIwESIAESIAESIAESoPByDJAACZAACZAACZAACdiaAIXX1uXlw5EACZAACZAACZAACVB4OQZIgARIgARIgARIgARsTYDCa+vy8uFIgARIgARIgARIgAQovBwDJEACJEACJEACJEACtiZA4bV1eflwJEACJEACJEACJEACFF6OARIgARIgARIgARIgAVsToPDaurx8OBIgARIgARIgARIgAQovxwAJkAAJkAAJkAAJkICtCVB4bV1ePhwJkAAJkAAJkAAJkACFl2OABEiABEiABEiABEjA1gQovLYuLx+OBEiABEiABEiABEiAwssxQAIkQAIkQAIkQAIkYGsCFF5bl5cPRwIkQAIkQAIkQAIkQOHlGCABEiABEiABEiABErA1AQqvrcvLhyMBEiABEiABEiABEqDwcgyQAAmQAAmQAAmQAAnYmgCF19bl5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAESIAEbE2Awmvr8vLhSIAESIAESIAESIAEKLwcAyRAAiRAAiRAAiRAArYmQOG1dXn5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAFbE6Dw2rq8fDgSIAESIAESIAESIAEKL8cACZAACZAACZAACZCArQlQeG1dXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwNYEKLy2Li8fjgRIgARIgARIgARIgMLLMUACJEACJEACJEACJGBrAhReW5eXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARKwNQEKr63Ly4cjARIgARIgARIgARKg8HIMkAAJkAAJkAAJkAAJ2JoAhdfW5eXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABGxNgMJr6/Ly4UiABEiABEiABEiABCi8HAMkQAIkQAIkQAIkQAK2JkDhtXV5+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABWxOg8Nq6vHw4EiABEiABEiABEiABCi/HAAmQAAmQAAmQAAmQgK0JUHhtXV4+HAmQAAmQAAmQAAmQAIWXY4AESIAESIAESIAESMDWBCi8ti4vH44ESIAESIAESIAESIDCyzFAAiRAAiRAAiRAAiRgawIUXluXlw9HAiRAAiRAAiRAAiRA4eUYIAESIAESIAESIAESsDUBCq+ty8uHIwESIAESIAESIAESoPByDJAACZAACZAACZAACdiaAIXX1uXlw5EACZAACZAACZAACVB4OQZIgARIgARIgARIgARsTYDCa+vy8uFIgARIgARIgARIgAQovBwDJEACJEACJEACJEACtiZA4bV1eflwJEACJEACJEACJEACFF6OARIgARIgARIgARIgAVsToPDaurx8OBIgARIgARIgARIgAQovxwAJkAAJkAAJkAAJkICtCVB4bV1ePhwJkAAJkAAJkAAJkACFl2OABEiABEiABEiABEjA1gQovLYuLx+OBEiABEiABEiABEiAwssxQAIkQAIkQAIkQAIkYGsCFF5bl5cPRwIkQAIkQAIkQAIkQOHlGCABEiABEiABEiABErA1AQqvrcvLhyMBEiABEiABEiABEqDwcgyQAAmQAAmQAAmQAAnYmgCF19bl5cORAAmQAAmQAAmQAAlQeDkGSIAESIAESIAESIAEbE2Awmvr8vLhSIAESIAESIAESIAEKLwcAyRAAiRAAiRAAiRAArYmQOG1dXn5cCRAAiRAAiRAAiRAAhRejgESIAESIAESIAESIAFbE6Dw2rq8fDgSIAESIAESIAESIAEKL8cACZAACZAACZAACZCArQlQeG1dXj4cCZAACZAACZAACZAAhZdjgARIgARIgARIgARIwNYEKLy2Li8fjgRIgARIgARIgARIgMLLMUACJEACJEACJEACJGBrAhReW5eXD0cCJEACJEACJEACJEDh5RggARIgARIgARIgARKwNQEKr63Ly4cjARIgARIgARIgARKg8HIMkAAJkAAJkAAJkAAJ2JoAhdfW5eXDkQAJkAAJkAAJkAAJUHg5BkiABEiABEiABEiABGxNgMJr6/Ly4UiABEiABEiABEiABCi8HAMkQAIkQAIkQAIkQAK2JkDhtXV5+XAkQAIkQAIkQAIkQAIUXo4BEiABEiABEiABEiABWxOg8Nq6vHw4EiABEiABEiABEiABCq/mGMjMzoOfjycSkjMhfs3LvQQahQXgeEK6ezth65JAWLAfUtOzOa4tGA81A7zh6eGB5LRsC3qr3l34eHkgtKYvTidlVm8QFj19w9oBOHkmHfkW9WekG/F1hFf1I0Dh1aw5hVcToGI6hVcRmEY4hVcDnmIqhVcRmEY4hVcDngupFF4XoDHFLQQovJpYKbyaABXTKbyKwDTCKbwa8BRTKbyKwDTCKbwa8FxIpfC6AI0pbiFA4dXESuHVBKiYTuFVBKYRTuHVgKeYSuFVBKYRTuHVgOdCKoXXBWhMcQsBCq8mVgqvJkDFdAqvIjCNcAqvBjzFVAqvIjCNcAqvBjwXUim8LkBjilsIUHjPY009l46zSSmoHRqMGoH+JWCnpKYhJzcXtUKCinxG4XXLuCyzUQqvdbwpvNaxpvBax5rCax1r0ROF11re7K1sAtVeeNPSMzB43HT8/tdRJ6X7+vfEU+MHw8vLE+LzJ6e/i2+/3yU/b9+2JeZO/xfq1A6Rv6fwWvvXi8JrHW8Kr3WsKbzWsabwWseawmsta/ZWPoFqL7xiZveD5etwR5/r0Kh+HWz98Rc8+OSbWDJ3Cq5udzne+/grfLp6M5bMfQYB/r546KnZaNGsIV6aPJLCexH+dlF4rYNO4bWONYXXOtYUXutYU3itZc3eKLxKY+Dg4WO4/f5n8OXiGbi0RWMMHDMNkTd0wpjBfWU76zfvxMTn38Yv3y2Gh4cHZ3iV6OoHU3j1GRptgcJrlJR+HIVXn6HRFii8RkmZE8clDeZwZCv6BKr9DK8D4dETp7Fi1XfYGP0Tbr2pK8aP7C8/6nTLg5j+5CgpveL67ffDuGvs89i6ej5CgmpQePXHoFILFF4lXFrBFF4tfErJFF4lXFrBFF4tfMrJFF5lZEoJaemZ8PX1hreXV7l5ubl5yMzKRmCAX6lxFX1eOOnHPQcQElwDl7VoonSvFzuYwnu+AjF/xOLdJavx094D6NHtKkybOBze3l648sYRePvlx9CjWwcZ6ZgB3rj8DTSsH4b8fMDDA8jNy5O/5uVeAt5ensjJ5Yl27qVc0LqXpyfy8vORz4HtdtzilDVxCd683EvAAx7w9PSQ/2bzcj+ByvhvtrgnO1zpGVm4ps9YzJ3xCG66rmO5j7Ttx18x+onX8P2X8xAaUhNPzngXo++7zSmtxT8vr7FxU+agfZuWeGBovyqFkcJbrFxJKefQa9DjePaxobi993VyhnfGU6PRu8c1MrL4DG9WTh58vT2RmJoN8Wte7iVQL9QPpxJ5JKh7KRe0HlrTB2kZOcjKoYS5m3egv5c8Wjg1PcfdXVX79r29gOBAH5xJ4THOVgyGuiF++Dsps1IdLSy+jtjhysvLx/4/Y9GkUT0E1wws95HE+0qxR+PR6tKmcjb4ihvux+LZT6Fzx9Yyr/jnFF47jBADz3DrkCfR/5YIuW5XrOHtc2Nn+Z2QuLiG1wBAN4ZwSYMb4RZrmksarGPNJQ3WseaSButYi564pMG9vIeMn4FnHhmCNpeF45V5H8ufTB88fBxi2cGN116FCaMGoGmjejgYexxTZi7Ex28/i7fe+wyL/rsWTRrWRWhwTfS/NQKdrmrt/FzsUCVmgLf+8AvOJKagZXgjjBvR37m0kzO87q2p21rf9csfiPkjDr0i/oHQ4Br4atN2TH11ET769xT8o/3lWLhsDT5bs0Xu0iDWvogdHLhLg9vKUWHDFN4KEZkWQOE1DWWFDVF4K0RkWgCF1zSUhhqylfBmJwH75xh6blODfEKA1o+W2qSYqXX4ithFSojuo2P+iUtbNMGb/1mBLle3wcQHBuHXA4cx6IHnsWfTIhyKO4E7R0zF5HH3ou1l4WhQrzaSU9Kcn4sZ4GX/+0a2ERYajM3bdmP2gk+xddV8uXaXwmtqda1rbF/MX3j46dnyuxjH9eS4ezHsrkj523NpGXjixXcQtX2P/P2VrVrI9TL16oTK33MfXutqJXqi8FrHm8JrHWsKr3WsKbzWsRY92Up4z8UBX4ZbC1D0ViMcuOOwIeG9ut1lzl2lPv8qCks/34CV708vIrylLWkoLMTic/ES24GDcdj/ZxxO/Z2Iue//D8vfnSYdiMJr/RAwrUfxUk5icqpcw9KgXhh8xCKvYpdY25udneM8cMLxMYXXtDIYaojCawiTKUEUXlMwGmqEwmsIkylBFF5TMBpuxFbCWwVmeAsLr1iC+ea7n2L9f19TEt7MzGz502whuzdd3xEN64XJn3b/9+1n5eFbFF7Dw99egRRea+tJ4bWON4XXOtYUXutYU3itY227GV5r0RnqrfiSBhXhXfTmZHS9uq3sp/AM75ate/CvZ//t3H5VfC76ofAaKol9gyi81taWwmsdbwqvdawpvNaxpvBax5rC637WrgrvyMdeRaeOrTH6vr5IS8uAOIvAscb3x90HMOrxWfjfopfQoG5t+W7TjLeWUHjdX87K3QOF19r6UHit403htY41hdc61hRe61hTeN3PWgjvkrlTcHW7yyFeWhMv21/YVeoHvPnuCrmkwbGlqnhpTazR3RT9M55/Y7F8f+mhYXfIpQviUC3xuaeHJyY+Px/fRP0oH0Ds8fvt97vwyTvPoV2bSzDhmbfk/8cO4T687q9wJeqBwmttMSi81vGm8FrHmsJrHWsKr3WsKbzWslbtTbyYdjYpBWG1guFx/vCbwm38fSYJYouyWiFBqk1XyngePKFZFgqvJkDFdAqvIjCNcAqvBjzFVAqvIjCNcAqvBjwXUm310poLz8+UykOAwqtZCwqvJkDFdAqvIjCNcAqvBjzFVAqvIjCNcAqvBjwXUim8LkBjilsIUHg1sVJ4NQEqplN4FYFphFN4NeApplJ4FYFphFN4NeC5kErhdQEaU9xCgMKriZXCqwlQMZ3CqwhMI5zCqwFPMZXCqwhMI5zCqwHPhVQKrwvQmOIWAhReTawUXk2AiukUXkVgGuEUXg14iqkUXkVgGuEUXg14LqRSeF2AxhS3EKDwamKl8GoCVEyn8CoC0win8GrAU0yl8CoC0win8GrAcyGVwusCNKa4hQCFVxMrhVcToGI6hVcRmEY4hVcDnmIqhVcRmEY4hVcDngupFF4XoDHFLQQovJpYKbyaABXTKbyKwDTCKbwa8BRTKbyKwDTCKbwa8FxIpfC6AI0pbiFA4dXESuHVBKiYTuFVBKYRTuHVgKeYSuFVBKYRTuHVgOdCKoXXBWgXKSUjMwtenp7w8fFWvgOd3LI62/7Tb6hftxZaNGuofD+lJVB4NTFSeDUBKqZTeBWBaYRTeDXgKaZSeBWBaYRTeDXguZBK4XUBmgUpR0+clscOz3r2QXnUsLiGjJ+B9m0uweRx9yrfgU5uWZ0NnTATfW7sjMEDeinfD4XXFGRFG6HwugFqOU1SeK3jTeG1jjWF1zrWFF7rWIueKLzW8jbaW8wfsRg4Zhp2f/Oec0b3UNwJBAT4oUHd2kabccbp5FJ4lXFfnAQKr7XcKbzW8abwWseawmsdawqvdawpvO5nfTD2OGbMWYIdu2LQMrwRxo8cgN49rpEdvzLvY/n/v2KP4/sffkHHKy/DzKfHoFnjelJ2hfS2uSxcLmOY8sgQrP9uJy5t0RgDbu2OPw8dw1MzF+DWnl2w9PNvkJ2dg4kPDIKvrw/e/WgVzialYOjA3hg7pJ/sY9b8/zpzZy/4FGI5QuGr/60RuOeOm3D85N94ee4ybP85Bh2uaIm7+t6AyBs6ydC4Y6cwfc5H8l7Dm9TH6YQkPDpmIGd43T+MjPVA4TXGyawoCq9ZJCtuh8JbMSOzIii8ZpGsuB0Kb8WMzIyw0wxvUmYS5myfYyYeQ22F+IXg0a6PlojNzMrGLYMn44rLm2P4oD7YuSsG8z/4Ap8tfEGK7ENPzcYv+//C+BH9ERpSE/MXf4F2bS7BjKdGY+XX0Zj66iK89/okeHt74fKWTTHl5YVo36YlHhjaD/ti/sI9D72Im7tfg7v63YA9vx3E/MUrZbtCcnNycjHppXew5qOX5RrbcVPmOHMPHj6GxORz8n6FiIu8j/49Be3btsQd90/BVVdcKmX5UNxJ2caGT16Xa3VvHz4FtUODMWZwX/j6eOOZV9/DqHtvo/AaGiUWBFF4LYBcqAsKr3W8KbzWsabwWseawmsda9GTnYQ3LikO4XPCrQUIIDwkHIcfPVyiXzETOnbS69i44k00rFewDEFIY0SX9pj08D1SeK9ud5kUSHGt37wT0+csQdTKf2P/n3ElljQUllaH8P7y3WJ4eHggLT0DnW55ECvefR5XtGou2+s/ciqG3RWJ/rdEFBFex42eTkjEgFHPSmm9/+4+2P7zbxg1cRY+fOtp1Aj0l2HPv/4B7uhzPVq1bIph/5rpFGjxGdfwWj7Uyu+QwmttQSi81vGm8FrHmsJrHWsKr3Ws7Sa8lW2G939royCWD0R/MddZ1GmvL0ZKahrefH5cCeH9/a+jUlK/+2wOEs4mKQlvbm4e2vccieXvTsOVrVrI/sSLarf27Ir7+vcsIbw5ubkY8eirCKsVjNkvjJPSLO732Vnvy6UVha8br+uIumEheGn2Evzw9X+cH1F4rf27WmFvFN4KEZkaQOE1FWe5jVF4rWNN4bWOtarwemYnwf/UGmQHt0N2UHu33mjgsWWoETsXie0WuL0vtz5IocbtNMNrFTOj/Xy3dRfGT3kLW1fNR0hwDaeEtrmsGZ55ZGgJ4V29Yatcl7trw0L8FXcC/xz9HH7esBB+vj4yt7wZ3ry8fLS7aYRh4X3jPyuwYcsPcnlFUM1A2f6WbXvwxIvvYNua+c6dIRzP+suBQ7j7gRek8AYGFMz+UniNjgSL4ii8FoE+3w2F1zreFF7rWFN4rWOtKry1d90N/1OrnTeYWas7cgObISe4g5TSzFoR2jfvk7IXXulxCN03Bp45SbLd09du1263MjRA4XVfFcSLY73vmYR777wJowf3xY+792PC1H/j7ZcfQ49uHaTw1qsTiicevBt/Hj6GV+Z+jMYN68jZ3/SMLFzTZyzen/2kXHubn58v19MWX8PrWNKgIrzfRP2IR5+bhyVzp+DSFk0kAB9vb2RlZ6PXoMflEohHx/xT/vkPuw8gOycHN1x7Fbr1HSfX697Xv5dcQyzknC+tuW/8KLdM4VVGppVA4dXCp5RM4VXCpRVM4dXCp5SsKrzHNl+PrfE/IzGv7G7y/Ooj168+opKS5P/zPQtmqBzXFXXao1lIOMT/m3uk4ZK8v+GdvAdCdP3ORpXacErLKUi5dKrSs1XGYAqve6vimDUVa2zF9eCw2zFh5AD5ayG8P+45INffiqtLxzZy3906tUPk7+e9vxLvfPSl/LV4ee3jlRvlS23ipbR9+w/hngdfQHnCK2ZgxS4O997ZExOeecuZe/+jr+CH3fuLPLjY+eGlySOx65c/8Mwr7yH2aLz8XMzmvjJlLHpGXI2PV27CjLeWyD+//JImSEo5h9H39ZVLJsy4ePCEJkUKryZAxXQKryIwjXAKrwY8xVQKryIwjfCKhDc5MwnrD63B1mNbsO1YNI4kx2r0VnbqDQFAqBdwZ02gb1gT1Ai9Ch7ZSfA7Gw27yK54egqvW4ZPkUbF+tqTp8/IHQ4C/H2dnzleWhs84GaINbXB55cWFE4WM71i5jUkqGBJhFWXkFmx1ZlY4yvW9zouIecpqely1wazLwqvJlEKryZAxXQKryIwjXAKrwY8xVQKryIwjfDShPfXv/diRcxSbD+yAb+c+b1I6+HegJDT5j5AvqcfzjUdjXzvghkyx+WZFY8aRxbhhkAgK/RaeGXFwyvtoPPzzWnA4WzgcA6wO9MTSXklp4u7Ne6Ofg2uwL0J76BhvQgkdF6v8ZSVJ5XCe/FqUXyXhot3J5WjZwqvZh0ovJoAFdMpvIrANMIpvBrwFFMpvIrANMJ985IRmhODE0eisfbYj3jnz2+xJy21SIt31ICUVyG6V/kBed7ByA7qgNyAZsiofzsy6hVstu+4xBpfsda3+JVdsx2yg9ufX+9bsOY3z6dAluXscUos1h1cJWeUC18d/L3wz86voHeLvmgaZP02WBp4S6RSeM2kqdaW2LZMLF8QW37xAii8mqOAwqsJUDGdwqsITCOcwqsBTzGVwqsIzEC440Wwgv8fhldanFwvuzsTeCsR+CIVznW5IZ7AnTWAoQ0uwfW1GiOrdgRyA5sj1z+8iKSW1W1wzCT4nYkqIreZtY2/zCaWUaw7tFrK7/bYNUgqNAHcNDgckS36YVCbIXINcFW7KLxVrWL2vV8Kr2ZtKbyaABXTKbyKwDTCKbwa8BRTKbyKwEoJ9zsTjZp/zoB3Riy80kuuuxUvnfU/DmxOv5B8fa1muKfVIAxqNbDSbAMWtjMSXx+JxnL/m/HVsR1IyUp23rBDfq9t0h2RLQoOE6jsF4W3sleo+twfhVez1hReTYCK6RReRWAa4RReDXiKqRReRWClhNfd2lXueuC4xHKCPJ9QOVu7O9sL/aJnIykrFcG+wRjW4X4MaftQpVwuIGaLa8bNd764JpY+fC2XPazG0ZS4IvI7u+cCdGtsfCZZn7J6CxRedWbMcA8BCq8mVwqvJkDFdAqvIjCNcAqvBjzFVAqvIrBSwsUetoHHlyE3IBynu213rpUVL6QNXBkJsWygbVg7fHnXNwgPq4vTSZn6nbqhhZqx8xC8fzLEfr/JbWYVmXkWz7L8tyVF5LfPJf3wfMSsSinvAg+F1w2DhE26RIDC6xK2C0kUXk2AiukUXkVgGuEUXg14iqkUXkVgpYSLE9HCdvaGT+o+KYsJndehsOze1XowXox4HWGBoQit6VtphVcszQj7IdL5hHneIcio3w9ZtXogM+x6ua5YXO/tmYfXd0yXSx6C/UIw5qoJmNhpij5Ik1ug8JoMlM25TIDC6zK6gkQKryZAxXQKryIwjXAKrwY8xVQKryKwMsKF9NaLagXPnGS8HXw/xv30gYwUsjun10L564r24TXnTvRaEbs++MevknvyihPYCl8Fp7t1R2aDfjgd2B7PRT+BT/cvkyFijW9lW+ZA4dUbC8w2jwCFV5MlhVcToGI6hVcRmEY4hVcDnmIqhVcRWDnC2+DbhvggGRhRcJBTEdmtKsJb+PG8MmIREL8avmei5H9C5gtfYou0b9EYk2O+w69nDsiPxJ6+L0TMqhS7OlB4zRnbbEWfAIVXkyGFVxOgYjqFVxGYRjiFVwOeYiqFVxFYGeGBx5fiw+ixePR0QYCQvtEdxheJrgozvOXREEse/MTs75kouXyj8DX7XC08H5+K5Nxs+cdiK7OJnZ+5qOt7KbzmjG22ok+AwqvJkMKrCVAxncKrCEwjnMKrAU8xlcKrCKyM8Kf/2xgfJZyVn87utQCDWg8pEVnVhbfwA4klHP6nV8M3YQvEMggx+yu2X5tzFnjhzIXIx7tMxej24+RaX6svCq/VxNlfWQQovOfJiHOdMzOzUa9OqNJoofAq4dIOpvBqIzTcAIXXMCrtQAqvNkI8vu4ufPLnV+XKrvjQTsJbnJrYlk3M/Nb8czri0pPxdH4PfBK7RYYVf7FNLJXwSd4H34QoeSCHOEEupaX5L71RePXHNlswh0C1F96/zyRh2L9mIvZowYKvluGNMGZwX/Trfa38/abon/GvZ/9dgvbPGxbCz9eHL62ZMw4Nt0LhNYxKO5DCq43QcAMUXsOoSgSK7cam/d8krIhZCnFi2vJ2vdAuYlWZDdpZeB0PHfTndAQdnIm0RoPxfcMJeC5qErYfj5Yfh/vXxLS6fhjhn1CCkXgZLrH9u86dIFyvyoVMCq8ZFNmGGQSqvfCe+jsRX6yLxu2R16FGgD+WfLYBi5evQ9TKfyPA3xcbo3/C0zMX4rOFLxTh3axxPXh4eFB4zRiFCm1QeBVgaYZSeDUBKqRTeBVgFQoVsiv22BXbj4V4emBzk3w0vnF7uaemVQfhFTO94iAOsSdxjn84fFL2IColCY+eAvZkFQDs4O+FWZd2RrcmNyI7uAPEPsZiSYTYBi31sqlIbTbOtaIUy6LwmoKRjZhAoNoLb3GGR0+cRuS9k7Bk7hRc3e5yKbwvvPEBor+YWypuLmkwYRQqNEHhVYClGUrh1QSokE7hVYB1PrSw7Ab7BGJLwzRcEdYOp6/bUW5j1UF4BYAGmxoU2dEhN6AZMmtF4INUH7y8fx2OnjspOYkdHWb3ehfh/qEI/WUM/E+tkX8udn9IvHKB8wAP9QoVZFB4XSXHPLMJUHiLEV35dTSmvrpICm7t0CApvI88Oxd3RF4HPz9fXNOhFSJv6ARvLy+ZSeE1e0iW3x6F1zreFF7rWFN41fDs/DgAACAASURBVFgXll1xetriprXQ+VwUklvPQmp40V0ZirdcXYRXLGvwyE5CVlh3ZAe3L7JMQfBbuHsuFu6ZJw+uEJdjR4fL0vcWme1NbLdAyq+rF4XXVXLMM5sAhbcQ0T8OHcV9D0/H8LsiMX5kf/nJvv2HsH7zToQE1cDx+ASsWPUd7uvfE888MlR+np2TBx9vTySdy5a/5uVeAnVC/PB3JT0S1L1Pbn3rITV8kZaZw3FtAfoAPy94enjgXEaOBb1V7S6SMpNw52e98cvpPXKf2TX9FqH5/3WSD5XQOx75PuXvRODt5YGaAT5ITD3/s/2qjUPr7gXLWdtewoLd82Q7IX6heODqCXjgiqFoum8UfM4UrPvNaDIUqR0KDu5QvcQ3zmeSM5GvmujGePF1hFf1I0DhPV/zYyf/xtAJM9DpqtaY+dQYeHl5ljoa/rc2Cs/Oeh97Ni2Ss7z5+YCHB5Cdmyd/zcu9BHy9PZHFbyzcC/l860IM8vLykcdx7XbeXp4eso9cwi6XdWJGInovvQl74vegfb32+GbodwiL+xBeuycir9HtyLl+ZYW1EqTF2M7O5cB2wIpNOowXt7yApfs+kn8U6h+KZ7tPwyMh+fD69QUgOwk5N3yLvHo9KuRbPKAy/pst7olX9SNA4QXw56FjGPHYK7jp+qvx7GPDnMsVShsO0Tv24cEn38BP6xfA38+XSxos/jvDJQ3WAeeSButYc0lDxayPpMRi1Fd3yxfUxDKGzwdsQKgnUHdbV3ilx+JMx+WGfvReXZY0VEy0ZMS2Y9F4fcd0544O4qjiT9r1wrWnFsnjjBM6r1NulksalJExwU0Eqr3wHjh4BANGPYvbenbFhFED4OlZ8J1fYIAfaoUE4eOVm9CqZVO0vbw5klJSMenF/8DH2wvvz35SxnENr5tGZhnNUnit403htY41hbd81uv+Wo3HNo2FWHvqkN3GR+ejxuG58MxJgnghK777fkMFo/BWjEmI76Mbx+BoShyaBjXF7oaJqJ2fgoRO65FZO6LiBgpFUHiVcDHYjQSqvfB+/e0OPPHiOyUQi314X5kyFm++uwKL/rvW+Xn7ti3x2rMPoknDuhReNw7Mspqm8FoHncJrHWsKb9msp0VPxnt7CtaYRrboi7f/MQLhv0+Us7py0qFWBBLbLzC8dyyF19i4Ft9c/PN/vfFbwj60C26EqLrHEVQjHPHdY4w1cD6KwquEi8FuJFDthdcI24zMLJxOSERQjUCEhtQsksIZXiMEzYuh8JrHsqKWKLwVETLvcwpvSZaFlzCIT1/q+jQm5X0vTwUTl5jVTbxyofKMI4XX+LgtLL0dAnyxuVEW0GEB0hqVPLK5rFYpvMZ5M9K9BCi8mnwpvJoAFdMpvIrANMIpvBrwFFOrq/B6Zich8MRSZNdsX0RcCy9haFKzCZa3vR7Xnv1EUs3zDkbqpVMr3H6srBJQeNUGZ2HpvcoP2HhpU2Rfv9Pw/rwUXjXejHYfAQqvJlsKryZAxXQKryIwjXAKrwY8xdTqKLw14+aj5h/T5RpccYmXotIbD8Wkv/Y4lzDc0rgbloQeRVjWERkjTv8SsptXwdZj5eGn8CoOTkCune78YSu5Z++dNYGPrp+ClEunGmqIwmsIE4MsIEDh1YRM4dUEqJhO4VUEphFO4dWAp5hanYQ38NgyBB2c7lyDm12zHbwyYhGXnoz+J4DdmQXwXr08ApPzC/aBFTGJ7ReWe2SwUeQUXqOkisaJ3TEGft4Tydnn8FwdPzz0z8OGvvGg8LrGm1nmE6DwajKl8GoCVEyn8CoC0win8GrAU0ytDsLrdyYaNf+cUWQNblLr1+RWYhv+WI5Hvx2HpOw0hHsDXzQCxI/PxZXS0vhsohHsFF4jlEqPEbs3DFwZKT/c1Qzy4I/0xkPKXV5C4XWdNzPNJUDh1eRJ4dUEqJhO4VUEphFO4dWAp5hqZ+EVs7ehex9wiq5Yg5vc5nXni0/Fd2F456oBaHRsMTyzE02b1S1cDgqv4uAsFv78phFYGLNcfkMipDfPOwQne54os1EKrx5vZptHgMKryZLCqwlQMZ3CqwhMI5zCqwFPMdWOwitEN+jPGQg8tlTSEKJ7Lnw8zoVPkD8KL74LwwsRszC6w3hFcurhFF51ZoUzxHremz/pIvfofbO+Px4LzkB8j5gyt4Wj8OrxZrZ5BCi8miwpvJoAFdMpvIrANMIpvBrwFFPtJLxi54UacRcOhRAo0hoNRnLr151rPovswhDUDO/ftkL+eNyKi8KrT9mxtCHUyxu7muYguHPZp9xRePV5swVzCFB4NTlSeDUBKqZTeBWBaYRTeDXgKabaRXiL77yQUa8vktq8VmT2r/gShjm9FiLYL0SRmOvhFF7X2RXOHPnVIKw/tAY3BACrepS9zprCaw5vtqJPgMKryZDCqwlQMZ3CqwhMI5zCqwFPMdUOwhv053QEHZwpn1ycfia2Dyt8DO3FWsJQvBQUXsXBWUa4WNrQ5YOWSM5Ow8JLLsett+4uNZLCaw5vtqJPgMKryZDCqwlQMZ3CqwhMI5zCqwFPMdVOwiv2yk1u81oRAhdzCQOFV3EwKoR/tm8OHtkyBbfX9ME79xfsp1z8ovAqAGWoWwlQeDXxUng1ASqmU3gVgWmEU3g14Cmm2kl4i28jJmR31Nq7JZHIFn1h9RIGCq/iYFQIF7O8bRY2lBknR5f+4hqFVwEoQ91KgMKriZfCqwlQMZ3CqwhMI5zCqwFPMdWuwisPK1gZKU/quqv1YCm7F/vikgZzK3DLorrYm34OS657Ajd1fJEzvObiZWsmEqDwasKk8GoCVEyn8CoC0win8GrAU0y1o/BWRtkVZaHwKg7OCsLnfh2JVw5G46HwTpjabwuF11y8bM1EAhReTZgUXk2AiukUXkVgGuEUXg14iql2E97f6g9F70+6VqqZXUdJKLyKg7OC8J9+fRW3f/cCOgTWwNqRpym85uJlayYSoPBqwqTwagJUTKfwKgLTCKfwasBTTLWT8B4Nn4g+uzdCzPC2DWuHb+7doUjDveEUXnP5igNGWr3fBkl5wPbhMWgaFF6kA67hNZc3W3OdAIXXdXYyk8KrCVAxncKrCEwjnMKrAU8x1S7C+9MvMzE8IQRx6UlSdj8fsMHSPXaNYKfwGqGkFvPQB8FYlZqDt3rMxMB2j5YQ3vQtQ+CVfgSplz5TZKs6tV7MixZfR3hVPwIUXs2aU3g1ASqmU3gVgWmEU3g14CmmVnXhFS+lTV8biWXH9sonr6yyK+6Nwqs4OA2Er117Fcb89TtuadwN7/XfVFR4/eLhsaqF/LPT125HdpA1J+qVd9sUXgNFtWEIhVezqBReTYCK6RReRWAa4RReDXiKqVVZeD/dvwzPRT2O5KxkhHgCD7WIwIRb1isSsC6cwms+67z9U9F045uy4ZgxJ4rM6jc88BA8Dn+I3IBmiO++3/zOXWiRwusCNBukUHg1i0jh1QSomE7hVQSmEU7h1YCnmFrVhFecmvbb3/uwcPc8bDsWJZ+2RwDwQX0guPNyZNTrp0jAunAKr/ms/U+txsiv7saX54AXImZhdIfxshOxvrf+ljby16UdSGL+nRhrkcJrjJPdoii8mhWl8GoCVEyn8CoC0win8GrAU0ytrMIrxPZYShy2HovGkeTDOJIc5xRcxyOGeHrgg/r5uK1BOyS2X1gpfmRdHn4Kr+LgNBAuxPbr1W0wIh64ok57bLhnu8wK3TcGgceXyV+f6Vh5vhGi8Booqg1DKLyaRaXwagJUTKfwKgLTCKfwasBTTK1swivW5L6xcwbe2zOv1CdpUrMJWnimY4BfAu4PBnybDEZy69eR5xOi+OTWh1N43cO8waYGqH0g2blbQ3MfOGd387yDcbLnSfd07EKrFF4XoNkghcKrWUQKryZAxXQKryIwjXAKrwY8xdTKIrxCdN/bOx8Ld8+Ve+iKq2ujCFzbOAJNQ5rLLaduCABq7RoEz5wkCJFJbLewUi9hKF4KCq/i4DQYHrYzEg/EROPDlIJlDU9mroHf2YLlLmmNBstxUlkuCm9lqYS190Hh1eRN4dUEqJhO4VUEphFO4dWAp5haGYT3zR9mlhDdOTcvKLKvatDBmQj6c7p8usxaEUhsvwC5/kX3XVV8dMvDKbzuQR4cMwkbf5uP/ieAK2u1xL46B50dne24HOmVaF03hdc9Y6Cyt0rh1awQhVcToGI6hVcRmEY4hVcDnmLqxRRescvCGzun40hyrLxrMaP7RJep6NY4Aj4pe+VMrvh/wNEl8v/iSmk5BSmXTlV8ysoRTuF1Tx0Cjy9F6L6xCPnLG8m5OdjVDLjKT+wDF4ITN51Avnu6dalVCq9L2Kp8EoVXs4QUXk2AiukUXkVgGuEUXg14iqkXQ3iLi+6VQfUx67JrcZN3ArwzYuGVXiDAxS+xj6rYT7WqXhRe91ROfDNUd2tXDD/li4+SsvBIKDCnLpDffDhOtnqHwuse7GxVgQCFVwFWaaEUXk2AiukUXkVgGuEUXg14iqlmCG/9qDZOSRVSmucdWupdRKUkYfqxWEQlJ8rPw72B58MgXz4rfom9U3P8w+XOC/k+IcgO7oB879BKcVqWImJnOIXXVXIV5zVaH4jdmUDHOEC8tHaoOYDuK3EiIJLCWzE+RriZAIVXEzCFVxOgYjqFVxGYRjiFVwOeYqqu8Dp+nFxet5vTgRcSAPH/wqJ7b3iE/H1W7Qjk+4QiO6gDcgKbVbm1uUaRU3iNklKPEy+u+Z2NRvNDQGwO5LKGDvfn4+SZdAqvOk5mmEyAwqsJlMKrCVAxncKrCEwjnMKrAU8xVVd4w3b2kW/EJ7ZbgLRGQwrW3mYX7LJw5Fw8nvnpP1h7dKv8fbBPIB5oNQCjOz2LmjWaKt5p1Q+n8LqvhuIbL7HWe9LB3ZibkIJxDVtg7pi/KLzuQ86WFQhQeBVglRZK4dUEqJhO4VUEphFO4dWAp5iqI7x+Z6IR9kOk3CLsVPcDzr1wxaERb+6cgRUxS+XdBPkGY0yH8Rhz1YQiR78q3mqVD6fwur+ExzZfj86//IwQn0Ccffochdf9yNmDAQIUXgOQyguh8GoCVEyn8CoC0win8GrAU0zVEV7HaVaOnRNKOzTirtaD8WLE69VadB0lofAqDk7FcPGThQbfNnQua1h590p0qcs1vIoYGe4GAhReTagUXk2AiukUXkVgGuEUXg14iqmuCq840rX+ljaytz+67cS7B1YV2UtXiO7jXaYW2UtX8dZsF07hdW9JHevJX8+5ApMO/Yph7Yfjle7cpcG91Nm6EQIUXiOUyomh8GoCVEyn8CoC0win8GrAU0x1VXjFZv814+ZjanZ7zD0RW+R0tOKHRijekm3DKbzuLW3tXYPgf2oN9rZ8GR3WPY1Q/1D8Nvq4fGmtYD/npcgJaS/Xml+si/vwXizyF7dfCq8mfwqvJkDFdAqvIjCNcAqvBjzFVFeEV/zoeN3qS/DC6XQczi7osPChEYq3UG3CKbzuK7VjOYPo4eRNJ9Dzs974LWEfPml/Kwbi1yJ7OztesHTf3ZTdMoX3YlC/+H1SeDVrQOHVBKiYTuFVBKYRTuHVgKeYqiq86/5ajRc2P4i4tLOyp7Zh7fBi99fl6Wi8yidA4XXfCHEsZ8gNCJd7Nb8b8xkmxmfgjhrAF40Asa+zOJI68PgyeRMXS3opvO4bA5W5ZQqvZnUovJoAFdMpvIrANMIpvBrwFFNVhHda9GS8t2ee7EEcGjHp6ofRv/Prij1W33AKr/tq71jO4OhB/OShxeGC3/1x70YEhl0rf1143+iLIb0UXveNgcrcMoX3fHWSUs4hMzMb9eqUfjpRSmoacnJzUSskqEg9KbzWDm8Kr3W8KbzWsTYivGL3hYErI/Hr33vljc2uC0xo2Azx3fdbd6M26InC674iijW6YTt7y0NL0psMRXr9fhgb9RRW/f4lZvdagEGtL6zbvZjSS+F13xiozC1Xe+H9+0wShv1rJmKPxss6tQxvhDGD+6Jf74LvRNPSM/Dk9Hfx7fe75O/bt22JudP/hTq1Q+TvKbzWDm8Kr3W8KbzWsa5IeLcdi8bItYPkS2lNgprh08uvRNfktUhuPQup4eOtu1Eb9EThtbaIG44sx4gvRyCyRV+8f9uKIp1fLOml8Fo7BipLb9VeeE/9nYgv1kXj9sjrUCPAH0s+24DFy9chauW/EeDvi/c+/gqfrt6MJXOfkb9/6KnZaNGsIV6aPJLCexFGMYXXOugUXutYlye8b/4wE2/smC5vRryUtvi2Fbj8/1rDMycJ8T1ibHsEsLvoU3jdRbb0djM943HJv1vIPaBjxpwoEXQxpJfCa+0YqCy9VXvhLV6IoydOI/LeSVgydwqubnc5Bo6ZhsgbOslZX3Gt37wTE59/G798txgeHh6c4bV4JFN4rQNO4bWOdVnC+9imsc6T0iZ2miL31HWcrCZeAOJyBvUaUXjVmelkNKwdgJBXQpCSlYztw2NK3RPaauml8OpUtOrmUniL1W7l19GY+uoiRH8xF7VDg9Dplgcx/clRUnrF9dvvh3HX2OexdfV8hATVoPBaPPYpvNYBp/Bax7o04XXIrjgSePFtnzp3YHDsvZvabByS27xm3U3apCcKr7WFFMJ7y5J+WH9oTYl1vIXvxErppfBaOwYqS28U3kKV+OPQUdz38HQMvysS40f2R35+Pq68cQTefvkx9OjWQUYePHwMt9//DDYufwMN64chNy8fXp4eyMjKlb/m5V4CNfy9cS4jx72dsHVJwN/XC9k5eRzXFowHH29PeADIysmTvS3b9xEeWDtK/nrriB/Rvl7Bvz/eJ1bDd89j8EiLRXrPH5EXUvDnvIwT8PQA/Hy8kJ6VazyJkS4TCPTzxjObnsPL37+Eh/8xAbN6vVlmWz6xH8H3p4JxnxM+DJn/WORyv+Uliq8jvKofAQrv+ZofO/k3hk6YgU5XtcbMp8bAy8tTfiJmeGc8NRq9e1wjf198hjcnNx/eXh5SwsSvebmXQEgNHySdO7/Lvnu7qvatiy8Kmdm5HNcWjAQ/H0+5REp84/zVn6sw+IuBste3b3kPw8NqwfvEl/A5vgoeOYnyz/O9Q5B822kL7sx+XYgJigA/L6Sm8xtnK6or/s1eu38TblveC9c16Y6v7tlYbre+Rz5CwM+jZUxWs2FI7/ie6bcp7olX9SNA4QXw56FjGPHYK7jp+qvx7GPD4O3l5RwJYg1vnxs7Y/R9t8k/4xrei/uXhEsarOPPJQ3WsXYsadgW95PcekzsxjC902N4OvV9+XKa48qu2c653ZPY+omXOgEuaVBnppMhljScPJOORvMCZTPHxqdV2Jxj2U5Gvb4407Hozg4VJhsI4JIGA5BsGFLthffAwSMYMOpZ3NazKyaMGgBPz4KZ3cAAP7nn7sJla/DZmi1ylwbxZw8++SZ3abiIfxEovNbBp/Bax1oI79HkOHT74B9Sdu9qPRhzei1Eg00N4JmTDLFe91yL8dyRwYSSUHhNgKjQhEN4e/23izxmuPh+vKU1VXdrV4g9fd11KAWFV6GANgqt9sL79bc78MSL75QoqdiH95UpY3EuLUN+HrV9j4y5slULzJ3xiPOACu7Da+3fBgqvdbwpvNax3nJ0LR76ejSSMhPl1mOfD1gvO3ecXOWuL/zWPWHl6YnCa20tHMK7fP9SPLZxLJoGh2P7sJgyb8IrIxb1t7RBnncwTvY86ZabpfC6BWulb7TaC6/RComT2LKzc5wHTjjyKLxGCZoTR+E1h6ORVii8RiiVjPHMToJPasFpaKVdmbUinH8sZnOn/d8k59Zjjn12xZ6l4qoZOw/B+ycjrdFgJLZb6NoNMasIAQqvtQPCIbziDZcuH7bG0ZQ4fNZ/vXPXkeJ3E/TndAQdnOnWMU/htXYMVJbeKLyalaDwagJUTKfwKgLTCKfwugbP8QW7ouzN6cCIeOBwNhDiCTwfBoxr2h553heON/fMSZQ/2s0NCEd897JnxSrqi59fIEDhtXY0FBZecYCKOEilW+Pu+Kz/ulJvpH5UG3ilx+JMx+XIqNfPLTdL4XUL1krfKIVXs0QUXk2AiukUXkVgGuEUXtfglSa84pCInPMvmfmdjcZjp4E5BRsuoIMv8EUjoHkFL46fvnY7soPau3ZTzHISoPBaOxgKC6/4iUbnD1uVeQiF+OZOrN9196EqFF5rx0Bl6Y3Cq1kJCq8mQMV0Cq8iMI1wCq/r8MQ6xKA/piPw+DJnIzFh/TEnNQif/PGlfDFNXI7T0xy7NKTH/wSxJKK0S8hunk/BUgderhOg8LrOzpXMwsIr8p+LmoRFe+c7X8ws3KZVh6pQeF2pZNXPofBq1pDCqwlQMZ3CqwhMI5zCqwHvfOq55F+weecjePvQNuzOvNBetwad8MINc3FFnYIZ27KOFta/A7ZQnACF19oxUVx4j6TEouuHbeRNFD9quMGmhnIbPnf/NIPCa+0YqCy9UXg1K0Hh1QSomE7hVQSmEU7hdR3er3/vxaKfX8W6w98gKStVNiTW6d5ZA3i0FtCmQQQSOhfsxEDhdZ2zK5kUXleouZ5TXHhFSyO/GlTiqGH/U6tRe9fdbl/OIPqn8Lpez6qcSeHVrB6FVxOgYjqFVxGYRjiFVw2eWIqw64eH8eof32BLSoHkiqtHAHB/cMF/jqv4rguc4VVjrRNN4dWhp55bmvC+t2cepkVPLrKsIXTfGLkEKLn1LKSGj1fvSCGDwqsAy0ahFF7NYlJ4NQEqplN4FYFphFN4jcP7ec9zeP2nOdiSVnBcrZjNHR7ii3HNOqBJ6OVyl4XcwOby4IjS1uJSeI2z1o2k8OoSVMsvTXjFT0B6f9LVuSev+GaxXlRruZwhvkeM2w9YofCq1dAu0RRezUpSeDUBKqZTeBWBaYRTeCuGt+PQp5gT/Qiikgu2XAjx8sbYNvdgZLfX4NhLt+JWuIbXCCOzYii8ZpE01k5pwisyWy9oIHdr2HnfTrSJ/wg14+ZBHJ19+rodxhrWiKLwasCrwqkUXs3iUXg1ASqmU3gVgWmE2014HduFidlWxxZhhfGImdd83wt74Do+k39ebHeETw59g3d/WYJ9yfEFouvpgQcvvw33RyxUEl1HH5zh1RioiqkUXkVgmuFlCa9jHe/7TcMwwj9B9mLViYIUXs2iVtF0Cq9m4Si8mgAV0ym8isA0wu0qvK4iScwD3koEPkguOCyiQHSBh8M74f4blqJmjaauNs1dGlwmp55I4VVnppNRmvD6nYnGh1Ej8PjR4xgeBLzbJgKpl05FZu0LpxDq9FlRLoW3IkL2/JzCq1lXCq8mQMV0Cq8iMI1wuwmv46WYlJZTkFW7RwkyPil74JF9/jSIQp8e/3sH5h/djyWn45GYW7BGt5mvH55tEo7+Xeea8kWaM7waA1UxlcKrCEwzvLDwiv2pQ/c+AL+zUXKbvo5xQLPAMGwbeUSzF7V0Cq8aL7tEU3g1K0nh1QSomE7hVQSmEW434Q3bGQlxyllCp/WGJHXbsWis2L8EK2KWOil2bRSBJ7pMRbfG5s5EUXg1BqpiKoVXEZhmuBDeU/EnEXRgEgKPFfxdyvMOxrnw8Wj87Ty5jnfDPdude1JrdmconcJrCJPtgii8miWl8GoCVEyn8CoC0wivrsL76f5lWB6zBNuORTnp3dV6MB7vMhVNg8I1iJadSuF1C9ZSG6XwWsda9NTo9LvI2/u83IFBXKnNxsnlC+LUwPJOXXPnXVJ43Um38rZN4dWsDYVXE6BiOoVXEZhGeGUTXrF1UYNvG8onMjpLW/jxHac4nbzpRIkjesVRv58eWIqFu+fhSHKsTAvyDcaYDuMxqO1Qt4mu4/4ovBoDVTGVwqsITDO8occB5G/sAc+cZLkln/i76zgiu7xT1zS7LTedwutOupW3bQqvZm0ovJoAFdMpvIrANMIrm/A6dlkQj5RRry/OdFxh+OkCjy9F6L6xMv54ZJozT3zBFTO6C3fPhZBecTUJaiZnc/u06OfSjguGb6pQIIXXFWqu5VB4XePmapZY0pAQuwO1dg2CV3pcCel9dOMY+XdQ/BRlTq+FrnajlEfhVcJlm2AKr2YpKbyaABXTKbyKwDTCK5vw1o9qA6/0gtlXcRndoL6w7DpOcRIb34vTnoqvzx1z1Xj0uaSfBjXXUim8rnFzJYvC6wo113McL615ZCchbGdv+KTukwexnOm4XMqvY5ZX7Fu9Y9h+S77JpPC6Xs+qnEnh1awehVcToGI6hVcRmEZ4ZRJeh7TmBjRDZq0IeQSpWAuY3Oa1cp/QkSfeCP89uAd21ojA1qNRJdbnjrlqgqUvzRS/aQqvxkBVTKXwKgLTDC+8S4NYluSQ3jzvECR0Xi+l17En78ROU+RPV9x9UXjdTbhytk/h1awLhVcToGI6hVcRmEa4u4TX/9Rq+CZEIyssAhn1jM2mhu3sI7cyEhvTiy+Qdbd2hfiCear7/hLrcdcfWgMxgxtz7FscP71Vbn9U/BLrcwe1HooxHce7fX2ukRJQeI1QMieGwmsOR6OtFN+HV0ivWN4gdkxxSG9UchIGroyUs7tWzPJSeI1Wz15xFF7NelJ4NQEqplN4FYFphLtLeB1LEwrP8JR3mz4pe88LbjBOdT8gBdexxZjjZCYhuGJ5gthGzLEWt3CbYl1uk6BwXNs4Ak1Dmlu6PtdICSi8RiiZE0PhNYej0VbKOmnNsS+2+HdA/KTmlu1LsP14NF6ImIXRHcYbbd6lOAqvS9iqfBKFV7OEFF5NgIrpFF5FYBrh7hBesfF8/S1tnHdlRHodXxgLL2EQSxUSfx6LL7Jr473MJnJG13E1qdEAIwPOooVXJho26IV2Eas0KFiTSuG1hrPohcJrHWvRU1nCKz5z/N0Wv14W9jCGbH8bTYPDsX1YjFtvksLrVryVtnEKr2ZpKLyaABXTKbyKwDTC3SG8CLt7gAAAIABJREFUNWPnIXj/ZKQ1GizvTKzFFdJ7+rrtyPUvucdt4a3IHC+piTe61/21Cuv+Wu18OscShcHhXdHjz/Fyz0/RR2I7a9761sAsUym8ugSN51N4jbMyI7I84RXtB8dMQs24+bKrZkfDcCQ9AbN7LcCg1kPM6L7UNii8bkNbqRum8GqWh8KrCVAxncKrCEwj3B3CW3vXIPifWiPX4mbU7ed8gaX4/pyO23ZsRbbBrxsW5baUklt4ycIdNYAhja7AtZE/QCx9EEsdqprsUng1BqkLqRReF6BppFQkvAXf+BZsG/hBMjAiHm6f5aXwahS0CqdSeDWLR+HVBKiYTuFVBKYR7g7hbbQ+UN6R4/CHwm9tl7Yp/dfru+HDhEQczr7wIG3D2mFMxwm4tWkPXLq9k9zQXmxxJL5gVkXZpfBqDFIXUim8LkDTSDEivIWlt/khIDYH+Kz/etOP8HY8BoVXo6BVOJXCq1k8Cq8mQMV0Cq8iMI1ws4VX7M5Qe9fdyK7ZDqev2+G8s8LSeyr4Wvy31gh5EESRdblBzRDZol+JXRUKrwEUDValZQyFS8MlDRoDVTGVwqsITDPcqPCKbsS/ER9uGY6J8RmICK2Pz+/4Uu7KYvZF4TWbaNVoj8KrWScKryZAxXQKryIwjXCzhdexVq+0/XM3/TYPm356Bh8mXZjKDfHyxp2BORjY8Qlc3f7FUp+k8EtwVVV2OcOrMUhdSKXwugBNI0VFeOU3rQlbcc3ym5GUl49DzYHmPkBmre5SfHOCOyAz7PpS1/ur3CKFV4WWfWIpvJq1pPBqAlRMp/AqAtMIN1t4xd65Yp1tQqf1yKwdIWdwS9tKTKzL7Vu/FQZ6HECw74WtyMp6FLFuV1xiE/uqenGG17rKUXitYy16UhVekTPx64FYfnAt3mjSEBMDTjhvWPx0KKHzhhJ7b6s+EYVXlZg94im8mnWk8GoCVEyn8CoC0wg3U3gdM7F/5dfEskbTsPy3JSWWLIhjffvWuwxX/TZcrssVl5HT1IREix0exP68VfWi8FpXOQqvdaxdFd4V+5fisY1jEdmiL1Zc2V3u7JLnHSxl14wlDhRea8dAZemNwqtZCQqvJkDFdAqvIjCNcDOF98vt47Bh/2J8kXrhhhxbid3ddmiRY30db2yLSMdWZBqPUSVSKbzWlYnCax1rV4VX7MTSZmFDeaO5bULky6iOQ2bMuHsKrxkUq14bFF7NmlF4NQEqplN4FYFphOsK77Zj0fLks3UHVyE5q2DGVlxi1mZQm6Hoc0nZxwoL6fWPX4UzHVdoPEHVSaXwWlcrCq91rF0VXpF388fX4Lczv+G7JkDnlubuqU3htXYMVJbeKLyalaDwagJUTKfwKgLTCHdFeI+kxEIcDLEiZgmOJMc6e+/gCzxSJwDX99qCGsFXGrorsXtDVV6mYOghzwdReFVo6cVSePX4qWa7soZX9DHz8ysw/8QhjK9bB0/fHafabbnxFF5TcVaZxii8mqWi8GoCVEyn8CoC0whXEV4xmzstelKRdblNA+ugv18KHgvJRONa7XDmHyu0367WeJxKnUrhta48FF7rWLs6wytOZPx512TceBS4Mqw11t/7s6k3TeE1FWeVaYzCq1kqCq8mQMV0Cq8iMI1wI8Ir1tpN+79JcrcFcYl1ubc074PhdWvj1lP/kX8mtgtLbv16tZmtdQU5hdcVaq7lUHhd4+ZqluoMr9+ZaIT9ULDzSsjhQCRnpyFmzAkE+5n3UiqF19VqVu08Cq9m/Si8mgAV0ym8isA0wisS3kV75+P1HdPlUb9CdB+6tDcere2JhvEX1t2mtJyClEunatxF9Uil8FpXZwqvdaxdmeF1vLSaWSsCNxwFth+PNv3UNQqvtWOgsvRG4dWsBIVXE6BiOoVXEZhGeFnCK/bPFVsGOU5Cuz60MT6sk4xLPFKcvWXU64u0xkORUa/sF9M0bs12qRRe60pK4bWOtSvC65jhFcJ7x+kQrD+0BrN7LcCg1kNMu3EKr2koq1RDFF7NclF4NQEqplN4FYFphJcmvNOiJ+O9PfNkq+E+3phTJwd31izoRGwKn95kKNLr9+NaXUXuFF5FYBrhFF4NeC6kqi5pEC+rNvi2YEuyx0On4M0fZmJipyl4vIt5Pymi8LpQSBukUHgLFTEnNxeeHp7w9PQwXFoKr2FUpgRSeE3BaKiRwsIrvgg9/vU/8XHcVpk7rTbwaC0gqEYzpNftJ0XXjA3hDd2YDYMovNYVlcJrHWvRk6rwipxG6wPlTb7YYBbEN9l3tR6MOb0WmnbjFF7TUFaphii858uVnpGFux94HmOH9EPfm7s5i7gp+mf869l/lyjqzxsWws/XBxRea8c7hdc63kJ4sw7/D6lxn+LWH1ZgdyYQ4gl8F14DrZrdiYz6t3PJgknloPCaBNJAMxReA5BMDHFFeOt+3wU+qfuwKvw13LFxEro2isDnA8w7OpzCa2KBq1BTFF4Ar/9nORZ/8rUs26vPPFBEeDdG/4SnZy7EZwtfKFLWZo3rwcPDg8Jr8WCn8FoDXBzXW/eHPkjMTJRbAwnZDfbyxpobnsZllz7MHRdMLgOF12Sg5TRH4bWOtaszvGE7I+F3Nhq7236Ejl8OQ9PgcGwfFmPajVN4TUNZpRqi8AJITEpFRlYW7nv4JUwcO6iE8L7wxgeI/mJuqYXlDK+1453Caw1v/1OrEbf9bim7iXnAFbVb4bN/bjZ1ayBrnqRq9ELhta5OFF7rWLsqvEF/TkfQwZkQu7wEr5spb/jY+DTTbpzCaxrKKtUQhbdQuSLvnYQJIweUEN5Hnp2LOyKvg5+fL67p0AqRN3SCt5eXzKTwWjveKbzW8D68awJu27ZIym7bsHb4fMAGyq4b0VN43Qi3WNMUXutY6wpvarNxCNo0n8Jrbcls2xuFtwLh3bf/ENZv3omQoBo4Hp+AFau+w339e+KZR4YWGRT5+bYdI5XqwTw8ALJ2b0l2H43GTR/2QGJuPoZfHonZd36CUP9Q93bK1gHxriz/HbFkJPDfEUswy05cYe1xajOw6UagXg94fL9FtpP3nHl/OcQ98ap+BCi8FQhv8SHxv7VReHbW+9izaZGc5XXM8J5JyZS/5uVeAuIFiBNn0t3bSTVuXRwi0WVxCyRlZ+D2WmFYPOIYx7UF40HO8MIDyenZFvRWvbsQM7whNXzxd3Jm9QZh0dM3qBWA+LPpSt/LiXcI6nzfVe784vvzXnmnxyeYt6RBfB3hVf0IUHgVhTd6xz48+OQb+Gn9Avj7+XJJg8V/Z7ikwX3Aheze9b8b8UvCfnTwBb65dxU8a/em8LoPubNlLmmwAPL5LrikwTrWoidXdmkQeWJrMrGkqtbBgiPL9489adqNcw2vaSirVEMUXgBi/938vHz0HfY0Hhx2O/r26gYfH29ZyI9XbkKrlk3R9vLmSEpJxaQX/wMfby+8P/tJ+TnX8Fo73im85vMWsylZhxfhtp1LsSc9Xcru153uQu0enyA1PZvCaz7yEi1SeC2ATOG1DnKhnlwV3gabGiAqJVm+OMttyS5K6WzXKYUXwMTn35brdAtfaz56GS2aNcSb767Aov+udX7Uvm1LvPbsg2jSsC6F9yL8daDwmgPd/9Qa+J9aBf/41fDMSULHuIKtx9r7eWHDVb2AdnMQWu9yCq85uCtshcJbISLTAjjDaxpKQw25Krxia7Jtx6MpvIYoM8gIAQqvAUoZmVk4nZCIoBqBCA05f47q+TzO8BoAaGIIhdc1mF4ZsfBL+L8CyT21ukgjzyWF4KVTSQj2CcRn//wWV9RpLz8v7Whh13pnVkUEKLwVETLvcwqveSyNtOSq8IbuG4OdB5dReI1AZowhAhReQ5jKDqLwagJUTKfwqgHzOxON4P2TIJYtFL6ya7aTxwH/hIa4aXXBjiOf9V+Pbo0jnGEUXjXWOtEUXh16arkUXjVeutGuCq/Yi/enX2ZSeHULwHwnAQqv5mCg8GoCVEyn8KoBE7MkgceXIc87GFm1uxccB1y3n/OktN6fdMWvf+/FqPbj8GL314o0TuFVY60TTeHVoaeWS+FV46Ub7arwBh5fit07x1J4dQvAfAqvWWOAwmsWSWPtUHiNcXJE1d3aVc7uJnRaj8zaF2Zvxedv7JiON3+YiSZBzfDNPTtKHCxB4VVjrRNN4dWhp5ZL4VXjpRvtqvCKn07ti4qUwntd2OX4vP+XyPUP170dmc9dGkzBWOUa4QyvZskovJoAFdMpvGrAxNY+4joeWXQPSzGrK2Z3xVV8KYOjBwqvGmudaAqvDj21XAqvGi/daFeFV7x3UH9LG3j8UXAH+ZdB7st7rvl4pDUaonVbFF4tfFU2mcKrWToKryZAxXQKr3FgYoYk7IdIiPW6p6/bUSSxvKUMFF7jjM2KpPCaRbLidii8FTMyM8JV4RX34JmdhIbvNnQKr1ialdB5gxRfnYvCq0Ov6uZSeDVrR+HVBKiYTuE1Dqxm7DwE75+MtEaDkdhuoTOxoqUMFF7jjM2KpPCaRbLidii8FTMyM0JHeMV9NJ5X8FOq3DbmyK5oi8JrZoWrTlsUXs1aUXg1ASqmU3iNA3O8sJbcehZSw8fLxCMpsej6YRv567KWMlB4jTM2K5LCaxbJituh8FbMyMwIHeEN/WUsamxeKm/n1PDt2jO7juei8JpZ4arTFoVXs1YUXk2AiukUXuPASnthbeDKPth2LKrUXRmKt8w1vMZZ60ZSeHUJGs+n8BpnZUakq8IrZDfw2FLnGt5j44u+h6BzbxReHXpVN5fCq1k7Cq8mQMV0Cq9xYMVfWHtvzzxMi54sz6XfOfxAiV0ZKLzG2ZodSeE1m2jZ7VF4rWMtenJFeB2yK9bsesUkyxum8FpbNzv2RuHVrCqFVxOgYjqF1xiw4i+siaUM4kW15MwkLLp1Ofpc0q/ChjjDWyEi0wIovKahrLAhCm+FiEwNUBVex7sHjhfUbv9mErYfj65wCZbKTXOGV4WWfWIpvJq1pPBqAlRMp/AaA1b8hbVRa+/Gur9WI7JFX7x/2wpDjVB4DWEyJYjCawpGQ41QeA1hMi1IVXjDdkbC72w0znRcjox6/fDP/0VSeE2rRvVuiMKrWX8KryZAxXQKrzFghYX369rDMHBlpOGlDI4eKLzGWJsRReE1g6KxNii8xjiZFaUqvA02NYRnThJO3nRCnghJ4TWrEmyHwqs5Bii8mgAV0ym8xoA5ljRk1orAP/5KkscHT+w0BY93mWqsAQAUXsOotAMpvNoIDTdA4TWMypRAFeEVp0KKl21zA5ohvvt+2T+F15QysBEAFF7NYUDh1QSomE7hNQbMIbzv5V6OMX/9Lo8P3jG84AuI0YvCa5SUfhyFV5+h0RYovEZJmROnIryBx5cidN9YZNTrizMdC5ZeUXjNqQNbofBqjwEKrzZCpQYovMZwOWZKmsf6IjYrC7N7LcCg1mrHcVJ4jbE2I4rCawZFY21QeI1xMitKRXiDYyahZtx8pLScgpRLC34aNfKrQVh/aI3hl22N3DdfWjNCyX4xnOHVrCmFVxOgYjqF1ziwDZ8GYkQ8XJrdFb1QeI2z1o2k8OoSNJ5P4TXOyoxIFeF1vLCW0Gk9MmtHyO4dJ0OqLskq794pvGZUtuq1QeHVrBmFVxOgYjqF1xgwsf1Yt0UNkZgHl2dGKLzGWJsRReE1g6KxNii8xjiZFaUivI69wx0vrFF4zaoC2xEEKLya44DCqwlQMZ3CawyYY1akRwDwybCCt51VLwqvKjHX4ym8rrNTzaTwqhLTizcqvKW9sEbh1WPP7KIEKLyaI4LCqwlQMZ3CWzEwMbvb5aPW8pCJ75oA7bpf+PFgxdkXIii8KrT0Yim8evxUsim8KrT0Y40Kb2kvrFF49fmzhQsEKLyao4HCqwlQMZ3CWzGw56ImYdHe+YgICkFUgyQUXg9XcTaFV4WRWbEUXrNIVtwOhbdiRmZGGBXe0l5Yo/CaWQm2ReHVHAMUXk2AiukU3vKBiSOEu37YRgbtv6YnWiVtcp5YpIiaL62pAtOIp/BqwFNMpfAqAtMMNyq8pb2wJrp+b888TIuejFHtx+HF7q9p3k1BOl9aMwVjlWuEwqtZMgqvJkDFdApv+cAe3TgGn+5fhrtaD8ai5uEIOjizyBY/Kri5pEGFll4shVePn0o2hVeFln6sqvA6jhR29LztWLQ8KbJrowh8PmC9/g1ReE1hWBUbofBqVo3CqwlQMZ3CWzawwrO724fHoG38Egqv4vi6WOEUXuvIU3itYy16Miq8QX9Ol/9epTYbh+Q2F2ZyxSmRvT/pimC/EMSMOWHKzXOG1xSMVa4RCq9mySi8mgAV0ym8ZQMbuLIPth2Lcv7or2bsPATvn1ziC4hR5JzhNUpKP47Cq8/QaAsUXqOkzIkzKryFj0NP6Fx0JrfLh61xNCXO5S0Wiz8Jhdec2la1Vii8mhWj8GoCVEyn8JYOzPFjvyDfYOwcfkDOhpT3BcQIdgqvEUrmxFB4zeFopBUKrxFK5sUYFV7P7CQ0+Lah7Ph4ZFqRG3C8iCuWas3ptVD75ii82girZAMUXs2yUXg1ASqmU3hLB+aY3S18GhGFV3FwXcRwCq918Cm81rEWPRkVXhFb9/su8EndV2JnGbOXNVB4rR0DlaU3Cq9mJSi8mgAV0+0uvH5no5FZq+BITaNXabO7ItexkXt2UHucvna70eaccZzhVUbmcgKF12V0yokUXmVkWgkqwlvW1mTiBsxc1kDh1SpplU2m8GqWjsKrCVAx3c7C22BTQ3jmJCG59Sykho83TKa02V1HsuOozuI/IjTSOIXXCCVzYii85nA00gqF1wgl82JUhLeswyfE3Zi5rIHCa159q1JLFF7NalF4NQEqpttZeB1yKpAktluAtEZDKqRT1uyuSCxvTVyFDQPch9cIJJNiKLwmgTTQDIXXACQTQ1SE1ysjFvW3tEGedwhO9iy6I4OZyxoovCYWuAo1ReHVLBaFVxOgYnp1EV6BRSxDEMsRyrvKm911/HhQLJEo/tazEeyc4TVCyZwYCq85HI20QuE1Qsm8GBXhFb3Wj2oNr/S4Uv/9u/m/XfBbwj7t3RoovObVtyq1ROHVrBaFVxOgYnp1EN60RoMReHyZnOUQolqW9Irjg8WP+QrvzODA6ZgpEb+P7xGDXP9wRdKc4VUGppFA4dWAp5hK4VUEphmuKry1dw2C/6k1pf6Uy3HqWmSLvnj/thUu3xmF12V0VTqRwqtZPgqvJkDF9OogvGK9reMffSG7CZ3WI88npAipxzaNxYqYpfLPZvdagEGtiy5/CNvZB35no1zeg1e0yxlexcGpEU7h1YCnmErhVQSmGa4qvI79w8U3/ontim5BlpyZhDYLC7YuE4dQiO0XXbkovK5Qq/o5FF7NGlJ4NQEqplcX4RXrb8N29pZb9BSX3opk17EdWZ53ME51P1BClo0ip/AaJaUfR+HVZ2i0BQqvUVLmxKkKb0W7y4z8ahDWH1qDFyJmYXQH4y/3Fn4aCq85ta1qrVB4NStG4dUEqJheXYRXYBHSW3dbF7meLbNWd5wO7oxbfvwMvyQelssYPh+wAVfUKbnGt35UG3ilxyrv9lC8FBRexcGpEU7h1YCnmErhVQSmGa4qvKI7xwu8J286UeIb9nV/rcaotXfLf/s23KO+3aJsPyxA86mYXhUJUHgLVS0nNxeeHp7w9PQoUcuU1DSIz2uFBBX5jMJr7bCvTsIryIrZDjHTm5yVjBuPArszgRBPYHMTyH/wcwPCkR3cQc4C5wY0g3/8Knkevfh1fPf9WsWh8GrhU0qm8Crh0gqm8GrhU052RXjDdkZC7El+puNyZNTrV6LP1gsaICUrGd9fNwZt63cytKMNZ3iVS2e7BArv+ZKmZ2Th7geex9gh/dD35m7OQqelZ+DJ6e/i2+93yT9r37Yl5k7/F+rULlg7ROG19u9EdRNeQXf/sfUYsGYwkrLT0D6gBr69pBbCso6WC16s+82srXaARfEGKbzWjW0Kr3WsKbzWsRY9uSK8QX9Ol9+4p7ScgpRLpxa5YfGTrxfX3YF3YndieBDwQQOUOJmtoifkDG9FhOz5OYUXwOv/WY7Fn3wtK/zqMw8UEd73Pv4Kn67ejCVzn0GAvy8eemo2WjRriJcmj6TwXoS/E9VNeMXekwNXRkK8rNE2rJ1cxuB4UUOs1RW7MXilHZYzwV5psXLNr6vbkFF4L8KAPt8lhdc69hRe61i7Krz+p1aj9q67S/xbFnhsGYIOTseR5Fi0OFzwHAcvrYnAnn8ovatA4bV2DFSW3ii8YpP/pFRkZGXhvodfwsSxg4oI78Ax0xB5QyeMGdxX1mz95p2Y+Pzb+OW7xfDw8OAMr8UjuToJ74r9S/HYxrGS8F2tB+PFiNddfivZlTJxhtcVaq7lUHhd4+ZKFoXXFWqu57gyw1v80ByH6Ip3E8Qlvqkfe2AHPkrKwn2N2uG1ATuUbpDCq4TLNsEU3kKljLx3EiaMHFBEeDvd8iCmPzlKSq+4fvv9MO4a+zy2rp6PkKAaFF6L/ypUF+EtLrtzehXdnscK7BReKygX9EHhtY41hdc61qInV4RX5NX9vov8iZXYj1wcuS4u8W6CWOIg9hVP2RqJq+I8kJSXj+3DY9A0yPhe4xRea8dAZemNwluO8Obn5+PKG0fg7ZcfQ49uHWTkwcPHcPv9z2Dj8jfQsH4YcnLz4e3lgZS0bPlrXu4lUCvIF2dTstzbyUVqPWTjZfBMj8PDAcPxzt4P5V3MvOF1PHj1hItyR0GBPkjPykVOTt5F6b86derv5wUPeCA9M6c6PfZFeVYvLw/U8PdG8rnsi9J/devU1X+zA3eNgt/Rgr3G8wKaIaPVc8hsOlT+PmjrzfBOiMIz6I6Zf0Th+qbdsequbwyjFffEq/oRoPCWI7ziIzHDO+Op0ejd4xoZWXyGNy8/H57nlzaIX/NyL4EAXy8pYXa80jd2x6iYrfgiteDpFvR9H0PaDbtoj+rr7Ync3Dzw+zj3l8Db0wMeHkA2YbsdtqfY/cTbE5n8Rs7trEUH/j5eyMzOhepXR+/Yj+D96wvIuWIacsIv/DvokRYL/7Utke8TglM3/YzLF14t33FYP3gTIpr1MPRM4usIr+pHgMJbgfCKNbx9buyM0ffdJiO5hvfi/iWx65IG8XLapFU9sSftHIJ9amD2ze+jzyUlt+Oxkj6XNFhHm0sarGPNJQ3WsRY9ubqkQazjFVfxUyZD942RR687dnB4Y8d0vPnDTKgcN8wlDdaOgcrSG4UXkPvr5uflo++wp/HgsNvRt1c3+Ph4yxotXLYGn63ZIndpCAzww4NPvsldGi7i6LWj8IqN1MXpaWKWooMv8FbPt3FZy/svIuWCrim81pWAwmsdawqvdax1hLe0uxS70tTf0kZ+FN8jRq7lLXzcsNG1vBRea8dAZemNwgvIXRfEzG3ha81HL0uxPZeWgSdefAdR2/fIj69s1QJzZzyCenVC5e+5D6+1Q9luwitmJsQMhbiG1KmPuSHxyO2iv4euGVWh8JpB0VgbFF5jnMyIovCaQdF4G67O8JbWg2N2N63RYCS2u/Air+pxwxRe4/WzUySF12A1k1LOITs7x3nghCONwmsQoElhdhHeIymxeD56MsTsrrjEufBPZq6WpwuZcWiEGbgpvGZQNNYGhdcYJzOiKLxmUDTehlnCW3h2V+zUIJY0OC7V44YpvMbrZ6dICq9mNSm8mgAV06u68ArRfXPnDKyIKXj7OMg3GItv+xTdGkfAcZwmhVdxUNggnMJrXREpvNaxFj2ZJbzicJ3gmElyUkBcmbW6I/XSZ5wnSjqOGzayrIHCa+0YqCy9UXg1K0Hh1QSomF5VhXfbsWis2L/EKbriscVhEo93mercP5LCqzgYbBRO4bWumBRe61ibKbyOuxansIXsnwSv9Dj5R2mNhyC51Wv415Yn8On+ZRjVfhxe7P5auQ9J4bV2DFSW3ii8mpWg8GoCVEyvasIrRPeNnTOw7ViU80mLi674QPy4rvbPd8sjgjnDqzgobBBO4bWuiBRe61i7Q3hFm2IHhxqxcxF0cKZ8GHE4xbra9+K27/+DpsHh2D4shsJrbZmrRG8UXs0yUXg1ASqmVxXhLS66YunCmA7jMeaqCaiVnwif5H1Sbn0TouCTssd5kpDAQeFVHBQ2CKfwWldECq91rN0lvI4nEBMFoXvHOpc5hMf6Ii4rCxvu2Y4r6rQv80E5w2vtGKgsvVF4NStB4dUEqJhe2YW3uOgG+wTh4fCOGN+4FeqkH4Df2QszvYUfPc87GNlBHZBVOwJpTYbK7XYu9sWX1qyrAIXXOtYUXutYu1t4HU/iWObweFwc3koEIkLrY8Xdu0vs4euIp/BaOwYqS28UXs1KUHg1ASqmV1bhFWvH3tg5HUeSY+UTOWZ0n/D9Aw1PfVrkKcV58Dn+4VJus4M7IDu4faUQ3OKloPAqDk6NcAqvBjzFVAqvIjDNcLNeWqvoNsQyh5yDr+HKb2cjKS8fb9b3x+irnyiymwOFtyKK9v6cwqtZXwqvJkDF9MomvGLT84ErIyFOShNXk6Bm8kW0Pi36yaULYpN0MXt7Lnw8smr3QHZQ+zJnHRRRuD2cwut2xM4OKLzWsabwWsda9GSV8Dqe6psD7+P+b8Yj1BPY1QxyTW/ilQucuzmIOM7wWjsGKktvFF7NSlB4NQEqplcm4RXLF0auHSRP+nGI7qDWQ5xPVPwITMVHvejhFF7rSkDhtY41hdc61hdDeEWfjoMoetT0x+aGGfKBM+r1Q1KbWfKnaRRea8dAZemNwqtZCQqvJkDF9MoivIv2zsdzUZPk3XdtFIHFt61AsF+I82lKOwLz/9u7E/Aqqrvx47/sJCFhCbsIKLYgFZBaNt+CWqtgFautwqPUrQhapS6I4otaQYG3iiwWbBVcqKjdOJ3NAAAgAElEQVQti1UBF6goQisC/2oRK9i6EBRkEcgCAbL+nzNpAoGE3DO/mcNgvvd5+hTIOWfu/cwYvhnmzrV8qcd8OMHrbhcQvO6sCV531scqeM1JiB5/7CD5hXny21PPkztllcQX53l3c9jb7teS0eNBtwhsLRICBK9yNxC8SkDL6cc6eM030tuXDqv8hLQR3Ud7lzAc/qjpIzAtX+4xHU7wuuMneN1ZE7zurF0Eb8UHUZhtJRRs8m7xaB6Ltn0iV74/TxomJMqnHVtKVuGXB1/4lWVuEdhaJAQIXuVuIHiVgJbTj2Xwmut0b39zmHe9rnlT2tQfz5T+Jw/w7gmZtOdD7zZj8Xuzvf+vuBvDtrPWR/INabGwE7yxKAUzhuANxjGWVQjeWJSCGxPmNbytFqcd9YleskXklb0itzYUmdr0kKEEb3A7+DhaieBV7iyCVwloOf1YBa/5rPYRb14vuYX5clpGc5nZ4QfSLT7/iHvoHvpyCloNlpzOMy1fYXSGE7zu9gXB686a4HVnHfYZXhO8Bxr1qXxBZUkNvDcGVzy+ST1F/vCft2RUl6u8NwtXfI1reN0eA1HZGsGr3BMErxLQcrqL4DVnbJN3r/DO1Cbs2yh3b3hbpm3d7D3TazJEpjYT7x3AFY+Ke+ia242VpJrbjZ0lxWltjtszuxWvi+C1PDgVwwleBZ7lVILXEkw5PMwzvH6fGsHrV+74nkfwKvcfwasEtJzuInjNU2qxtIXkFebJpVtElu0rf5Lmvo43te1efmuxtLbeB0UcT7cZs6QWgtdWzP94gte/ne1MgtdWTDee4NX5MTs4AYJXaUnwKgEtp7sK3o2rr5PBH8yRjUUimUnpMv/nS4/6UZWWL+O4GE7wuttNBK87a4LXnbXZEsHr1put1SxA8CqPDoJXCWg53UXwzt3wnPfmNPPokpIg8678WOqnn2j5TI//4QSvu31I8LqzJnjdWRO8bq3Z2tEFCF7lEULwKgEtp4cZvOaWY/f/7U6Zu/4571kNb95KpmVukZzOM6Sg1cEPlLB8ysftcILX3a4jeN1ZE7zurAlet9ZsjeAN9RggeEPlPWLxsIL3y/xsGfLqoMpbjj3Q9xH5ZWqOZG64y7tOd8eZ77l9oRHYGsHrbicQvO6sCV531gSvW2u2RvCGegwQvKHyOgnewz8i+OkL58rpKSJZq/tJfHGuHO+3F/O7hwhev3L28wheezO/Mwhev3L+5nENrz83ZgUvwCUNSlOCVwloOT3oM7yT10yQSavGec+i30kXeR8mkVWYXedj13gQvJYHp2I4wavAs5xK8FqCKYcTvEpApgcmQPAqKQleJaDl9KCC15zVvX/Fnd4lDOZR8RHB5h68zZZ39M7s7m92kezqNtfyGX57hhO87vYlwevOmuB1Z222RPC69WZrNQsQvMqjg+BVAlpO1waveWPapNXj5cm1070tt85o453V7X1CH+8jgrPW9PM+cKKofmfZ2WOJ9+k8dfVB8Lrb8wSvO2uC1501wevWmq0dXYDgVR4hBK8S0HK6JnjNxwOP+dtd8mVeduVZ3aGn/1oyUxoQu9XsB4LX8uBUDCd4FXiWUwleSzDlcM7wKgGZHpgAwaukJHiVgJbT/QTvvA3Py5z1s2Xl5uXe1nq16iMP9J1Y5YMkslb3l5Tdyzmze8j+IHgtD07FcIJXgWc5leC1BFMOJ3iVgEwPTIDgVVISvEpAy+mxBq+5dOHJDx+TuetnV57RzUjOlJE975Xruw6vstWGHw2TtM3PSWlipncZg7kNGQ/etObyGCB43WkTvO6szZYIXrfebK1mAYJXeXQQvEpAy+kmeLdu3VrjtbXmTWjm+tyKD48wy5vrdO/oea/0P2mAd/nCoQ9it+YdwBley4NTMZzgVeBZTiV4LcGUwwleJSDTAxMgeJWUBK8S0HK6Cd6Sl9rJrm5zqpyJPfyuC2ZZc5sxc42ueUNadY/M9XdK/U2PcWa3hn1A8FoenIrhBK8Cz3IqwWsJphxO8CoBmR6YAMGrpCR4lYCW01vtWyyy/FIpTWwgO3sslp3JbavcdcFctjC063AZ2OkqOTGjbY2rp215ThquG+Z9va5+dHBt9ARvbULBfZ3gDc6ytpUI3tqEgv06wRusJ6v5FyB4/dt5MwleJaDl9Fb/ukLkq1e8WW8Xpss1OzPlyz1fe7+vuJdubUsSu7UJlX+d4I3NKYhRBG8QirGtQfDG5hTUKII3KEnW0QoQvEpBglcJaDHd3Ce3xVstJadU5JpdWbJg905v9vcafUem9Jtd5a4LNS1bb/tCafzBIO/LnNk9Oj7Ba3FwKocSvEpAi+kErwVWAEMJ3gAQWSIQAYJXyUjwKgEtppszs8tWDpNrdyRJbnGRZCYkydjGRXJLk/LLG2q7u4L5QIms1f28T1Hb0+ZmyTt1osXW695QgtfdPid43VkTvO6szZYIXrfebK1mAYJXeXQQvEpAi+krX+skl32+0Zth7qU79bwZ0nnjOEnb8nzlNb01Re+hsVvQarDkdJ5pseW6OZTgdbffCV531gSvO2uC1601Wzu6AMGrPEIIXiVgjNM3bF4sP3/lUu9yhoHfHShTzp9VObPhuqFHjV5iN0bkw4YRvP7c/MwieP2o+ZtD8Ppz8zuLM7x+5ZgXtADBqxQleJWAMUw399a9/MWzJbdov1zTsr1M+Pm6I2bVFL3mut+sNf3ERG9R/c6y439WxbBFhhgBgtfdcUDwurMmeN1Zc4bXrTVb4wxvqMcAwRsqr5hPTOv5bEfv/6/JEJk18CXZktqv2o0eHr0l9dpWiV3zKWqlSVU/eCLcZ398r07wutt/BK87a4LXnTXB69aarRG8oR4DBG94vCZyL3upn5gzvF2TRZZ+p7VkXfalbNm5r8aNHhq9JaltK8/sErv2+4ngtTfzO4Pg9StnP4/gtTfTzOCSBo0ec4MU4JKGWjSXrnhfbrnvd0eMen/JTElJTuI+vEEejYetddlL/WXl5uXSuX4TWd78G0lsd7PU/+H0owavWaIies2vSxMzZXvfTziz62M/Ebw+0HxOIXh9wvmYRvD6QFNMIXgVeEwNVIDgrYXzzRX/kP+dMFPmzxxbZWSbE5pJXFwcwRvo4XhwsUmrxsnkNRPEfHLaxnYijcvyZMeZ70nTdj1rDd6K6DX33DVndmu7XVlIL+G4X5bgdbcLCV531gSvO2uzJYLXrTdbq1mA4I0heMdOmiUrXp5W7UguabD7zyvjswnehPz2o2ucuHLzCu9SBvNYcM79MuCrsZVvOGuVlRpT8Jq5CfuzxVzHy8OfAMHrz83PLILXj5q/OQSvPze/swhev3LMC1qA4I0heG+9b5r8tN//SEpKsvygawfpd3Z3SUxI8GYSvLEfkvWzp0vmhru8CTV9ytmhb1IzHxX8YFq2d8uxvI4Py562w8UmeGN/ZoysToDgdXdcELzurAled9ZmSwSvW2+2VrMAwVvL0bFuwxeyeNlqaZCRLlu27ZS5C96WKy89V+659SpvZmlZmcTHxUlhUan3ax7VC8QVZEvyX8+QuKIcb0BZWjs58JNPjxg88MWfy8J/vyJ9Tuwrfx34F0lZ0KTcuWFXiSvMlbg4kf0XHDkP9+AFkhPjpbi0TEpLOa6D1626YmJCnIjESXFJadibqvPrm+8hSYnx3vdsHuELpCQlyIGikvA3ZLGFesnlJ6x41C0Bgtdyf//lteVy38NPy9qlT3lneYtKysScMcgvKPJ+zaN6gfprLpfkrQuksMUAScxdK/H7Nsne05+UAyeW/+BgHo+/P03uWTZSMpMbyDtXr5GTJUcy3+lxxIL5Z/5VirL6Qh2yQEZakuw/UMxxHbKzWd78BRwfJ1JwIFph4OClO99EYnycpNdLlNyCIufbrosbbJyRLLvzCyVKfzua58Sj7gkQvJb7fMWqdXLjqEnyj8UzpF5KMpc0xOBn3jzW+INBlXdMqLdjoTRcN0zMbcO29V3vrWBuPXb+n3t5v37qJ3Ok/8kDvF8fehmE9we9npEtDQbFsFWGaAW4pEErGPt8LmmI3Uo7kksatIJ287mkwc6L0eEJELy12L7w0lLp0P5E6fTddpKbv0fufOBxSUpMkKenjPJmcg3v0QHNJ501XdlLEvZlV16Ha2Y0X95REvZtkl3d5sg/4tt6b1Iz1+8O6XKzPNB3oreoedNZ4/cHeffSNbcX291tnmR95/yY37QW3n82dWNlgtfdfiZ43VkTvO6szZYIXrfebK1mAYK3lqNj8hNz5ak/vVY5qkun9jLxvhuldcumBG8M/2Vlrr9T6m96TA406iM7eyyunJG25TnvLO+alO/Lj//zmRe7l3ccLFN/PNMbYyI3a3U/iS/O9e7QkNNlpnd7Md60FgN6QEMI3oAgY1iG4I0BKaAhBG9AkDEuQ/DGCMWw0AUI3hiI9x8olB07cyQjPU0aNqhfZQZneGsGNNHa9N3yyxTMPXQPvR+uOfP79ZJT5EfZeyWnVKrEbkUMm3n7m10kOafNrPzgCII3hgM2oCEEb0CQMSxD8MaAFNAQgjcgyBiXIXhjhGJY6AIEr5KY4K0Z0MSuiV5zz938U+6tMtCc0R0453RZl7dNuqSmy+tDdnhfz1x/l9TfNN379Z42N0veqeWXN1Q8CF7lAWsxneC1wFIOJXiVgBbTCV4LrACGErwBILJEIAIEr5KR4K0eMOPTcWI+ZKIktY3s6L2qykf7mtg11+yaN6p1SUmQd1qXSOkZcyR942OSsnu5t2BN9+kleJUHrMV0gtcCSzmU4FUCWkwneC2wAhhK8AaAyBKBCBC8SkaC90hA82azpn/v5V1/u7P7YjnQuE/loENjt1NWZ1nc7TxpnT258uvmzWlH+zhggld5wFpMJ3gtsJRDCV4loMV0gtcCK4ChBG8AiCwRiADBq2QkeI8EzFrd3ztTa66/3dVtbo2x++LPlkjDeJFmyztIfHGe9+a0XWfMPerHARO8ygPWYjrBa4GlHErwKgEtphO8FlgBDCV4A0BkiUAECF4lI8FbFfDwe+6WJjXwBhx+ZtfEbmZK+dfM5Q/mWt9D35xW024heJUHrMV0gtcCSzmU4FUCWkwneC2wAhhK8AaAyBKBCBC8SkaC9yCgufNCs+UdvUsZ8jo+LHvaDj8idjOSM8XE7veadKmcaOZVhHFtu4PgrU0ouK8TvMFZ1rYSwVubUHBfJ3iDs4xlJYI3FiXGuBAgeJXKBO9BwJruuXv70mEyd/1zUl3s2vITvLZi/scTvP7tbGcSvLZi/scTvP7t/MwkeP2oMScMAYJXqUrwlgOm7FohWWv6eb+uuOeuuQvD/SvukpWblwcSu2Ztgld5wFpMJ3gtsJRDCV4loMV0gtcCK4ChBG8AiCwRiADBq2QkeMsBD7/n7uQ1E2TSqnHe11pntJGnL5xb5TIGv+wEr185+3kEr72Z3xkEr185+3kEr72ZZgbBq9FjbpACBK9Sk+Atf9NZxT13X24/U8wlDF/mZXuyQ7rcLCN73lv5BjUlN2d4tYAW8wleCyzlUIJXCWgxneC1wApgKMEbACJLBCJA8CoZ63rwVtxzN68wV+5OvFSe+OQlT9TcY/eBvo9I7xMO3oNXSe1N5wxvEIqxrUHwxuYUxCiCNwjF2NYgeGNzCmoUwRuUJOtoBQhepWBdD15zz93Xv1wut+1Ol+z9ez3NEd1Hyx09q36UsJK5cjrBG5Rk7esQvLUbBTWC4A1KsvZ1CN7ajYIcQfAGqclaGgGCV6MnInU1eM19c4s/myw3/XOuvLynHLFXqz7yQN+JgVyrW9NuIXiVB6zFdILXAks5lOBVAlpMJ3gtsAIYSvAGgMgSgQgQvErGuhS85n659bYvkvTsafLq1x/KddtEckpFMpPS5I5eY+T6ruX33Q3zQfCGqVt1bYLXnTXB686a4HVnbbZE8Lr1Zms1CxC8yqOjLgSvOZubnj1d6m1bKB/uzZXbd4gs21cO17tFd5nS71k5MaOtUjK26QRvbE5BjCJ4g1CMbQ2CNzanIEYRvEEoxr4GwRu7FSPDFSB4lb51IXjrLWkhs3fnydQckY1F5WDmQyTMm9IGdvyFUtBuOsFr56UZTfBq9OzmErx2XprRBK9Gz34uwWtvxoxwBAhepeu3OXhXbl4h89f9Tv786auVSiZ0B3a8KtBbjdnsAoLXRks3luDV+dnMJnhttHRjCV6dn+1sgtdWjPFhCRC8StkoBq+51jZpz4dVXlnyrhVVfp+wb6MkFGyq/LP44hwxly6Ya3L/mCdVzuaaQRdltZZLe06S/icPUIrpphO8Oj+b2QSvjZZuLMGr87OZTfDaaOnHErx6Q1YIRoDgVTpGLXgP/Yhfm5dmrsk1oTsr7+Cstoki12aW/6/h92dIQSu3ly9U9/wJXpu9qhtL8Or8bGYTvDZaurEEr87PdjbBayvG+LAECF6lbNSCt972hdL4g0FSktpGiusdfCNZUUYXKUtqUPlqS9Laya74LJnzxZvy+Ia/yKa92yq/1u+ki+SKU/rL5YUrJW3L896fbztrvZQcsp6Szfd0gtc3nfVEgteazPcEgtc3nfVEgteaTDWB4FXxMTlAAYJXiRm14K34mN/89qMl/5TqP/xh8ReL5I3PF8jc9c9VvvrWGW28N6AN7HRVlTsumE9SS/9iuuSdOlEpFcx0gjcYx1hWIXhjUQpmDMEbjGMsqxC8sSgFN4bgDc6SlXQCBK/OL3IfPFFT8H6Zny3zNjwvc9fPli/zsquczR146lXH/NrcWHcDwRurlH4cwas3jHUFgjdWKf04gldvaLMCwWujxdgwBQhepW7UzvA2XDfUuwwhr+PDsqftcDFnc03kvvH5wlrP5iopnEwneJ0wexsheN1ZE7zurAled9ZmSwSvW2+2VrMAwas8OqIWvFmr+0nK7hXyWJOR8tC/5lU5m3t5x8HS/+SLj5uzudXtGoJXecBaTCd4LbCUQwleJaDFdILXAiuAoQRvAIgsEYgAwatkjFLw5h3IlRde7yXTtmR7txczD3Nt7tDTh3v3zs1MOfimNeXLPmbTCV539ASvO2uC1501wevOmjO8bq3Z2tEFCF7lERKF4DXX505ePb7Km9DObN5NLu/8K+efhKbkrHU6wVsrUWADCN7AKGtdiOCtlSiwAQRvYJQxLcQZ3piYGORAgOBVIh/L4DVndCetHi9Prp1e+Sp+mi5yWyOR715SoHxl0ZxO8LrbLwSvO2uC1501wevOmjO8bq3ZGmd4Qz0GjlXw/uubD2XIa4Mqr9E11+fe0fNe6fnuqd7r3dKP4A11x9eBxQledzuZ4HVnTfC6syZ43VqzNYI31GPgWATv5DUTZNKqcd7r6pTVWaaeN1O+16SLmHvmNn/nVClNzJSt524N9XUfq8U5w+tOnuB1Z03wurMmeN1ZE7xurdkawRvqMeAyeM1Z3dvfHCbm/81jRPfR3lndikfFxwofaNRHdvZYHOrrPlaLE7zu5Aled9YErztrgtedNcHr1pqtEbyhHgOugrems7qHvjiCN9RdXecWJ3jd7XKC1501wevOmuB1a83WCN5Qj4HDgzcp/0NJ2Jct+5sNCGS7n2S/KLcuHyXrcrd46w1v2kRG9rxPUk4YKKVJVW8zlrblOWm4bpgUtBosOZ1nBrL9qC3CGV53e4TgdWdN8LqzJnjdWRO8bq3ZGsEb6jFwePA2fbeXF7zb+244IkhjeSLmOtyUnX+T5N3vyMMbFsnYrTnetLaJIrNaiJydenAVE9X7m10s+5td5G2rpo8VjmW7x8sYgtfdniJ43VkTvO6sCV531gSvW2u2RvCGegwcGrzJ6x+QjM8meNszMbqr25xatx1flCvJu1dI8s7lkrJ7uZgzxP88IHLdNvH+3zxuOqGD3PX9X0la417e1+ttWyD1ti+qXLs0sYHsbz5AzFr1ti+U/PajJf+Ug9f21vokjqMBBK+7nUXwurMmeN1ZE7zurAlet9ZsjeAN9RioCN7cr9ZIg+U9vG2ZuyTEF+dJXseHZU/b4dVu34RrxqfjvUA1D/PJaGsPiLxUkCyz8sokt7hIWqe3kKnn/1F6n9DniDW8uN2xUNK/mCZJe9ZV+XpO5xlS0OoXob7uY7U4wetOnuB1Z03wurMmeN1ZE7xurdkawRvIMZC/p0CKS0qkUYOMKuv95u0xkhAfJ6X/flziD2yVwgY9pFOrs6T1xolSlpAuOZ3/IKXJzavMSd79rtT//BFZW7DXO4v7QXG69+tDH0O63Cwje94b08cBm8sgUrctlPTs6ZKwb5Ps7L5YDjQ+MpIDgTjGixC87nYAwevOmuB1Z03wurMmeN1aszWCV3UMFOzbL6PGPSFv/f0Db50undrLtHG3SJPG5W8Yixsbp1r/0Mm9WvXx7qd7QfuLqz2rG8uGzJljcz1vSb22sQw/7sYQvO52GcHrzprgdWdN8LqzJnjdWrM1gld1DDz5wqsyb+EymT3tHkmtlyy/unuKnNSmpTx41y+9de979QZJ+HyG92tzGUFJahvvPrl5+3dKUu5qiSsrkeLUk6U0pbkk7vlY4otzvbHmz1o16y2nNe0qvU/o64Uuj9oFCN7ajYIaQfAGJVn7OgRv7UZBjSB4g5KMbZ2WjVNl6659UhbbcCejzN8jPOqeQFxZWVmUjsPI7YHLht4v/c7uLkMHX+Q9t8XLVsuIMb+Xj95+RuLi4qTstdMlLmet7Gs3XHZ3eLjK8zfX5zb+YJCYN5WVJTX07t5gru/d3W3et/aSg7B3IMEbtvDB9Qled9YErztrgtedtdkSwevWm63VLEDw1nJ0dL/gRhk3aogXvebx8b83yuXDxsi7Cx+TBhnpIi/EiaS3lX3n/j8pjq96X1wzPnXVzyXx6/I3ppVkdpH9vV6U0rRv5+UGLv5Dy0hLkvyCIhebqvPbSE1JlMLiEikp4WfisA+G5KR4iZM4OVBUEvam6vz68fFxUi85QQr2F9d5CxcA9VOTZM++aH3PNn+P8Kh7AgTvUfa5Ofl92jnXye//73Y5q3dXb+RnGzfLxdfeI2/OmSQtm2eJLL9EpMNtIs3Prn6lwhyR108XaXa2SO9Zde8I4xUjgAACCCCAAALHWIDgjeEM7/i7r5fzz/pBtWd4i0vKJDEhTgoOFNd4JiyuKMe7pIGHXoAzvHrDWFfgDG+sUvpxnOHVG8a6Amd4Y5UKZhxneINxZBW9AMFbi6G5hrf/OT3k+isv9EYefg3v4Z+0pt8lrHA0Aa7hdXd8cA2vO2uu4XVnzTW87qzNlriG1603W6tZgOCt5eiY+fwimb/oHe8uDWmpKXLjqMlV7tJA8Lr9z4vgdedN8LqzJnjdWRO87qwJXrfWbO3oAgRvLUfI3oL9MvKBP8jy99Z6I0/rcJJMG3+rNGtSfokCwev2PzGC1503wevOmuB1Z03wurMmeN1aszWCN5BjIDd/rxQVFVd+4ETFogRvILwxL0LwxkylHkjwqgljXoDgjZlKPZDgVRNaLcAlDVZcDA5RgDO8SlyCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwXH/sckAAA2WSURBVOsDjSmhCBC8SlaCVwloOZ3gtQRTDCd4FXiWUwleSzDFcIJXgedjKsHrA40poQgQvEpWglcJaDmd4LUEUwwneBV4llMJXkswxXCCV4HnYyrB6wONKaEIELxKVoJXCWg5neC1BFMMJ3gVeJZTCV5LMMVwgleB52MqwesDjSmhCBC8obCyKAIIIIAAAggggEBUBAjeqOwJngcCCCCAAAIIIIBAKAIEbyisLIoAAggggAACCCAQFQGCV7knCguLZHfuHmnWpKHExcUpV2P64QKlpWVSVlYmCQnxR+BgH9zxUlRcIt/szJHGjTIlJTnpiIXNfti+c7c0adxAEhMSgttwHVzJHM/me8aevfukedNG1Xp/sytX0lLrSVpqSh0UcvuS8/cUSHFJiTRqkOF2w3Vwa3zProM7PUIvmeD1uTPMX1p/eHaBPPbMS94KjRtmyPQJt0nXTu19rsi0wwWM8ZhJs7w/HjvyusovYx/ssTLz+UUydeb8ykX7nd1d7h9xrTTITPf+7J2Va2XkA3+Qgn37vd/fP+IaGXjxOcE+iTqy2ocffyY3j54qu3LyvVdsonb0LYPl0gv6eL/ftHmb3DhqsmR/tc37/c9+0ld+M+IaSUrkhwzNIbJ56zdyyXX3yhWX/EhG3DDQW8ocz6PGPSFv/f0D7/ddOrWXaeNu8X6o42Ev8NBjf5Jn5y2uMrHbad+R56bf45204O9Le1NmBCtA8Pr0/OCj/8gvho+X2dNGS+eOJ8vvnvqLvLp0pbw5Z7LEx3Om1ydr5bTFy1bLuKmzvTC47KKzqgQv9lrdqvPnLVomJ7ZqJl07nSJfbtkuQ0Y8JEOuuFCuHdRf9u0vlL6X3iLDf3mpDP7Zj2XZu/+UW++bJov/NFFat2wa7BOpA6ut/fgz+c/nX8mPfvh9yaifJo8/+4o8/uwCeX/JTO9M77A7H5H66aky/u6hsnX7Thl4w1j5ze1Xy4Dzz6wDOuG8RHMGd/DN4+Sz7C0y5IqfVAbvky+8KvMWLpPZ0+6R1HrJ8qu7p8hJbVrKg3f9Mpwn8i1f9bfTX/C+f9x10xWVrzQlJUlaNG0sfM/+lu/84+TlEbw+d9Skx+fK+k+z5clH7vRW2P5Njpxz2W0yf+ZYOfU7bX2uyrQKgYJ9ByRvz16ZMmOe1EtJrhK82Id7nNz38NOy+esd8vSUUd7Z3Zv+d4p8sGSmJP/3Uoef/GKUF7+Df3ZeuE+kDqw+d+EymfbUi/LW/KneGcczB9zsnREzZ8bMY/yjs2Xr9l0ybfytdUAj+JdoLlUYPnqqtGiaJXl7CqR1yyaVwXvZ0PvF/GvG0MEXeRs2P2SPGPN7+ejtZ7g8zceuMMGbk7dHfjt62BGz+Z7tA5QpgQsQvD5JzT/xNmpQX+659arKFb539rXy+/+7Xc7q3dXnqkw7XOCBKc9KSUlJleDFPrzjxFzL2++KkXLhub3ljhsHigmyWXNel9eee6hyo7++51Fpd2JL7+s8/An848N/y4Ilf5cVqz6UO24cJBee20s+27hZLr72Hln24lRpmtXQW3j2/CXyyuK/ez9I87AXmPC75+XTL76SJx6+Q0aNn1EleLtfcKOMGzXEi17z+PjfG+XyYWPk3YWPSYOM8st5eMQuYIJ3yTtrpNf3O3nXQ5t/xTijy3e9BfieHbsjI8MTIHh92pp/euzQvk2Vv/TNN9AxI6/1/vLiEYxAdcGLfTC21a1y/yPPyGtLV8mrs3/rvRHT/LPvG2+vrhJc5i+v+mmp3rHOw5/Aor+ulFeXvicfbfhcbrz6Yu9secU/+x4aXOYHDnPZw1vzpvjbUB2e9aeXl8qsOW/I3CfGeNejm7O3FWd4zTWlp51zXZUTFBU/cLw5Z5K0bJ5Vh+X8vfSFS96VjV9t9S7N+eiTL2Tpivdl8pibpN/ZPbxLdfj70p8rs4ITIHh9Wpq/9M0b1Ubf8ovKFTjD6xPzKNNqOsOLffDWv5/1sjw262X58+P3S+eOJ3kb4Axv8M6HrmjO9F59ywR544WHxbyD3Zzhfecvj1a+cYozvP79+11xp7Rt3VxOaXeCt8jSv73vXTddcRmDOUEx/u7r5fyzfuB9nTO8/q2rm3n3hBmSk5svjz90h3eGl+/Zwfqymr0AwWtv5s0w1yR98tkmmTFxpPd7ruH1CVnLtOqCF/tgrc0txyY9PseL2z8+erd0+m67yg1UXMP7z78+KUlJid6fm5C4+vLzuYY3gN1gbj921s9u9a7bPbltqyOu4X1wyrOy/ZvdXMPrw3rOK29Jbv7eypkvv/E3adwwUwac11sG/fRHYq7h7X9OD7n+ygu9MVzD6wP5KFPMnV/MD3Tmjd18zw7WltX8CRC8/twOedfpPdL51JPl0Sfny2tL3+MuDT49D59WUlIqpaWlMu7R2VJcXCJj7rhWEhISvDtgHHzHL/ZBcN/70FPy0usrvDMxJ7dtWbmkuUdsYWGxdL/gBhl18xVyJXdpUHMbZ3N96BldO0h8XJxMmTlfzD8FvzVvsnf28fqREyWzfrp35pG7NKi5qyxw6CUN5gvmdnzzF73j3aXB3O/Y3A6OuzT4NzdvML74/DOlTesW3smg6257yPth4oarBvA92z8rMwMUIHh9YpprwKY/85J3SyHzMPfTnDHxjsp3V/tclmn/FZi74G0ZO/mPVTzM7YLMfUmxD/YwMWdsv/p6xxGLmjeqmX8SNvcpNW9Uq3jce9tVcsUl5wb7JOrIauYs+tj/3lvavGTzQ8WEu4dKrzM6eQJfbPraC6+K/XFJ/x96P+xVnF2vI0yhvMzDg3dvwX7vn9qXv7fW295pHU7yzqSba9d52AsMumGsd+1uxcMcu/fdfrV3lx2+Z9t7MiN4AYJXabr/QKHs2p0nLZplcf9dpaXtdOxtxfyPN2fct+7YJc2yGhJf/hm9meZWWTt35UmZlEmzrEbVft/YtmO3dz/e9LR6yq0xvTYBc9lDUVExHzhRG1QMXzf3PN6dmy9Nsxp59zY+/MH37BgQGRKaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJkDwhkbLwggggAACCCCAAAJRECB4o7AXeA4IIIAAAggggAACoQkQvKHRsjACCCCAAAIIIIBAFAQI3ijsBZ4DAggggAACCCCAQGgCBG9otCyMAAIIIIAAAgggEAUBgjcKe4HngAACCCCAAAIIIBCaAMEbGi0LI4AAAggggAACCERBgOCNwl7gOSCAAAIIIIAAAgiEJvD/AdUe4YrDzGN9AAAAAElFTkSuQmCC" }, "metadata": {}, "output_type": "display_data" @@ -4608,7 +4579,13 @@ "source": [ "#| caption: Initial and optimized trajectories for a more realistic example.\n", "#| label: fig:initial_and_optimized_trajectories\n", - "fig.show()\n" + "initial_poses = gtsam.utilities.extractPose2(initial)\n", + "fig = go.Figure()\n", + "fig.add_scatter(x=initial_poses[:,0], y=initial_poses[:,1], name=\"initial\", marker=dict(color='orange'))\n", + "final_poses = gtsam.utilities.extractPose2(result)\n", + "fig.add_scatter(x=final_poses[:,0], y=final_poses[:,1], name=\"optimized\", marker=dict(color='green'))\n", + "fig.update_yaxes(scaleanchor = \"x\",scaleratio = 1);\n", + "fig.show()" ] } ], @@ -4620,7 +4597,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3.8.12 ('gtbook')", + "display_name": "gtbook", "language": "python", "name": "python3" }, @@ -4634,17 +4611,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.9.19" }, "latex_metadata": { "affiliation": "Georgia Institute of Technology", "author": "Frank Dellaert and Seth Hutchinson", "title": "Introduction to Robotics" - }, - "vscode": { - "interpreter": { - "hash": "9f7376ced4243bb13dfcffa8a3ba834e0602aa8334cd3a1d8ba8d285f4628083" - } } }, "nbformat": 4, diff --git a/S65_driving_planning.ipynb b/S65_driving_planning.ipynb index e74194d1..b0c78248 100644 --- a/S65_driving_planning.ipynb +++ b/S65_driving_planning.ipynb @@ -5,7 +5,7 @@ "id": "UcRF7OziizF1", "metadata": {}, "source": [ - "# Planning for Autonomous Driving." + "# Planning for Autonomous Driving" ] }, { @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "tLBxSLGeWPV0", "metadata": { "tags": [ @@ -32,12 +32,12 @@ }, "outputs": [], "source": [ - "%pip install -q -U gtbook\n" + "%pip install -q -U gtbook" ] }, { "cell_type": "code", - "execution_count": 87, + "execution_count": null, "id": "ewrl5k4_akQV", "metadata": { "tags": [ @@ -46,7 +46,7 @@ }, "outputs": [], "source": [ - "# no imports (yet)\n" + "# no imports (yet)" ] }, { @@ -114,15 +114,13 @@ "id": "R0sQPSK681cf", "metadata": {}, "source": [ - "```{index} motion primitives\n", - "```\n", "## Motion Primitives\n", "\n", "Consider a car traveling in reverse that wishes to suddenly change its orientation\n", "by completing a rapid 180-degree turn (a favorite maneuver for drivers like James Bond and Steve McQueen).\n", "How would we go about implementing this type of maneuver in an autonomous vehicle?\n", "\n", - "This two approaches we have considered before can be very inefficient for planning trajectories that have such well-defined\n", + "The two approaches we have considered before can be very inefficient for planning trajectories that have such well-defined\n", "characteristics.\n", "For all of our probabilistic methods, we used a discrete time formulation and considered\n", "the effects of executing an action (e.g., move forward, move left) for a small duration of time, $\\Delta t$.\n", @@ -132,6 +130,8 @@ "In each case, the language of path segments is very simple, and in each case,\n", "a full plan will consist of many sequential steps.\n", "\n", + "```{index} motion primitives\n", + "```\n", "Instead, the U-turn maneuver could be achieved by a predefined\n", "sequence of steps: after achieving a reasonable speed, remove your foot from the gas pedal;\n", "turn left sharply and hit the breaks; at the perfect moment, release the breaks\n", @@ -146,8 +146,7 @@ "id": "53y_6iTD1Ptz", "metadata": {}, "source": [ - "This idea is illustrated in the figure below, which shows four motion primitives\n", - "for a car.\n", + "This idea is illustrated in the Figure [1](#fig:MotionPrimitives), which shows four motion primitives for a car.\n", "The primitive $P_1$ corresponds to driving forward, while motion primitives $P_2$, $P_3$, and $P_4$ correspond to veering\n", "to the left at increasingly sharp angles." ] @@ -157,14 +156,13 @@ "id": "y_GGvtQc94pI", "metadata": {}, "source": [ - "```{index} polynomial trajectories, splines\n", - "```\n", - "\n", "
\n", "\"\"\n", "
Four motion primitives for a car veering to its left.
\n", "
\n", "\n", + "```{index} polynomial trajectories, splines\n", + "```\n", "Motion primitives can be defined in numerous ways.\n", "The figure above illustrates four fixed motion primitives, but it would not be difficult to generalize each of these\n", "to a class of motions by using parametric descriptions. \n", @@ -208,7 +206,7 @@ "For example, the traffic in rural Georgia is irrelevant when leaving downtown Atlanta on\n", "a trip to Boston.\n", "In this case, immediate driving decisions depend on the car just ahead, and the nearby\n", - "cars in adjacent lanes.\n" + "cars in adjacent lanes." ] }, { @@ -218,7 +216,7 @@ "source": [ "## Polynomial Trajectories\n", "\n", - "Let’s begin with the simple problem of changing lanes along a straight stretch of highway. The situation is illustrated in the figure below.\n", + "Let’s begin with the simple problem of changing lanes along a straight stretch of highway. The situation is illustrated in Figure [2](#fig:LaneChange).\n", "\n", "
\n", "\"\"\n", @@ -239,7 +237,7 @@ "\\end{equation}\n", "At the start of the maneuver, $s=0$, which matches the initial condition $d(0)=0$, and at $ s = s_\\mathrm{g}$\n", "we match the end condition $d(s_\\mathrm{g}) = d_\\mathrm{g}$.\n", - "This trajectory is illustrated in the figure below.\n", + "This trajectory is illustrated in Figure [3](#fig:LinearLaneChange).\n", "\n", "
\n", "\"\"\n", @@ -305,7 +303,7 @@ "\\end{aligned}\n", "\\end{equation}\n", "Note that these six equations are all linear in the parameters $\\alpha_i$, so it is a simple matter to solve\n", - "these." + "them." ] }, { @@ -320,13 +318,13 @@ "While the derivation above produced a single polynomial trajectory,\n", "it is a simple matter to extend this formalism to construct trajectories\n", "that are composed of multiple consecutive polynomial segments.\n", - "Such trajectores belong to the more general class of **splines**.\n", + "Such trajectories belong to the more general class of **splines**.\n", "In general, a spline is a continuous, piecewise polynomial curve, and we are not\n", "necessarily given the specific values for the transition points between adjacent\n", "segments.\n", "\n", - "In fact, we have actually done exactly this in the above derviation,\n", - "if we consider that for $s < 0$ and for $s > s_\\mathrm{g}$ the trajectory $d(s)$ is linear and pararallel to the $s$-axis,\n", + "In fact, we have actually done exactly this in the above derivation,\n", + "if we consider that for $s < 0$ and for $s > s_\\mathrm{g}$ the trajectory $d(s)$ is linear and parallel to the $s$-axis,\n", "i.e., we have solved for a special case of three polynomial segments with two of those\n", "segments being linear and one quintic.\n", "\n", @@ -408,11 +406,11 @@ "trajectory, becomes an important problem. In this section, we address the problem\n", "of following such a trajectory.\n", "\n", - "The figure below illustrates the situation.\n", - "We denote by $\\gamma(s)$ the desired trjactory of the car, where $s$, an arc length\n", + "Figure [4](#fig:FrenetFrame) illustrates the situation.\n", + "We denote by $\\gamma(s)$ the desired trajectory of the car, where $s$, an arc length\n", "parameter, is a function of time, and therefore the instantaneous desired speed of \n", "the car is $\\dot{s}(t)$.\n", - "Since the goal is to keep the car on the deisred trajectory, it is convenient\n", + "Since the goal is to keep the car on the desired trajectory, it is convenient\n", "to represent the state of the car in a coordinate frame that is local to the trajectory.\n", "To do so, for each point along $\\gamma$, we define a frame with origin $\\gamma(s)$,\n", "with axes $t_\\gamma(s)$ and $n_\\gamma(s)$, the tangent and normal vectors\n", @@ -480,15 +478,15 @@ "want the maneuver to take too long), and the comfort of the human passenger.\n", "As mentioned above, humans are sensitive to acceleration changes in the lateral direction,\n", "therefore, we might wish to minimize the overall effect of such changes.\n", - "Mathematically, the instantaneous change in lateral cceleration is given by the third\n", + "Mathematically, the instantaneous change in lateral acceleration is given by the third\n", "derivative of $d$, which is known as the **jerk**.\n", "For a given trajectory $d(t)$, defined on the interval $[0,T]$, the following\n", - "cost functional penalizes aggregate jerk and total exectution time\n", + "cost functional penalizes aggregate jerk and total execution time\n", "\\begin{equation}\n", "J(d) = \\int_0^T \\left(\\frac{d}{dt}\\ddot{d}(\\tau)\\right)^2 d\\tau + \\beta T\n", "\\end{equation}\n", "In general, it may not be possible to solve this optimization problem in real time.\n", - "In such cases, rather than using $J$ to solve find the optimal $d$, we\n", + "In such cases, rather than using $J$ to find the optimal $d$, we\n", "can use a generate-and-test approach.\n", "With such an approach, several values of $T$ are proposed, and the corresponding quintic trajectories\n", "are computed for each of these. It is then a simple matter to evaluate the cost of each of\n", diff --git a/S66_driving_DRL.ipynb b/S66_driving_DRL.ipynb index 9cf583a9..65de8df3 100644 --- a/S66_driving_DRL.ipynb +++ b/S66_driving_DRL.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "pVeijfbAiYRG", "metadata": { "tags": [ @@ -40,7 +40,7 @@ } ], "source": [ - "%pip install -q -U gtbook\n" + "%pip install -q -U gtbook" ] }, { @@ -94,7 +94,6 @@ "source": [ "```{index} pair: deep reinforcement learning; DRL\n", "```\n", - "\n", "Deep reinforcement learning (DRL) applies the power of deep learning to bring reinforcement learning to much more complex domains than what we were able to tackle with the Markov Decision Processes and RL concepts introduced in Chapter 3. The use of large, expressive neural networks has allowed researchers and practitioners alike to work with high bandwidth sensors such as video streams and LIDAR, and bring the promise of RL into real-world domains such as autonomous driving. This is still a field of active discovery and research, however, and we can give but a brief introduction here about what is a vast literature and problem space." ] }, @@ -139,11 +138,10 @@ "id": "0xi2Y6T5YAtY", "metadata": {}, "source": [ - "```{index} deep reinforcement learning; DQN\n", - "```\n", - "\n", "## Deep Q-Learning\n", "\n", + "```{index} deep reinforcement learning; DQN\n", + "```\n", "> DQN is an early deep learning RL method akin to Q-learning.\n", "\n", "Recall from Section 3.6 that we can define a policy in terms of **Q-values**, sometimes also called state-action values, and that we can define the optimal policy as \n", @@ -166,7 +164,7 @@ "id": "pCKixeLwsh2Z", "metadata": {}, "source": [ - "```{index} execution phase, experience replay\n", + "```{index} pair: deep Q-network; DQN\n", "```\n", "In the **deep Q-network** or DQN method we use a *supervised learning* approach to Q-learning. We train a neural network, parameterized by $\\theta$, to approximate the optimal Q-values:\n", "\\begin{equation}\n", @@ -174,6 +172,8 @@ "\\end{equation}\n", "It might be worthwhile at this point to re-visit Section 5.6, where we introduced neural networks and how to train them using stochastic gradient descent (SGD).\n", "\n", + "```{index} execution phase, experience replay\n", + "```\n", "In the context of RL, the DQN method uses two additional ideas that are crucial in making the training converge to something sensible in difficult problems. The first is splitting the training into *execution* and *experience replay* phases:\n", "\n", "- during the **execution phase**, the policy is executed (possibly with some degree of randomness) and the experiences $(x,a,r,x')$, with $r$ the reward, are stored in a dataset $D$;\n", @@ -189,6 +189,8 @@ "\\mathcal{L}_{\\text{DQN}}(\\theta; D) \\doteq \\sum_{(x,a,r,x')\\in D} [\\text{target}(x,a,x') - Q(x,a; \\theta)]^2\n", "\\end{equation}\n", "\n", + "```{index} off-policy RL\n", + "```\n", "With this basic scheme, a team from DeepMind was able to achieve human or super-human performance on about 50 Atari 2600 games in 2015 {cite:p}`Mnih15nature_dqn`.\n", "DQN is a so-called **off-policy** method, in that each execution phase uses the best policy we computed so far, but we can still replay earlier experiences gathered with \"lesser\" policies. Nothing in the experience replay phase references the policy: every experience leads to a valid Q-value backup and a valid supervised learning signal." ] @@ -198,19 +200,22 @@ "id": "D6PHabNMU4OO", "metadata": {}, "source": [ - "```{index} stochastic policy, deep reinforcement learning; policy optimization\n", - "```\n", - "\n", "## Policy Optimization\n", "\n", + "```{index} deep reinforcement learning; policy optimization\n", + "```\n", "> Policy optimization takes a black box optimization approach to a deep policy.\n", "\n", + "```{index} stochastic policy\n", + "```\n", "Whereas the above gets at an optimal policy indirectly, via deep Q-learning, a different and very popular idea is to directly parameterize the policy using a neural network, with weights $\\theta$. It is common to make this a **stochastic policy**,\n", "\\begin{equation}\n", "\\pi(a|x; \\theta)\n", "\\end{equation}\n", "where $a \\in {\\cal A}$ is an action, $x \\in {\\cal X}$ is a state, and the policy outputs a *probability* for each action $a$ based on the state $x$. One of the reasons to prefer stochastic policies is that they are differentiable, as they output continuous values rather than discrete actions. This allows us to optimize for them via gradient descent, as we explore in the next section.\n", "\n", + "```{index} cross-entropy\n", + "```\n", "In Chapter 5 we used *supervised* learning to train neural networks, and we just applied this for learning Q-values in DQN. It is useful to consider how this might work for training a *policy*. Recall from Section 5.6 that we defined the empirical cross-entropy loss as\n", "\\begin{equation}\n", "\\mathcal{L}_{\\text{CE}}(\\theta; D) \\doteq - \\sum_{(x,y=c)\\in D} \\sum_c \\log p_c(x;\\theta)\n", @@ -231,7 +236,9 @@ "id": "So1rSw4zS-C5", "metadata": {}, "source": [ - "In **policy optimization** we gather data by rolling out a set of trajectories $\\tau_i$. In supervised learning we have a dataset $D$ and labels $y_c$, but we have to proceed a bit differently in a reinforcement learning setting. In particular, for *on-policy* RL we gather data by executing our current best guess for the policy for some rollout length or horizon $H$, and we do this many different times, each time obtaining a *trajectory* $\\tau_i$.\n", + "```{index} policy optimization, off-policy RL\n", + "```\n", + "In **policy optimization** we gather data by rolling out a set of trajectories $\\tau_i$. In supervised learning we have a dataset $D$ and labels $y_c$, but we have to proceed a bit differently in a reinforcement learning setting. In particular, for **on-policy** RL we gather data by executing our current best guess for the policy for some rollout length or horizon $H$, and we do this many different times, each time obtaining a *trajectory* $\\tau_i$.\n", "That still leaves the training signal: where does that come from? \n", "The key idea is to estimate how good a particular action is by estimating the state-action values $Q$ from the rollout rewards.\n", "In detail, we estimate the expected discounted reward starting at $x_{it}$, and taking action $a_{it}$, as\n", @@ -257,9 +264,9 @@ "- Initialize $\\theta$\n", "- Until convergence:\n", " 1. roll out a number of trajectories $\\tau_i$ using the current policy $\\pi(a;x,\\theta)$\n", - " 2. try and change the parameters $\\theta$ as to decrease the surrogate loss function $\\mathcal{L}(\\theta)$\n", + " 2. try to change the parameters $\\theta$ as to decrease the surrogate loss function $\\mathcal{L}(\\theta)$\n", " \n", - "A simple, gradient-free approach for step 2 is simple hill-climbing aka stochastic search:\n", + "A simple, gradient-free approach for step 2 is simple hill-climbing, aka stochastic search:\n", "\n", " - perturb $\\theta$ to $\\theta'$\n", " - set $\\theta \\leftarrow \\theta'$ *iff* $\\mathcal{L}(\\theta') < \\mathcal{L}(\\theta)$\n", @@ -273,18 +280,23 @@ "id": "-sLUpvmQ2sNd", "metadata": {}, "source": [ - "```{index} deep reinforcement learning; policy gradient methods\n", - "```\n", - "\n", "## Policy Gradient Methods\n", "\n", + "```{index} deep reinforcement learning; policy gradient methods\n", + "```\n", "> Policy gradient methods are akin to policy iteration, with a neural flavor.\n", "\n", + "```{index} softmax function, logit\n", + "```\n", "In a nutshell, policy gradient methods calculate the *gradient* of the surrogate loss $\\mathcal{L}(\\theta)$ defined above with respect to the policy parameters $\\theta$:\n", "\\begin{equation}\n", "\\nabla_\\theta \\mathcal{L}(\\theta) \\leftarrow - \\sum_i \\sum_{t=1}^H \\hat{Q}(x_{it},a_{it}) \\nabla_\\theta \\log \\pi(a_{it}|x_{it}, \\theta),\n", "\\end{equation}\n", - "where $\\nabla_\\theta \\log \\pi(a_{it}|x_{it}, \\theta)$ is the gradient of the logarithm of the stochastic policy. This is easily obtained via back-propagation using any neural network framework of choice. In the case that actions are discrete, as in our example above, a stochastic policy network typically has a \"softmax\" function at the end. Then $\\nabla_\\theta \\log \\pi(a_{it}|x_{it}, \\theta)$ is the derivative of the \"logit\" layer right before the softmax function.\n", + "where $\\nabla_\\theta \\log \\pi(a_{it}|x_{it}, \\theta)$ is the gradient of the logarithm of the stochastic policy. This is easily obtained via back-propagation using any neural network framework of choice. In the case that actions are discrete, as in our example above, a stochastic policy network typically ends with a *softmax* function. Recall that if the network outputs a vector of raw scores (the *logits*) $z \\in \\mathbb{R}^K$, the softmax is defined as\n", + "\\begin{equation}\n", + "\\mathrm{softmax}(z)_i = \\frac{e^{z_i}}{\\sum_{j=1}^K e^{z_j}}, \\quad i = 1,\\dots,K.\n", + "\\end{equation}\n", + "Thus, the logits are the raw outputs before applying the softmax, and $\\nabla_\\theta \\log \\pi(a_{it}|x_{it}, \\theta)$ is computed with respect to *these* values.\n", "We then use gradient descent to update the policy parameters:\n", "\\begin{equation}\n", "\\theta \\leftarrow \\theta - \\alpha \\nabla_\\theta \\mathcal{L}(\\theta)\n", @@ -294,12 +306,6 @@ "The algorithm above, using the estimated Q-values, is almost identical to the REINFORCE method {cite:p}`Williams92ml_reinforce`. That algorithm further improves on performance by not using the raw Q-values but rather the difference between the Q-values and some baseline policy. This has the effect of reducing the variance in the estimated Q-values due to using only a finite amount of data.\n", "The REINFORCE algorithm was introduced in 1992 and hence pre-dates the deep-learning revolution by about 20 years. It should also be said that in DRL, the neural networks that are used are typically not very deep. Several modern methods, such as \"proximal policy optimization\" (PPO) {cite:p}`Schulman17_PPO` apply a number of techniques to improve this basic method even further and make it more sample-efficient. PPO is now one of the most often-used DRL methods." ] - }, - { - "cell_type": "markdown", - "id": "xtNoiDaqfViL", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/S67_driving_summary.ipynb b/S67_driving_summary.ipynb index 3adf11bd..09ae3d52 100644 --- a/S67_driving_summary.ipynb +++ b/S67_driving_summary.ipynb @@ -106,7 +106,7 @@ "P^1 \\\\ 1\n", "\\end{bmatrix}\n", "\\end{equation}\n", - "Finally, composition of homogeneous transformations requires nothing more than simple\n", + "Composition of homogeneous transformations requires nothing more than simple\n", "matrix multiplication.\n", "Given the transformations $T^0_1$ and $T^1_2$ (which denote the relative position and orientation\n", "of frame 1 with respect to frame 0, and of frame 2 with respect to frame 1, respectively),\n", @@ -185,7 +185,7 @@ "Neither of these apply for the case of self-drivings cars,\n", "whose motions are subjected to nonholonomic constraints (when the wheels do not skid),\n", "and whose speed and acceleration are important factors for both safety and passenger comfort.\n", - "Unfortunately, the introductions of nonholonomic constraints and the consideration\n", + "Unfortunately, the introduction of nonholonomic constraints and the consideration\n", "of vehicle dynamics adds significant complexity to the motion planning problem.\n", "\n", "To deal with these complexities, we introduced the idea of *motion primitives*,\n", @@ -213,9 +213,7 @@ "the desired trajectory.\n", "We showed how this approach could be incorporated into an optimization framework\n", "that minimizes total jerk throughout the motion, since it has been shown\n", - "that human passengers are sensitive to jerk in the lateral direction.\n", - "\n", - "\n" + "that human passengers are sensitive to jerk in the lateral direction." ] }, { @@ -224,9 +222,9 @@ "metadata": {}, "source": [ "In Chapter 3, we introduced reinforcement learning, including policy optimization (using value iteration and policy iteration) and Q-learning. Then, in Chapter 5, we introduced deep learning to solve computer vision problems.\n", - "In this chapter, we combine the two, and introduce *Deep Reinforcement Learning*, or *Deep RL*.\n", + "In this chapter, we combined the two, and introduce *deep Reinforcement Learning*, or *Deep RL*.\n", "\n", - "We first descriped how Q-learning can be implemented using a deep neural network\n", + "We first described how Q-learning can be implemented using a deep neural network\n", "to encode the Q-function.\n", "With this approach, the system learns the Q-values associated to a given problem,\n", "and these are encoded in a *deep Q-network* (*DQN*).\n", @@ -235,13 +233,12 @@ "in which a deep network encodes a stochastic\n", "policy $\\pi(a | x, \\theta)$ (i.e., $\\pi$ is a probability distribution\n", "over actions, given the current state and parameter $\\theta$).\n", - "The parameter $\\theta$ can be estimated using a simple hill climing algorithm,\n", - "where the loss function to be optimized is computed using fininte horizon rollouts.\n", + "The parameter $\\theta$ can be estimated using a simple hill climbing algorithm,\n", + "where the loss function to be optimized is computed using finite horizon rollouts.\n", "\n", "Finally, we gave a brief introduction to policy gradient methods, in which simple\n", "hill climbing is replaced by a gradient descent on the loss function,\n", - "using the same finite-horizon rollout loss function that was used for policy optimization.\n", - "\n" + "using the same finite-horizon rollout loss function that was used for policy optimization." ] }, { @@ -266,10 +263,11 @@ "\n", "SLAM has been a topic of robotics research since the 1990's, and the number\n", "of important papers in the area are too numerous to mention here,\n", - "so we focus on the two main methods that were specifically addressed in this\n", - "chapter.\n", - "The seminal reference for the ICP algorithm is due to Besl and Jain {cite:p}`Besl92pami_ICP`.\n", + "so we focus on the two main methods that were specifically addressed in this chapter.\n", + "The seminal reference for the ICP algorithm is due to {cite:t}`Besl92pami_ICP`.\n", "Pose SLAM was introduced by {cite:t}`Ila10tro_PoseSLAM`.\n", + "Another seminal paper on SLAM is by {cite:t}`Guivant01tra_SLAM`, who also collected the\n", + "Victoria Park dataset of which we used an excerpt here.\n", "\n", "Splines have been used in robotics for many years, mainly to plan\n", "trajectories for robot arms specified in terms of via points\n", @@ -286,12 +284,6 @@ "policy gradient algorithms were introduced by {cite:t}`Williams92ml_reinforce`\n", "and PPO is described in {cite:p}`Schulman17_PPO`." ] - }, - { - "cell_type": "markdown", - "id": "a2R1mtOquCVI", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/S75_drone_planning.ipynb b/S75_drone_planning.ipynb index 174f77fa..c0039dbf 100644 --- a/S75_drone_planning.ipynb +++ b/S75_drone_planning.ipynb @@ -423,7 +423,7 @@ "id": "WWy7tWQLm4Mx", "metadata": {}, "source": [ - "Figure 3 shows both cost map and its blurred version, as well as the (un-blurred) gradient images `grad_u` and `grad_v`. We can then combined these into one rank-3 tensor and evaluate the cost *and* derivatives in one operation:" + "Figure [3](#fig:obstacles-2x2) shows both cost map and its blurred version, as well as the (un-blurred) gradient images `grad_u` and `grad_v`. We can then combined these into one rank-3 tensor and evaluate the cost *and* derivatives in one operation:" ] }, { @@ -791,12 +791,12 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "_1ShK_gNkTUX", "metadata": {}, "outputs": [], "source": [ - "# Add height of 1.5 meters\n", + "# Add height of 1.5 meters to the path:\n", "xy = gtsam.utilities.extractPoint2(result)\n", "path = np.hstack([xy, np.full((len(xy),1), 1.5)])\n", "\n", diff --git a/S76_drone_learning.ipynb b/S76_drone_learning.ipynb index b3fb110c..cb47cfca 100644 --- a/S76_drone_learning.ipynb +++ b/S76_drone_learning.ipynb @@ -145,7 +145,7 @@ "
\n", "\n", "The original 2020 NeRF paper by Mildenhall et al. {cite:p}`Mildenhall22eccv_Nerf` does an excellent job at explaining the basics.\n", - "Figure 1, from that paper, describes the setup: given a set of input images, with *known* camera pose and intrinsics calibration, a scene representation is learned that encodes the scene.\n", + "Figure [1](#fig:NeRF-setup), from that paper, describes the setup: given a set of input images, with *known* camera pose and intrinsics calibration, a scene representation is learned that encodes the scene.\n", "The original NeRF paper defines a large neural network that can be trained to predict the value of every pixel in every image. By doing so, the neural network can then also *generate* new images that are not in the original training set. What is more, the neural network can also be used to predict the 3D structure of the underlying scene, making it possible to do much more than simply view synthesis.\n", "\n", "The original NeRF work has introduced a new way on how to infer 3D scenes from images, and became very popular because:\n", @@ -182,7 +182,7 @@ "metadata": {}, "source": [ "\n", - "Figure 2 sketches out how volume rendering works. On the left, for *every* pixel you want to render, a ray is projected into the 3D volume of interest (here represented by a cube) and sampled 3D point locations are defined along this ray. For every sample, the neural network $F_\\Theta$ is queried for both a local *density* $\\delta$ and a *color* $c$. The colors are then integrated along the ray, with local color contributions weighted by the local density, to yield a final pixel color.\n", + "Figure [2](#fig:NeRF-pipeline) sketches out how volume rendering works. On the left, for *every* pixel you want to render, a ray is projected into the 3D volume of interest (here represented by a cube) and sampled 3D point locations are defined along this ray. For every sample, the neural network $F_\\Theta$ is queried for both a local *density* $\\delta$ and a *color* $c$. The colors are then integrated along the ray, with local color contributions weighted by the local density, to yield a final pixel color.\n", "\n", "To get some intuition, think about where high density regions will exist for the simple example scene shown. Here, we expect the density to be high on the *surface* of the object - the lego excavator toy in this example. If that is the only high-density area on the ray, the pixel corresponding to that ray will just use the color from that high-density surface. However, the integration scheme, which we implement below, is also able to account for occlusions or even semi-transparency." ] @@ -1103,7 +1103,7 @@ "
Training loss for SimpleDVGO training run over 5 epochs.
\n", "
\n", "\n", - "The training loss curve is shown in Figure 4. Note that, since we used 199 images and down-sampled the images by a factor of 4, each epoch corresponds to $200x200x199\\approx 8M$ rays. Given that the batch size used was 1024, this corresponds to approximately 7,800 iterations per epoch, or 39,000 iterations in total for 5 epochs. The loss drops dramatically in the first few iterations, but even after 5 epochs the loss is still slowly but steadily declining." + "The training loss curve is shown in Figure [4](#fig:NeRF-loss). Note that, since we used 199 images and down-sampled the images by a factor of 4, each epoch corresponds to $200x200x199\\approx 8M$ rays. Given that the batch size used was 1024, this corresponds to approximately 7,800 iterations per epoch, or 39,000 iterations in total for 5 epochs. The loss drops dramatically in the first few iterations, but even after 5 epochs the loss is still slowly but steadily declining." ] }, { diff --git a/references.bib b/references.bib index 188ae664..deeb1137 100644 --- a/references.bib +++ b/references.bib @@ -5,546 +5,584 @@ @string{tro @article{Adamkiewicz22ral_nerf_nav, - author = {Adamkiewicz, Michal and Chen, Timothy and Caccavale, Adam and Gardner, Rachel and Culbertson, Preston and Bohg, Jeannette and Schwager, Mac}, - doi = {10.1109/LRA.2022.3150497}, - journal = {IEEE Robotics and Automation Letters}, - number = {2}, - pages = {4606-4613}, - title = {Vision-Only Robot Navigation in a Neural Radiance World}, - volume = {7}, - year = {2022} + author = {Adamkiewicz, Michal and Chen, Timothy and Caccavale, Adam and Gardner, Rachel and Culbertson, Preston and Bohg, Jeannette and Schwager, Mac}, + doi = {10.1109/LRA.2022.3150497}, + journal = {IEEE Robotics and Automation Letters}, + number = {2}, + pages = {4606-4613}, + title = {{Vision-Only Robot Navigation in a Neural Radiance World}}, + volume = {7}, + year = {2022} +} + +@book{AutoMobileRobotsBook, + author = {Siegwart, R. and Nourbakhsh, I. and Scaramuzza, D.}, + publisher = {The MIT Press}, + title = {{Introduction to Autonomous Mobile Robots}}, + year = {2011} } @article{Baum66aoms_hmms, - author = {Baum, Leonard E. and Petrie, Ted}, - doi = {10.1214/aoms/1177699147}, - journal = {The Annals of Mathematical Statistics}, - number = {6}, - pages = {1554 -- 1563}, - publisher = {Institute of Mathematical Statistics}, - title = {{Statistical Inference for Probabilistic Functions of Finite State Markov Chains}}, - volume = {37}, - year = {1966} + author = {Baum, Leonard E. and Petrie, Ted}, + doi = {10.1214/aoms/1177699147}, + journal = {The Annals of Mathematical Statistics}, + number = {6}, + pages = {1554 -- 1563}, + publisher = {Institute of Mathematical Statistics}, + title = {{Statistical Inference for Probabilistic Functions of Finite State Markov Chains}}, + volume = {37}, + year = {1966} } @book{Beard11book, - author = {Beard, Randal W. and McLain, Timothy W.}, - month = feb, - publisher = {Princeton University Press}, - title = {{Small Unmanned Aircraft: Theory and Practice}}, - year = {2012}} + author = {Beard, Randal W. and McLain, Timothy W.}, + month = {feb}, + publisher = {Princeton University Press}, + title = {{Small Unmanned Aircraft: Theory and Practice}}, + year = {2012} +} @article{Bellman60, - author = {Bellman, Richard and Kalaba, Robert}, - doi = {10.1109/TAC.1960.6429288}, - journal = {IRE Transactions on Automatic Control}, - number = {1}, - pages = {5-10}, - title = {Dynamic programming and adaptive processes {I}: Mathematical foundation}, - volume = {AC-5}, - year = {1960} + author = {Bellman, Richard and Kalaba, Robert}, + doi = {10.1109/TAC.1960.6429288}, + journal = {IRE Transactions on Automatic Control}, + number = {1}, + pages = {5-10}, + title = {{Dynamic programming and adaptive processes {I}: Mathematical foundation}}, + volume = {AC-5}, + year = {1960} +} + +@article{Besl92pami_ICP, + author = {Besl, P.J. and McKay, Neil D.}, + doi = {10.1109/34.121791}, + journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, + number = {2}, + pages = {239-256}, + title = {{A method for registration of {3-D} shapes}}, + volume = {14}, + year = {1992} +} + +@inproceedings{Brohan23_rt2_vla, + title = {{RT-2: Vision-Language-Action Models Transfer Web Knowledge to Robotic Control}}, + author = {Anthony Brohan and Noah Brown and Justice Carbajal and Yevgen Chebotar and Xi Chen and Krzysztof Choromanski and Tianli Ding and Danny Driess and Avinava Dubey and Chelsea Finn and Pete Florence and Chuyuan Fu and Montse Gonzalez Arenas and Keerthana Gopalakrishnan and Kehang Han and Karol Hausman and Alex Herzog and Jasmine Hsu and Brian Ichter and Alex Irpan and Nikhil Joshi and Ryan Julian and Dmitry Kalashnikov and Yuheng Kuang and Isabel Leal and Lisa Lee and Tsang-Wei Edward Lee and Sergey Levine and Yao Lu and Henryk Michalewski and Igor Mordatch and Karl Pertsch and Kanishka Rao and Krista Reymann and Michael Ryoo and Grecia Salazar and Pannag Sanketi and Pierre Sermanet and Jaspiar Singh and Anikait Singh and Radu Soricut and Huong Tran and Vincent Vanhoucke and Quan Vuong and Ayzaan Wahid and Stefan Welker and Paul Wohlhart and Jialin Wu and Fei Xia and Ted Xiao and Peng Xu and Sichun Xu and Tianhe Yu and Brianna Zitkovich}, + booktitle = {{arXiv preprint arXiv:2307.15818}}, + year = {2023}, + url = {https://robotics-transformer2.github.io/} } @book{Chan23book_prob4ds, - author = {Chan, Stanley H.}, - isbn = {978-1-60785-747-1}, - publisher = {Michigan Publishing Services}, - title = {Introduction to Probability for Data Science}, - url = {https://probability4datascience.com/index.html}, - year = {2023} + author = {Chan, Stanley H.}, + isbn = {978-1-60785-747-1}, + publisher = {Michigan Publishing Services}, + title = {{Introduction to Probability for Data Science}}, + url = {https://probability4datascience.com/index.html}, + year = {2023} } @book{Choset05book_motion, - author = {Choset, Howie and Lynch, Kevin M. and Hutchinson, Seth and Kantor, George and Burgard, Wolfram and Kavraki, Lydia E. and Thrun, Sebastian}, - isbn = {9780262033275}, - publisher = {MIT Press}, - title = {Principles of Robot Motion}, - url = {https://mitpress.mit.edu/9780262033275/principles-of-robot-motion/}, - year = {2005} + author = {Choset, Howie and Lynch, Kevin M. and Hutchinson, Seth and Kantor, George and Burgard, Wolfram and Kavraki, Lydia E. and Thrun, Sebastian}, + isbn = {9780262033275}, + publisher = {MIT Press}, + title = {{Principles of Robot Motion}}, + url = {https://mitpress.mit.edu/9780262033275/principles-of-robot-motion/}, + year = {2005} +} + +@article{Dellaert17fnt_fg, + author = {Dellaert, Frank and Kaess, Michael}, + doi = {10.1561/2300000043}, + issn = {1935-8253}, + journal = {Foundations and Trends{\textregistered} in Robotics}, + number = {1-2}, + pages = {1-139}, + title = {{Factor Graphs for Robot Perception}}, + url = {https://www.nowpublishers.com/article/Details/ROB-043}, + volume = {6}, + year = {2017}, + bdsk-url-2 = {https://doi.org/10.1561/2300000043} +} + +@article{Dellaert21ar_fg, + author = {Dellaert, Frank}, + doi = {10.1146/annurev-control-061520-010504}, + journal = {Annual Review of Control, Robotics, and Autonomous Systems}, + pages = {141--166}, + publisher = {Annual Reviews}, + title = {{Factor graphs: Exploiting Structure in Robotics}}, + url = {http://annualreviews.org/eprint/85PQDQYUGNEU6JPHW698/full/10.1146/annurev-control-061520-010504}, + volume = {4}, + year = {2021} } @inproceedings{Dellaert99icra_mcl, - author = {Dellaert, Frank and Fox, Dieter and Burgard, Wolfram and Thrun, Sebastian}, - booktitle = {Proceedings 1999 IEEE International Conference on Robotics and Automation}, - doi = {10.1109/ROBOT.1999.772544}, - pages = {1322-1328 vol.2}, - title = {Monte Carlo localization for mobile robots}, - volume = {2}, - year = {1999} + author = {Dellaert, Frank and Fox, Dieter and Burgard, Wolfram and Thrun, Sebastian}, + booktitle = {{Proceedings 1999 IEEE International Conference on Robotics and Automation}}, + doi = {10.1109/ROBOT.1999.772544}, + pages = {1322-1328 vol.2}, + title = {{Monte Carlo Localization for Mobile Robots}}, + volume = {2}, + year = {1999} } -@article{Dellaert17fnt_fg, - author = {Dellaert, Frank and Kaess, Michael}, - doi = {10.1561/2300000043}, - issn = {1935-8253}, - journal = {Foundations and Trends{\textregistered} in Robotics}, - number = {1-2}, - pages = {1-139}, - title = {Factor Graphs for Robot Perception}, - url = {https://www.nowpublishers.com/article/Details/ROB-043}, - volume = {6}, - year = {2017} -, - bdsk-url-2 = {https://doi.org/10.1561/2300000043}} +@inproceedings{Dosovitskiy21iclr_VIT, + title = {{An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale}}, + author = {Dosovitskiy, Alexey and Beyer, Lucas and Kolesnikov, Alexander and Weissenborn, Dirk and Zhai, Xiaohua and Unterthiner, Thomas and Dehghani, Mostafa and Minderer, Matthias and Heigold, Georg and Gelly, Sylvain and Uszkoreit, Jakob and Houlsby, Neil}, + booktitle = {{International Conference on Learning Representations}}, + year = {2021}, + url = {https://openreview.net/forum?id=YicbFdNTTy} +} -@article{Dellaert21ar_fg, - author = {Dellaert, Frank}, - doi = {10.1146/annurev-control-061520-010504}, - journal = {Annual Review of Control, Robotics, and Autonomous Systems}, - pages = {141--166}, - publisher = {Annual Reviews}, - title = {Factor graphs: Exploiting structure in robotics}, - utl = {http://annualreviews.org/eprint/85PQDQYUGNEU6JPHW698/full/10.1146/annurev-control-061520-010504}, - volume = {4}, - year = {2021} +@book{duda2012pattern, + author = {Duda, R.O. and Hart, P.E. and Stork, D.G.}, + isbn = {9781118586006}, + publisher = {Wiley}, + title = {{Pattern Classification}}, + url = {https://books.google.co.uk/books?id=Br33IRC3PkQC}, + year = {2012} +} + +@misc{DvgoChecpoint5Epochs, + author = {Dellaert, Frank}, + title = {{DVGO} Checkpoint after 5 epochs}, + year = {2023}, + url = {https://zenodo.org/records/10767234/files/simple_dvgo-5-epochs.pt} } @book{Farrell08book, - author = {Farrell, Jay A.}, - publisher = {McGraw-Hill}, - title = {Aided Navigation: {GPS} with High Rate Sensors}, - year = {2008}} + author = {Farrell, Jay A.}, + publisher = {McGraw-Hill}, + title = {{Aided Navigation: {GPS} with High Rate Sensors}}, + year = {2008} +} @article{Forster16tro, - author = {Forster, C. and Carlone, L. and Dellaert, F. and Scaramuzza, D.}, - doi = {10.1109/TRO.2016.2597321}, - fullauthor = {Christian Forster and Luca Carlone and Frank Dellaert and Davide Scaramuzza}, - journal = TRO, - title = {On-Manifold Preintegration for Real-Time Visual-Inertial Odometry}, - year = 2016 + author = {Forster, C. and Carlone, L. and Dellaert, F. and Scaramuzza, D.}, + doi = {10.1109/TRO.2016.2597321}, + fullauthor = {Christian Forster and Luca Carlone and Frank Dellaert and Davide Scaramuzza}, + journal = TRO, + title = {{On-Manifold Preintegration for Real-Time Visual-Inertial Odometry}}, + year = 2016 } -@inproceedings{Gamagedara19acc_geometric_control, - author = {Gamagedara, Kanishke and Bisheban, Mahdis and Kaufman, Evan and Lee, Taeyoung}, - booktitle = {2019 American Control Conference (ACC)}, - doi = {10.23919/ACC.2019.8815189}, - pages = {3285-3290}, - title = {Geometric Controls of a Quadrotor UAV with Decoupled Yaw Control}, - year = {2019} +@article{FrancoisLavet18fnt_DRL, + author = {Fran{\c c}ois-Lavet, Vincent and Henderson, Peter and Islam, Riashat and Bellemare, Marc G. and Pineau, Joelle}, + doi = {10.1561/2200000071}, + issn = {1935-8245}, + journal = {Foundations and Trends in Machine Learning}, + number = {3--4}, + pages = {219--354}, + publisher = {Now Publishers}, + title = {{An Introduction to Deep Reinforcement Learning}}, + url = {http://dx.doi.org/10.1561/2200000071}, + volume = {11}, + year = {2018} } -@book{Goodfellow16book_dl, - author = {Goodfellow, Ian and Bengio, Yoshua and Courville, Aaron}, - isbn = {9780262035613}, - publisher = {MIT Press}, - title = {Deep Learning}, - url = {https://www.deeplearningbook.org}, - year = {2016} +@article{Fukushima80bc_neocognitron, + title = {{Neocognitron: A self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position}}, + author = {Fukushima, Kunihiko}, + journal = {Biological Cybernetics}, + volume = {36}, + number = {4}, + pages = {193--202}, + year = {1980}, + publisher = {Springer}, + url = {https://en.wikipedia.org/wiki/Neocognitron} } -@book{Hartley00, - author = {Hartley, Richard and Zisserman, Andrew}, - publisher = {Cambridge University Press}, - title = {Multiple View Geometry in Computer Vision}, - year = {2000}} - -@article{Jelinek75it_decoder, - author = {Jelinek, F. and Bahl, L. and Mercer, R.}, - doi = {10.1109/TIT.1975.1055384}, - journal = {IEEE Transactions on Information Theory}, - number = {3}, - pages = {250-256}, - title = {Design of a linguistic statistical decoder for the recognition of continuous speech}, - volume = {21}, - year = {1975} +@inproceedings{Gamagedara19acc_geometric_control, + author = {Gamagedara, Kanishke and Bisheban, Mahdis and Kaufman, Evan and Lee, Taeyoung}, + booktitle = {{2019 American Control Conference (ACC)}}, + doi = {10.23919/ACC.2019.8815189}, + pages = {3285-3290}, + title = {{Geometric Controls of a Quadrotor UAV with Decoupled Yaw Control}}, + year = {2019} } -@book{LaValle06book_planning, - author = {LaValle, Steven M.}, - isbn = {9780521862059}, - publisher = {Cambridge University Press}, - title = {Planning Algorithms}, - url = {https://lavalle.pl/planning/}, - year = {2006} +@book{Goodfellow16book_dl, + author = {Goodfellow, Ian and Bengio, Yoshua and Courville, Aaron}, + isbn = {9780262035613}, + publisher = {MIT Press}, + title = {{Deep Learning}}, + url = {https://www.deeplearningbook.org}, + year = {2016} } -@book{Latombe91book, - address = {USA}, - author = {Latombe, Jean-Claude}, - date-added = {2024-07-17 14:37:24 +0200}, - date-modified = {2024-07-17 14:37:44 +0200}, - isbn = {0792391292}, - publisher = {Kluwer Academic Publishers}, - title = {Robot Motion Planning}, - year = {1991}} - -@article{Lupton12tro, - author = {Lupton, Todd and Sukkarieh, Salah}, - doi = {10.1109/TRO.2011.2170332}, - journal = TRO, - month = {Feb}, - number = {1}, - pages = {61-76}, - title = {Visual-Inertial-Aided Navigation for High-Dynamic Motion in Built Environments Without Initial Conditions}, - volume = {28}, - year = {2012} +@misc{graphviz, + year = {2024}, + author = {Dellaert, Frank}, + title = {{Graphviz}: open source graph visualization software}, + url = {https://graphviz.org/} } -@book{Lynch17book_MR, - address = {USA}, - author = {Lynch, Kevin M. and Park, Frank C.}, - isbn = {1107156300}, - publisher = {Cambridge University Press}, - title = {Modern Robotics: Mechanics, Planning, and Control}, - url = {http://modernrobotics.org/}, - year = {2017} +@misc{gtbook, + year = {2024}, + author = {Dellaert, Frank}, + title = {{gtbook: an nbdev powered toolbox for {Frank} and {Seth}’s robotics book}}, + url = {https://gtbook.github.io/gtbook/} } -@book{Ma04book, - author = {Ma, Yi and Soatto, Stefano and Kosecka, Jana and Sastry, Shankar S.}, - publisher = {Springer}, - title = {An Invitation to {3-D} Vision}, - year = {2004}} +@article{Guivant01tra_SLAM, + title = {Optimization of the Simultaneous Localization and Map-Building Algorithm for Real-Time Implementation}, + author = {José E. Guivant and Eduardo Mario Nebot}, + journal = {IEEE Transactions on Robotics and Automation}, + volume = {17}, + number = {3}, + pages = {242--257}, + year = {2001}, + publisher = {IEEE} +} -@article{Mahony12ram, - author = {Mahony, Robert and Kumar, Vijay and Corke, Peter}, - doi = {10.1109/MRA.2012.2206474}, - journal = RAM, - publisher = {IEEE}, - title = {Multirotor aerial vehicles: Modeling, estimation, and control of quadrotor}, - year = 2012 +@book{HaldBook03, + author = {Hald, Anders}, + publisher = {Wiley}, + title = {{A History of Probability and Statistics and Their Applications before 1750}}, + year = {2003} } -@article{Mildenhall22eccv_Nerf, - address = {New York, NY, USA}, - author = {Mildenhall, Ben and Srinivasan, Pratul P. and Tancik, Matthew and Barron, Jonathan T. and Ramamoorthi, Ravi and Ng, Ren}, - doi = {10.1145/3503250}, - issn = {0001-0782}, - journal = {Commun. ACM}, - month = {dec}, - number = {1}, - numpages = {8}, - pages = {99--106}, - publisher = {Association for Computing Machinery}, - title = {NeRF: representing scenes as neural radiance fields for view synthesis}, - volume = {65}, - year = {2021} +@book{HaldBook98, + author = {Hald, Anders}, + publisher = {Wiley}, + title = {{A history of mathematical statistics from 1750 to 1930}}, + year = {1998} } -@book{Murray94book, - author = {Murray, Richard M. and Li, Zexiang and Sastry, Shankar S.}, - publisher = {CRC Press}, - title = {A Mathematical Introduction to Robotic Manipulation}, - year = {1994}} +@book{Hartley00, + author = {Hartley, Richard and Zisserman, Andrew}, + publisher = {Cambridge University Press}, + title = {{Multiple View Geometry in Computer Vision}}, + year = {2000} +} -@article{Schoenemann66_procrustes, - author = {Schoenemann, Peter}, - doi = {10.1007/BF02289451}, - journal = {Psychometrika}, - month = {March}, - number = {1}, - pages = {1-10}, - title = {{A generalized solution of the orthogonal procrustes problem}}, - volume = {31}, - year = 1966 +@article{Ila10tro_PoseSLAM, + author = {Ila, Viorela and Porta, Josep M. and Andrade-Cetto, Juan}, + doi = {10.1109/TRO.2009.2034435}, + journal = {IEEE Transactions on Robotics}, + number = {1}, + pages = {78-93}, + title = {{Information-Based Compact Pose SLAM}}, + volume = {26}, + year = {2010} } -@book{Siegwart11book_robots, - author = {Siegwart, Roland and Nourbakhsh, Illah Reza and Scaramuzza, Davide}, - isbn = {9780262015356}, - publisher = {MIT Press}, - title = {Introduction to Autonomous Mobile Robots}, - url = {https://mitpress.mit.edu/9780262015356/introduction-to-autonomous-mobile-robots/}, - year = {2011} +@book{JCL:91, + address = {Boston, MA}, + author = {Latombe, J.C.}, + publisher = {Kluwer Academic Publishers}, + title = {{Robot Motion Planning}}, + year = {1991} } -@article{Silver16_alphago, - author = {Silver, David and Huang, Aja and Maddison, Chris J and Guez, Arthur and Sifre, Laurent and Van Den Driessche, George and Schrittwieser, Julian and Antonoglou, Ioannis and Panneershelvam, Veda and Lanctot, Marc and others}, - journal = {Nature}, - number = 7587, - pages = {484--489}, - publisher = {Nature Publishing Group}, - title = {Mastering the game of Go with deep neural networks and tree search}, - volume = 529, - year = 2016 +@article{Jelinek75it_decoder, + author = {Jelinek, F. and Bahl, L. and Mercer, R.}, + doi = {10.1109/TIT.1975.1055384}, + journal = {IEEE Transactions on Information Theory}, + number = {3}, + pages = {250-256}, + title = {{Design of a linguistic statistical decoder for the recognition of continuous speech}}, + volume = {21}, + year = {1975} } -@inproceedings{Sun22cvpr_dvgo, - author = {Sun, Cheng and Sun, Min and Chen, Hwann-Tzong}, - booktitle = {2022 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, - doi = {10.1109/CVPR52688.2022.00538}, - pages = {5449-5459}, - title = {Direct Voxel Grid Optimization: Super-fast Convergence for Radiance Fields Reconstruction}, - year = {2022} +@inproceedings{Kirillov23iccv_SAM, + title = {{Segment Anything}}, + author = {Kirillov, Alexander and Mintun, Eric and Ravi, Nikhila and Mao, Hanzi and Rolland, Chloe and Gustafson, Laura and Xiao, Tete and Whitehead, Spencer and Berg, Alexander C. and Lo, Wan-Yen and Doll{\'a}r, Piotr and Girshick, Ross}, + booktitle = {{Proceedings of the IEEE/CVF International Conference on Computer Vision}}, + pages = {1--10}, + year = {2023} } -@book{Sutton18book_reinforcement, - address = {Cambridge, MA, USA}, - author = {Sutton, Richard S. and Barto, Andrew G.}, - edition = {2}, - isbn = {9780262039246}, - publisher = {The MIT Press}, - title = {Reinforcement Learning: An Introduction}, - url = {http://incompleteideas.net/book/the-book-2nd.html}, - year = {2018} +@book{Latombe91book, + address = {USA}, + author = {Latombe, Jean-Claude}, + date-added = {2024-07-17 14:37:24 +0200}, + date-modified = {2024-07-17 14:37:44 +0200}, + isbn = {0792391292}, + publisher = {Kluwer Academic Publishers}, + title = {{Robot Motion Planning}}, + year = {1991} +} +@book{LaValle06book_planning, + author = {LaValle, Steven M.}, + isbn = {9780521862059}, + publisher = {Cambridge University Press}, + title = {{Planning Algorithms}}, + url = {https://lavalle.pl/planning/}, + year = {2006} } -@book{Thrun05book_probabilistic, - author = {Thrun, Sebastian and Burgard, Wolfram and Fox, Dieter}, - isbn = {9780262201629}, - publisher = {The MIT Press}, - title = {Probabilistic Robotics}, - url = {https://mitpress.mit.edu/9780262201629/probabilistic-robotics/}, - year = {2005} +@book{LavalleBook, + address = {Cambridge, MA}, + author = {LaValle, S. M.}, + publisher = {Cambridge University Press}, + title = {{Planning Algorithms}}, + year = 2006 } -@book{Watkins89thesis_Qlearning, - author = {Watkins, Christopher John Cornish Hellaby}, - publisher = {King's College, Cambridge United Kingdom}, - title = {Learning from delayed rewards}, - year = {1989}} - -@book{Zhang23book_d2l, - title={Dive into Deep Learning}, - author={Zhang, Aston and Lipton, Zachary C. and Li, Mu and Smola, Alexander J.}, - publisher={Cambridge University Press}, - url={https://d2l.ai}, - year={2023} +@article{Lecun98ieee_LeNet, + title = {{Gradient-based learning applied to document recognition}}, + author = {LeCun, Yann and Bottou, L{\'e}on and Bengio, Yoshua and Haffner, Patrick}, + journal = {Proceedings of the IEEE}, + volume = {86}, + number = {11}, + pages = {2278--2324}, + year = {1998}, + publisher = {IEEE}, + url = {https://ieeexplore.ieee.org/abstract/document/726791} } -@book{HaldBook98, - author = {Hald, Anders}, - publisher = {Wiley}, - title = {A history of mathematical statistics from 1750 to 1930}, - year = {1998}} -@book{HaldBook03, - author = {Hald, Anders}, - publisher = {Wiley}, - title = {A History of Probability and Statistics and Their Applications before 1750}, - year = {2003}} +@inproceedings{Li20icma_LaneChange, + author = {Li, Zhiyuan and Liang, Huawei and Zhao, Pan and Wang, Shaobo and Zhu, Hui}, + booktitle = {{2020 IEEE International Conference on Mechatronics and Automation (ICMA)}}, + doi = {10.1109/ICMA49215.2020.9233841}, + pages = {338-344}, + title = {{Efficient Lane Change Path Planning based on Quintic spline for Autonomous Vehicles}}, + year = {2020} +} -@book{Pearl88Probabilistic, - author = {Pearl, J.}, - publisher = {Morgan Kaufmann Publishers, Inc.}, - title = {Probabilistic Reasoning in Intelligent Systems: Networks of Plausible Inference}, - year = {1988}} +@article{Lupton12tro, + author = {Lupton, Todd and Sukkarieh, Salah}, + doi = {10.1109/TRO.2011.2170332}, + journal = TRO, + month = {Feb}, + number = {1}, + pages = {61-76}, + title = {{Visual-Inertial-Aided Navigation for High-Dynamic Motion in Built Environments Without Initial Conditions}}, + volume = {28}, + year = {2012} +} -@book{ProbGraphModels, - author = {Koller, D. and Friedman, N.}, - publisher = {The MIT Press}, - title = {Probabilistic Graphical Models Principles and Techniques}, - year = {2009}} +@book{Lynch17book_MR, + address = {USA}, + author = {Lynch, Kevin M. and Park, Frank C.}, + isbn = {1107156300}, + publisher = {Cambridge University Press}, + title = {{Modern Robotics: Mechanics, Planning, and Control}}, + url = {http://modernrobotics.org/}, + year = {2017} +} -@book{duda2012pattern, - author = {Duda, R.O. and Hart, P.E. and Stork, D.G.}, - isbn = {9781118586006}, - publisher = {Wiley}, - title = {Pattern Classification}, - url = {https://books.google.co.uk/books?id=Br33IRC3PkQC}, - year = {2012} +@book{Ma04book, + author = {Ma, Yi and Soatto, Stefano and Kosecka, Jana and Sastry, Shankar S.}, + publisher = {Springer}, + title = {{An Invitation to {3-D} Vision}}, + year = {2004} } -@article{SmaSon73, - author = {Smallwood, Richard D. and Sondik, Edward J.}, - journal = {Operations Research}, - month = {Sep.}, - number = {5}, - pages = {1071--1088}, - publisher = {INFORMS}, - title = {The Optimal Control of Partially Observable {M}arkov Processes Over a Finite Horizon}, - volume = {21}, - year = {1973}} +@article{Mahony12ram, + author = {Mahony, Robert and Kumar, Vijay and Corke, Peter}, + doi = {10.1109/MRA.2012.2206474}, + journal = RAM, + publisher = {IEEE}, + title = {{Multirotor aerial vehicles: Modeling, estimation, and control of quadrotor}}, + year = 2012 +} -@article{Son78, - author = {Sondik, Edward J.}, - journal = {Operations Research}, - month = {March}, - number = {2}, - pages = {282--304}, - publisher = {INFORMS}, - title = {The Optimal Control of Partially Observable {M}arkov Processes Over the Infinite Horizon: Discounted Costs}, - volume = {26}, - year = {1978}} +@article{Mildenhall22eccv_Nerf, + address = {New York, NY, USA}, + author = {Mildenhall, Ben and Srinivasan, Pratul P. and Tancik, Matthew and Barron, Jonathan T. and Ramamoorthi, Ravi and Ng, Ren}, + doi = {10.1145/3503250}, + issn = {0001-0782}, + journal = {Commun. ACM}, + month = {dec}, + number = {1}, + numpages = {8}, + pages = {99--106}, + publisher = {Association for Computing Machinery}, + title = {{NeRF: representing scenes as neural radiance fields for view synthesis}}, + volume = {65}, + year = {2021} +} -@book{AutoMobileRobotsBook, - author = {Siegwart, R. and Nourbakhsh, I. and Scaramuzza, D.}, - publisher = {The MIT Press}, - title = {Introduction to Autonomous Mobile Robots}, - year = {2011}} +@article{Mnih15nature_dqn, + author = {Mnih, Volodymyr and Kavukcuoglu, Koray and Silver, David and Rusu, Andrei A. and Veness, Joel and Bellemare, Marc G. and Graves, Alex and Riedmiller, Martin and Fidjeland, Andreas K. and Ostrovski, Georg and Petersen, Stig and Beattie, Charles and Sadik, Amir and Antonoglou, Ioannis and King, Helen and Kumaran, Dharshan and Wierstra, Daan and Legg, Shane and Hassabis, Demis}, + doi = {10.1038/nature14236}, + issn = {1476-4687}, + journal = {Nature}, + month = feb, + number = {7540}, + pages = {529--533}, + title = {{Human-level control through deep reinforcement learning}}, + url = {https://doi.org/10.1038/nature14236}, + volume = {518}, + year = {2015} +} -@book{JCL:91, - address = {Boston, MA}, - author = {Latombe, J.C.}, - publisher = {Kluwer Academic Publishers}, - title = {Robot Motion Planning}, - year = {1991}} +@book{Murray94book, + author = {Murray, Richard M. and Li, Zexiang and Sastry, Shankar S.}, + publisher = {CRC Press}, + title = {{A Mathematical Introduction to Robotic Manipulation}}, + year = {1994} +} -@book{LavalleBook, - address = {Cambridge, MA}, - author = {LaValle, S. M.}, - publisher = {Cambridge University Press}, - title = {Planning Algorithms}, - year = 2006} +@book{Pearl88Probabilistic, + author = {Pearl, J.}, + publisher = {Morgan Kaufmann Publishers, Inc.}, + title = {{Probabilistic Reasoning in Intelligent Systems: Networks of Plausible Inference}}, + year = {1988} +} -@article{Ila10tro_PoseSLAM, - author = {Ila, Viorela and Porta, Josep M. and Andrade-Cetto, Juan}, - doi = {10.1109/TRO.2009.2034435}, - journal = {IEEE Transactions on Robotics}, - number = {1}, - pages = {78-93}, - title = {Information-Based Compact Pose SLAM}, - volume = {26}, - year = {2010} +@book{ProbGraphModels, + author = {Koller, D. and Friedman, N.}, + publisher = {The MIT Press}, + title = {{Probabilistic Graphical Models Principles and Techniques}}, + year = {2009} } -@article{Williams92ml_reinforce, - author = {Williams, R. J.}, - journal = {Machine Learning}, - pages = {229-259}, - title = {Simple statistical gradient-following algorithms for connectionist reinforcement learning}, - volume = 8, - year = {1992}} +@article{Schoenemann66_procrustes, + author = {Schoenemann, Peter}, + doi = {10.1007/BF02289451}, + journal = {Psychometrika}, + month = {mar}, + number = {1}, + pages = {1-10}, + title = {{A generalized solution of the orthogonal procrustes problem}}, + volume = {31}, + year = 1966 +} @article{Schulman17_PPO, - author = {Schulman, John and Wolski, Filip and Dhariwal, Prafulla and Radford, Alec and Klimov, Oleg}, - bibsource = {dblp computer science bibliography, https://dblp.org}, - biburl = {https://dblp.org/rec/journals/corr/SchulmanWDRK17.bib}, - eprint = {1707.06347}, - eprinttype = {arXiv}, - journal = {CoRR}, - timestamp = {Mon, 13 Aug 2018 16:47:34 +0200}, - title = {Proximal Policy Optimization Algorithms}, - url = {http://arxiv.org/abs/1707.06347}, - volume = {abs/1707.06347}, - year = {2017} + author = {Schulman, John and Wolski, Filip and Dhariwal, Prafulla and Radford, Alec and Klimov, Oleg}, + bibsource = {dblp computer science bibliography, https://dblp.org}, + biburl = {https://dblp.org/rec/journals/corr/SchulmanWDRK17.bib}, + eprint = {1707.06347}, + eprinttype = {arXiv}, + journal = {CoRR}, + timestamp = {Mon, 13 Aug 2018 16:47:34 +0200}, + title = {{Proximal Policy Optimization Algorithms}}, + url = {http://arxiv.org/abs/1707.06347}, + volume = {abs/1707.06347}, + year = {2017} } -@book{Spong96book, - address = {NY, NY}, - author = {Spong, M. and Hutchinson, S. and Vidyasagar, M.}, - publisher = {John Wiley and Sons}, - title = {Robot Modeling and Control}, - year = {2006}} - -@article{Besl92pami_ICP, - author = {Besl, P.J. and McKay, Neil D.}, - doi = {10.1109/34.121791}, - journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, - number = {2}, - pages = {239-256}, - title = {A method for registration of 3-D shapes}, - volume = {14}, - year = {1992} +@book{Siegwart11book_robots, + author = {Siegwart, Roland and Nourbakhsh, Illah Reza and Scaramuzza, Davide}, + isbn = {9780262015356}, + publisher = {MIT Press}, + title = {{Introduction to Autonomous Mobile Robots}}, + url = {https://mitpress.mit.edu/9780262015356/introduction-to-autonomous-mobile-robots/}, + year = {2011} } -@inproceedings{Li20icma_LaneChange, - author = {Li, Zhiyuan and Liang, Huawei and Zhao, Pan and Wang, Shaobo and Zhu, Hui}, - booktitle = {2020 IEEE International Conference on Mechatronics and Automation (ICMA)}, - doi = {10.1109/ICMA49215.2020.9233841}, - pages = {338-344}, - title = {Efficient Lane Change Path Planning based on Quintic spline for Autonomous Vehicles}, - year = {2020} +@article{Silver16_alphago, + author = {Silver, David and Huang, Aja and Maddison, Chris J and Guez, Arthur and Sifre, Laurent and Van Den Driessche, George and Schrittwieser, Julian and Antonoglou, Ioannis and Panneershelvam, Veda and Lanctot, Marc and others}, + journal = {Nature}, + number = 7587, + pages = {484--489}, + publisher = {Nature Publishing Group}, + title = {{Mastering the game of Go with deep neural networks and tree search}}, + volume = 529, + year = 2016 } -@inproceedings{Werling10icra_Frenet, - author = {Werling, Moritz and Ziegler, Julius and Kammel, S{\"o}ren and Thrun, Sebastian}, - booktitle = {2010 IEEE International Conference on Robotics and Automation}, - doi = {10.1109/ROBOT.2010.5509799}, - pages = {987-993}, - title = {Optimal trajectory generation for dynamic street scenarios in a Fren{\'e}t Frame}, - year = {2010} +@article{SmaSon73, + author = {Smallwood, Richard D. and Sondik, Edward J.}, + journal = {Operations Research}, + month = {sep}, + number = {5}, + pages = {1071--1088}, + publisher = {INFORMS}, + title = {{The Optimal Control of Partially Observable {M}arkov Processes Over a Finite Horizon}}, + volume = {21}, + year = {1973} } -@article{FrancoisLavet18fnt_DRL, - author = {Fran{\c c}ois-Lavet, Vincent and Henderson, Peter and Islam, Riashat and Bellemare, Marc G. and Pineau, Joelle}, - doi = {10.1561/2200000071}, - issn = {1935-8245}, - journal = {Foundations and Trends in Machine Learning}, - number = {3--4}, - pages = {219--354}, - publisher = {Now Publishers}, - title = {An Introduction to Deep Reinforcement Learning}, - url = {http://dx.doi.org/10.1561/2200000071}, - volume = {11}, - year = {2018} +@article{Son78, + author = {Sondik, Edward J.}, + journal = {Operations Research}, + month = {mar}, + number = {2}, + pages = {282--304}, + publisher = {INFORMS}, + title = {{The Optimal Control of Partially Observable {M}arkov Processes Over the Infinite Horizon: Discounted Costs}}, + volume = {26}, + year = {1978} } -@article{Mnih15nature_dqn, - author = {Mnih, Volodymyr and Kavukcuoglu, Koray and Silver, David and Rusu, Andrei A. and Veness, Joel and Bellemare, Marc G. and Graves, Alex and Riedmiller, Martin and Fidjeland, Andreas K. and Ostrovski, Georg and Petersen, Stig and Beattie, Charles and Sadik, Amir and Antonoglou, Ioannis and King, Helen and Kumaran, Dharshan and Wierstra, Daan and Legg, Shane and Hassabis, Demis}, - doi = {10.1038/nature14236}, - issn = {1476-4687}, - journal = {Nature}, - month = feb, - number = {7540}, - pages = {529--533}, - title = {Human-level control through deep reinforcement learning}, - url = {https://doi.org/10.1038/nature14236}, - volume = {518}, - year = {2015} +@book{Spong96book, + address = {NY, NY}, + author = {Spong, M. and Hutchinson, S. and Vidyasagar, M.}, + publisher = {John Wiley and Sons}, + title = {{Robot Modeling and Control}}, + year = {2006} } -@misc{DvgoChecpoint5Epochs, - author = {Dellaert, Frank}, - title = {{DVGO} Checkpoint after 5 epochs}, - year = {2023}, - url = {https://zenodo.org/records/10767234/files/simple_dvgo-5-epochs.pt}, +@inproceedings{Sun22cvpr_dvgo, + author = {Sun, Cheng and Sun, Min and Chen, Hwann-Tzong}, + booktitle = {{2022 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}}, + doi = {10.1109/CVPR52688.2022.00538}, + pages = {5449-5459}, + title = {{Direct Voxel Grid Optimization: Super-fast Convergence for Radiance Fields Reconstruction}}, + year = {2022} } -@misc{gtbook, - year = {2024}, - author = {Dellaert, Frank}, - title = {gtbook: an nbdev powered toolbox for {Frank} and {Seth}’s robotics book}, - url = {https://gtbook.github.io/gtbook/}, +@book{Sutton18book_reinforcement, + address = {Cambridge, MA, USA}, + author = {Sutton, Richard S. and Barto, Andrew G.}, + edition = {2}, + isbn = {9780262039246}, + publisher = {The MIT Press}, + title = {{Reinforcement Learning: An Introduction}}, + url = {http://incompleteideas.net/book/the-book-2nd.html}, + year = {2018} } -@misc{graphviz, - year = {2024}, - author = {Dellaert, Frank}, - title = {{Graphviz}: open source graph visualization software}, - url = {https://graphviz.org/}, +@book{Thrun05book_probabilistic, + author = {Thrun, Sebastian and Burgard, Wolfram and Fox, Dieter}, + isbn = {9780262201629}, + publisher = {The MIT Press}, + title = {{Probabilistic Robotics}}, + url = {https://mitpress.mit.edu/9780262201629/probabilistic-robotics/}, + year = {2005} } @inproceedings{Vaswani17neurips_attention, - title={Attention Is All You Need}, - author={Vaswani, Ashish and Shazeer, Noam and Parmar, Niki and Uszkoreit, Jakob and Jones, Llion and Gomez, Aidan N and Kaiser, Łukasz and Polosukhin, Illia}, - booktitle={Advances in Neural Information Processing Systems}, - pages={5998--6008}, - year={2017}, - url={https://en.wikipedia.org/wiki/Attention_Is_All_You_Need} -} -@article{Dosovitskiy21iclr_VIT, - title={An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale}, - author={Dosovitskiy, Alexey and Beyer, Lucas and Kolesnikov, Alexander and Weissenborn, Dirk and Zhai, Xiaohua and Unterthiner, Thomas and Dehghani, Mostafa and Minderer, Matthias and Heigold, Georg and Gelly, Sylvain and Uszkoreit, Jakob and Houlsby, Neil}, - booktitle={International Conference on Learning Representations}, - year={2021}, - url={https://openreview.net/forum?id=YicbFdNTTy} + title = {{Attention Is All You Need}}, + author = {Vaswani, Ashish and Shazeer, Noam and Parmar, Niki and Uszkoreit, Jakob and Jones, Llion and Gomez, Aidan N and Kaiser, Łukasz and Polosukhin, Illia}, + booktitle = {{Advances in Neural Information Processing Systems}}, + pages = {5998--6008}, + year = {2017}, + url = {https://en.wikipedia.org/wiki/Attention_Is_All_You_Need} } -@inproceedings{Kirillov23iccv_SAM, - title={Segment Anything}, - author={Kirillov, Alexander and Mintun, Eric and Ravi, Nikhila and Mao, Hanzi and Rolland, Chloe and Gustafson, Laura and Xiao, Tete and Whitehead, Spencer and Berg, Alexander C. and Lo, Wan-Yen and Doll{\'a}r, Piotr and Girshick, Ross}, - booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, - pages={1--10}, - year={2023} + +@book{Watkins89thesis_Qlearning, + author = {Watkins, Christopher John Cornish Hellaby}, + publisher = {King's College, Cambridge United Kingdom}, + title = {{Learning from delayed rewards}}, + year = {1989} } -@inproceedings{Brohan23_rt2_vla, - title={RT-2: Vision-Language-Action Models Transfer Web Knowledge to Robotic Control}, - author={Anthony Brohan and Noah Brown and Justice Carbajal and Yevgen Chebotar and Xi Chen and Krzysztof Choromanski and Tianli Ding and Danny Driess and Avinava Dubey and Chelsea Finn and Pete Florence and Chuyuan Fu and Montse Gonzalez Arenas and Keerthana Gopalakrishnan and Kehang Han and Karol Hausman and Alex Herzog and Jasmine Hsu and Brian Ichter and Alex Irpan and Nikhil Joshi and Ryan Julian and Dmitry Kalashnikov and Yuheng Kuang and Isabel Leal and Lisa Lee and Tsang-Wei Edward Lee and Sergey Levine and Yao Lu and Henryk Michalewski and Igor Mordatch and Karl Pertsch and Kanishka Rao and Krista Reymann and Michael Ryoo and Grecia Salazar and Pannag Sanketi and Pierre Sermanet and Jaspiar Singh and Anikait Singh and Radu Soricut and Huong Tran and Vincent Vanhoucke and Quan Vuong and Ayzaan Wahid and Stefan Welker and Paul Wohlhart and Jialin Wu and Fei Xia and Ted Xiao and Peng Xu and Sichun Xu and Tianhe Yu and Brianna Zitkovich}, - booktitle={arXiv preprint arXiv:2307.15818}, - year={2023}, - url={https://robotics-transformer2.github.io/} +@inproceedings{Werling10icra_Frenet, + author = {Werling, Moritz and Ziegler, Julius and Kammel, S{\"o}ren and Thrun, Sebastian}, + booktitle = {{2010 IEEE International Conference on Robotics and Automation}}, + doi = {10.1109/ROBOT.2010.5509799}, + pages = {987-993}, + title = {{Optimal trajectory generation for dynamic street scenarios in a Fren{\'e}t Frame}}, + year = {2010} } -@article{Fukushima80bc_neocognitron, - title={Neocognitron: A self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position}, - author={Fukushima, Kunihiko}, - journal={Biological Cybernetics}, - volume={36}, - number={4}, - pages={193--202}, - year={1980}, - publisher={Springer}, - url={https://en.wikipedia.org/wiki/Neocognitron} +@article{Williams92ml_reinforce, + author = {Williams, R. J.}, + journal = {Machine Learning}, + pages = {229-259}, + title = {{Simple statistical gradient-following algorithms for connectionist reinforcement learning}}, + volume = 8, + year = {1992} } -@article{Lecun98ieee_LeNet, - title={Gradient-based learning applied to document recognition}, - author={LeCun, Yann and Bottou, L{\'e}on and Bengio, Yoshua and Haffner, Patrick}, - journal={Proceedings of the IEEE}, - volume={86}, - number={11}, - pages={2278--2324}, - year={1998}, - publisher={IEEE}, - url={https://ieeexplore.ieee.org/abstract/document/726791} -} \ No newline at end of file +@inproceedings{Wilson21_Argoverse2, + author = {Benjamin Wilson and William Qi and Tanmay Agarwal and John Lambert and Jagjeet Singh and Siddhesh Khandelwal and Bowen Pan and Ratnesh Kumar and Andrew Hartnett and Jhony Kaesemodel Pontes and Deva Ramanan and Peter Carr and James Hays}, + title = {{Argoverse 2: Next Generation Datasets for Self-driving Perception and Forecasting}}, + booktitle = {{Proceedings of the Neural Information Processing Systems Track on Datasets and Benchmarks (NeurIPS Datasets and Benchmarks 2021)}}, + year = {2021} +} + + @book{Zhang23book_d2l, + title = {{Dive into Deep Learning}}, + author = {Zhang, Aston and Lipton, Zachary C. and Li, Mu and Smola, Alexander J.}, + publisher = {Cambridge University Press}, + url = {https://d2l.ai}, + year = {2023} +}