<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Home - Algorithms</title>
    <link rel="self" type="application/atom+xml" href="https://shaostassen.github.io/ShaoFastRobots/tags/algorithms/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://shaostassen.github.io/ShaoFastRobots"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-20T00:00:00+00:00</updated>
    <id>https://shaostassen.github.io/ShaoFastRobots/tags/algorithms/atom.xml</id>
    <entry xml:lang="en">
        <title>Lab 10: Grid Localization using Bayes Filter</title>
        <published>2026-04-20T00:00:00+00:00</published>
        <updated>2026-04-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Shao Stassen
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-10/"/>
        <id>https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-10/</id>
        
        <content type="html" xml:base="https://shaostassen.github.io/ShaoFastRobots/Fast Robots Stuff/lab-10/">&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;The robot doesn&#x27;t know where it is on its own, so it has to figure out its position from ToF sensor readings, this is called localization. In this lab I implemented a Bayes filter, which keeps a running &quot;belief&quot; about where the robot thinks it is. Every time fresh sensor data or a control input comes in, the filter updates that belief through Bayesian inference. The goal of this lab is to get everything working in simulation before putting it on real hardware.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;grid-localization-parameters&quot;&gt;Grid Localization Parameters&lt;&#x2F;h3&gt;
&lt;p&gt;The robot&#x27;s state is 3D: $(x, y, \theta)$. The arena spans roughly -5.5 to 6.5 ft in $x$, -4.5 to 4.5 ft in $y$, and $[-180°, +180°)$ in heading. Since we can&#x27;t compute over a continuous space, the arena is chopped into a 3D grid of $(12, 9, 18)$ cells, each $1 \text{ ft} \times 1 \text{ ft} \times 20°$. Every cell holds a probability of the robot being there, and the whole grid sums to 1. After each update, the cell with the highest probability is our best guess at the robot&#x27;s pose.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bayes-filter-architecture&quot;&gt;Bayes Filter Architecture&lt;&#x2F;h2&gt;
&lt;p&gt;The filter runs in a loop with two stages. The &lt;strong&gt;prediction step&lt;&#x2F;strong&gt; uses control inputs to forecast where the robot moved, and the &lt;strong&gt;update step&lt;&#x2F;strong&gt; corrects that guess by comparing expected sensor readings against the real ones.&lt;&#x2F;p&gt;
&lt;div style=&quot;background-color: #f4f4f4; padding: 20px; border-radius: 5px; font-family: &#x27;Courier New&#x27;, monospace; max-width: 600px; margin: 0 auto;&quot;&gt;
&lt;p&gt;&lt;strong&gt;Algorithm&lt;&#x2F;strong&gt; Bayes_Filter( $bel(x_{t-1}),\ u_t,\ z_t$ ) &lt;br&gt;&lt;br&gt;
    &lt;strong&gt;for all&lt;&#x2F;strong&gt; $x_t$ &lt;strong&gt;do&lt;&#x2F;strong&gt; &lt;br&gt;
        $\overline{bel}(x_t) = \sum_{x_{t-1}} p(x_t \mid u_t, x_{t-1}) , bel(x_{t-1})$ &lt;br&gt;
        $bel(x_t) = \eta , p(z_t \mid x_t) , \overline{bel}(x_t)$ &lt;br&gt;
    &lt;strong&gt;end for&lt;&#x2F;strong&gt; &lt;br&gt;
    &lt;strong&gt;return&lt;&#x2F;strong&gt; $bel(x_t)$&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;h3 id=&quot;odometry-motion-model&quot;&gt;Odometry Motion Model&lt;&#x2F;h3&gt;
&lt;p&gt;The control input $u$ is broken into an initial rotation, a translation, and a final rotation, which together describe any transition between two poses.&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img src=&quot;odometry.jpg&quot; alt=&quot;Odometry Model Parameters&quot; style=&quot;display:block; width:100%; max-width:600px; margin: 0 auto;&quot;&gt;
&lt;figcaption&gt;Odometry model parameters: $\delta_{rot1}$, $\delta_{trans}$, and $\delta_{rot2}$.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;$$\delta_{rot1} = \operatorname{atan2}(\bar{y}&#x27; - \bar{y},, \bar{x}&#x27; - \bar{x}) - \bar{\theta}$$&lt;&#x2F;p&gt;
&lt;p&gt;$$\delta_{trans} = \sqrt{(\bar{x}&#x27; - \bar{x})^2 + (\bar{y}&#x27; - \bar{y})^2}$$&lt;&#x2F;p&gt;
&lt;p&gt;$$\delta_{rot2} = \bar{\theta}&#x27; - \bar{\theta} - \delta_{rot1}$$&lt;&#x2F;p&gt;
&lt;h2 id=&quot;algorithm-implementation&quot;&gt;Algorithm Implementation&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;compute-control-motion-model&quot;&gt;Compute Control &amp;amp; Motion Model&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;compute_control()&lt;&#x2F;code&gt; pulls the three kinematic parameters out of two consecutive poses. &lt;code&gt;odom_motion_model()&lt;&#x2F;code&gt; then evaluates how likely a given transition is by plugging the actual and expected controls into Gaussians.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; math&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; numpy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; compute_control&lt;&#x2F;span&gt;&lt;span&gt;(cur_pose, prev_pose):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_prev, y_prev, yaw_prev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; prev_pose&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x_cur, y_cur, yaw_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; cur_pose&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_trans&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; math.hypot(x_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; x_prev, y_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y_prev)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_rot_1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; math.degrees(math.atan2(y_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; y_prev, x_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; x_prev))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; yaw_prev&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_rot_1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mapper.normalize_angle(delta_rot_1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_rot_2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; yaw_cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; yaw_prev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; delta_rot_1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_rot_2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mapper.normalize_angle(delta_rot_2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; delta_rot_1, delta_trans, delta_rot_2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; odom_motion_model&lt;&#x2F;span&gt;&lt;span&gt;(cur_pose, prev_pose, u):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rot1, trans, rot2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; u&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_rot1, delta_trans, delta_rot2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; compute_control(cur_pose, prev_pose)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    p1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; loc.gaussian(delta_rot1, rot1, loc.odom_rot_sigma)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    p2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; loc.gaussian(delta_trans, trans, loc.odom_trans_sigma)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    p3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; loc.gaussian(delta_rot2, rot2, loc.odom_rot_sigma)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; p1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; p2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; p3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;prediction-step&quot;&gt;Prediction Step&lt;&#x2F;h3&gt;
&lt;p&gt;This is the heavy computation stage. The grid has $12 \times 9 \times 18 = 1944$ cells, and a naive implementation considers every &lt;em&gt;(previous, current)&lt;&#x2F;em&gt; pair, so each tick loops over $1944^2 \approx 3.8$ million transitions. To keep things tractable, I skip any previous cell with belief below $0.0001$, since those barely contribute to the sum. The tradeoff is a small accuracy hit for a huge speedup. After accumulating the probabilities I normalize so the grid stays a valid distribution.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; prediction_step&lt;&#x2F;span&gt;&lt;span&gt;(cur_odom, prev_odom):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; compute_control(cur_odom, prev_odom)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    loc.bel_bar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.zeros((mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_X&lt;&#x2F;span&gt;&lt;span&gt;, mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_Y&lt;&#x2F;span&gt;&lt;span&gt;, mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_A&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; cx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_X&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; cy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_Y&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;            for&lt;&#x2F;span&gt;&lt;span&gt; ca&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_A&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                if&lt;&#x2F;span&gt;&lt;span&gt; loc.bel[cx, cy, ca]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 0.0001&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    prev_pose&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mapper.from_map(cx, cy, ca)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                    for&lt;&#x2F;span&gt;&lt;span&gt; cur_cx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_X&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                        for&lt;&#x2F;span&gt;&lt;span&gt; cur_cy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_Y&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;                            for&lt;&#x2F;span&gt;&lt;span&gt; cur_ca&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;MAX_CELLS_A&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                cur_pose&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mapper.from_map(cur_cx, cur_cy, cur_ca)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                prob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; odom_motion_model(cur_pose, prev_pose, u)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                loc.bel_bar[cur_cx, cur_cy, cur_ca]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; prob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; loc.bel[cx, cy, ca]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    loc.bel_bar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; &#x2F;=&lt;&#x2F;span&gt;&lt;span&gt; np.sum(loc.bel_bar)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;sensor-model-update-step&quot;&gt;Sensor Model &amp;amp; Update Step&lt;&#x2F;h3&gt;
&lt;p&gt;I vectorized the update step to avoid looping over every cell. By reshaping the raw sensor readings and broadcasting them against the cached &lt;code&gt;mapper.obs_views&lt;&#x2F;code&gt; array, NumPy computes all 18 sensor likelihoods across the entire grid in one shot. This is a huge speedup over the nested-loop version if I used the sensor_model function and it still implements the same Bayesian update:&lt;&#x2F;p&gt;
&lt;p&gt;$$p(z_t \mid x_t, m) = \prod_{k=1}^{18} p(z_t^k \mid x_t, m)$$&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; update_step&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    actual_obs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; loc.obs_range_data.flatten().reshape(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;, mapper.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;OBS_PER_CELL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    likelihoods&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; loc.gaussian(mapper.obs_views, actual_obs, loc.sensor_sigma)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cell_likelihoods&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; np.prod(likelihoods,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt; axis&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    loc.bel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; cell_likelihoods&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; loc.bel_bar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    loc.bel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; &#x2F;=&lt;&#x2F;span&gt;&lt;span&gt; np.sum(loc.bel)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;simulation-results&quot;&gt;Simulation &amp;amp; Results&lt;&#x2F;h2&gt;
&lt;p&gt;The video below shows the filter localizing along a pre-planned rectangular path. Red is the raw odometry estimate, green is ground truth, and blue is the Bayes filter&#x27;s belief. The odometry drifts badly, you can see it shoot off the map entirely in the bottom right and overshoot again near the top, but the blue belief tracks green closely the whole way around the obstacle. The white cells behind the robot visualize the belief grid, brighter means higher probability, and I&#x27;m ignoring anything below $0.0001$.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;450&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;embed&#x2F;DMEheQDtAwY&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;figcaption&gt;Bayes simulation tracking ground truth vs. odometry.&lt;&#x2F;figcaption&gt;
&lt;p&gt;The filter works noticeably better near obstacles. This is probably because ToF sensors are more stable at short ranges, so readings there carry more useful information. In the open middle of the arena there aren&#x27;t many nearby things to lock onto, and the belief gets a bit fuzzier. Still, across the full trajectory the Bayes estimate consistently tracked ground truth far better than odometry alone.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;collaboration&quot;&gt;Collaboration&lt;&#x2F;h2&gt;
&lt;p&gt;I referenced Lucca Correia&#x27;s site while debugging the filter and putting together the video demo. I also used lec 17-20 of the class lecture to implement my Bayes Filter.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
