notes

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 91e2aa5fd0cfc64aae22f9e3a9c3060e18f791e7
parent 850f95bd60e32d3ac71b67b8afc0af36566047c4
Author: ling0x <ling0x@users.noreply.github.com>
Date:   Thu,  6 Aug 2026 11:05:34 +0100

bayesian statistics and slam

Diffstat:
Aartificial_intelligence/bayesian_statistics.txt | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aartificial_intelligence/slam.txt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aasync_programming/async_factory.txt | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 269 insertions(+), 0 deletions(-)

diff --git a/artificial_intelligence/bayesian_statistics.txt b/artificial_intelligence/bayesian_statistics.txt @@ -0,0 +1,64 @@ +Bayes Theorem: + +a rule for updating a belief when evidence arrives. Concretely: 1000 people, +10 have a disease. The test catches 9 of those 10, and also falsely flags 99 +of the 990 healthy. You test positive — so you're one of the 9 + 99 = 108 +positives, of whom 9 are actually sick. That's ~8%. The theorem is just that +counting written symbolically: P(A|B) = P(B|A)·P(A) / P(B). The base rate +(10/1000) does most of the work, which is why the answer feels wrong at first. + +Bayes Statistics + +the school of statistics that treats probability itself as a degree of belief, +and uses the theorem above as its engine. You start with a prior (what you +believed before data), multiply by the likelihood (how well each hypothesis +explains the data), get a posterior. Contrast with frequentist statistics, +which treats parameters as fixed unknowns and probability as long-run +frequency, giving you p-values and confidence intervals instead of a +distribution over "what the parameter probably is." Practical upshot: +Bayesian methods let you inject prior knowledge and get honest uncertainty, +at the cost of having to justify the prior and usually needing MCMC or +variational methods to compute anything. + +Bayes Filter + +Bayes' theorem applied recursively over time to estimate a hidden state from +noisy observations. Two alternating steps: + +• Predict: push the belief forward through a motion/dynamics model (uncertainty grows) + +• Update: multiply by the likelihood of the new measurement (uncertainty shrinks) + +The posterior from one cycle becomes the prior for the next. It's the abstract +template; the famous algorithms are special cases — the Kalman filter assumes +everything is Gaussian and linear so the belief stays a mean + covariance, the +extended/unscented Kalman filter relaxes linearity, and the particle filter +drops the Gaussian assumption entirely and represents the belief as a cloud of +weighted samples. Standard tooling in robot localization, SLAM, sensor fusion, +and object tracking. + +Goal of Bayes Filter: To estimate state x, given observation z and control +(action) u, which is to estimate the probability of a agent in state x, +given z and u + +p(x|z,u) + +Derivation of Bayes filter + +Kalman Filter + +Linear Kalman Filter + +Linear Kalman Filter is really just a special case of Bayes Filter, where the +state transition and measurement function are linear and follow Gaussian +distribution. The state transition function is, + +xₜ= Aₜxₜ₋₁+Bₜuₜ+ ϵₜ + +Extended Kalman Filter + +Reference + +Probalistic Robotics, by Sebastian Thrun, Wolfram Burgard, Dieter Fox + +https://bayesianstatistics.com/ +\ No newline at end of file diff --git a/artificial_intelligence/slam.txt b/artificial_intelligence/slam.txt @@ -0,0 +1,54 @@ +=============================================================================== +SLAM (Simultaneous Localization and Mapping) +=============================================================================== + +SLAM = Simultaneous Localization and Mapping. A robot dropped into an unknown +environment has to build a map and figure out where it is in that map — at the +same time. The circularity is the whole problem: a good map requires knowing +where you were when you took each measurement, and knowing where you are +requires a map to measure against. Wheel odometry drifts, sensors are noisy, +so neither side is ever known exactly. + +The Bayesian formulation. You define a joint posterior over trajectory and map +given everything observed: + +p(x₁:ₜ, m | z₁:ₜ, u₁:ₜ) + +where x is pose, m is the map, z are sensor readings, u are control/odometry +inputs. That's it — SLAM is the problem of computing this posterior, and every +algorithm is a different tractable approximation of it. + +The recursion is the Bayes filter from before, with the map bolted into the state: + +• Predict: apply the motion model p(xₜ | xₜ₋₁, uₜ). You moved forward 1m ± noise, +so the pose belief smears out. + +• Update: apply the observation model p(zₜ | xₜ, m). A laser scan that matches +a known wall sharpens the belief; the map cells get updated too. + +Why the naive version fails. The map has thousands of landmarks, and observing +one landmark correlates it with the robot pose and thus with every other +landmark. A full covariance matrix over N landmarks is O(N²) to store and +update, which is why EKF-SLAM chokes past a few hundred features. + +The workarounds are the interesting part: + +FastSLAM uses a Rao-Blackwellized particle filter. Key insight: conditioned on +a known trajectory, the landmarks are independent of each other. So sample +trajectories as particles, and attach a small independent EKF per landmark +per particle. The correlation problem dissolves. + +Graph SLAM / factor graphs (GTSAM, g2o, Ceres) — the modern default. +Drop the filter, keep every pose as a node and every measurement as a +constraint edge. Under Gaussian noise, maximizing the joint posterior is +equivalent to minimizing a sum of squared residuals, so it becomes sparse +nonlinear least squares. The information matrix is sparse because each +measurement touches only a few poses, which is what makes it scale. + +Loop closure is where the Bayesian machinery earns its keep. Recognizing +"I've been here before" injects a constraint linking two distant poses, and +the optimizer redistributes accumulated drift backward across the whole +trajectory. Getting one wrong catastrophically corrupts the map, so place +recognition usually pairs with robust kernels or switchable constraints — +themselves a way of putting a heavier-tailed prior on the residuals so +outliers can't dominate. +\ No newline at end of file diff --git a/async_programming/async_factory.txt b/async_programming/async_factory.txt @@ -0,0 +1,149 @@ +Async Factory Pattern + +A constructor method that constructs the Future function without calling +it. This is used when we don't know about the lifetime of the function. + +The factory pattern splits execution into two phases: + +1. Synchronous setup, runs now: request_body(messages) and api_key.clone() execute immediately, while &mut self is still held. Everything the request needs is copied out of self. + +2. The actual I/O, runs whenever: the async move block captures only those owned values (body, api_key, chunks), so the returned future is 'static — it has no ties to self's lifetime. Hence the doc's "we don't know about the lifetime of the function": the caller might spawn it on another task, join it with other futures, or drop it, and none of that has to be coordinated with the borrow of self. + +(Rust futures are always lazy — even a plain async fn doesn't run until polled. What the factory buys you isn't laziness, it's the lifetime decoupling: setup borrows self, the future doesn't.) + +Box::pin earns its keep when the concrete type must be erased — e.g. different provider implementations behind one trait, or storing the future in a struct field. + + +Async Factory: + +impl LlmInference for LlmClaudeContext { + /// Sends the call and pushes each body frame into `chunks`. + /// + /// The body — settings folded together with `messages` — and the key are both + /// taken before the returned future is built, so the future borrows nothing + /// from `self` and can be spawned or joined freely. + /// + /// A non-success status is rejected before any frame is sent, so a consumer + /// never sees part of a failed response — the provider's own error text comes + /// back on the error instead. + /// + /// # Arguments + /// - `chunks`: Each body frame, in arrival order. Closed when this returns. + /// + /// # Returns + /// - `Ok(())` at the end of the body, or an [`LlmError`] if the request + /// failed, the status was rejected, the connection dropped mid-body, or the + /// receiver went away while frames were still arriving. + fn http_infer( + &mut self, + chunks: Sender<Vec<u8>>, + messages: Vec<LlmClaudeMessage>, + ) -> Pin<Box<dyn Future<Output = Result<(), LlmError>> + Send + 'static>> { + let body = self.request_body(messages); + let api_key = self.api_key.clone(); + + Box::pin(async move { + let mut response = Client::new() + .post(REQUEST_URL) + .header("x-api-key", api_key) + .header("anthropic-version", PROVIDER_VERSION) + .json(&body) + .send() + .await + .map_err(LlmError::Http)?; + + // `send` resolves on the response headers, so the status is known + // while the body is still in flight. Rejecting a bad one here means + // the body never reaches the consumer. + let status = response.status(); + if !status.is_success() { + let detail = response + .text() + .await + .map_err(|e| LlmError::Inference { status, message: e.to_string() })?; + return Err(LlmError::Inference { status, message: detail }); + } + + // `chunk` resolves as soon as hyper has a frame and yields `None` at + // the end of the body, so nothing collects the reply. `send` waits + // when the channel is full — that wait is the backpressure. + while let Some(frame) = response.chunk().await.map_err(LlmError::Http)? { + chunks.send(frame.to_vec()).await.map_err(|_| { + LlmError::ClaudeResponseStreaming { + message: "the chunk receiver was dropped while the response body was \ + still arriving" + .to_string(), + } + })?; + } + Ok(()) + }) + } +} + +Call site: + +In the call site it just spawns that constructed Future function, and then +polls the Future's inner channel that is streaming the responses from +claude, until all messages are polled, and then calls await on the handle +itself to make sure the Future is finished. + + impl LlmInferenceContext { + /// Runs one inference pass: says `message`, then turns the streamed reply into + /// the files it asked for. + /// + /// The turn is appended to [`messages`](LlmInferenceContext::messages) before + /// the call and the model's own reply is appended after it, so a later pass + /// sees the whole conversation. The provider is handed a *snapshot*, which it + /// consumes; the context's own history is untouched by that. + /// + /// Reading and lexing run concurrently with the request: the provider is + /// spawned and its frames are consumed here as they arrive, so a file is + /// available as soon as its fence closes rather than at the end of the reply. + /// + /// # Arguments + /// - `root_path`: The session directory every fenced path is resolved against. + /// - `message`: What to say to the model this pass. + /// + /// # Returns + /// - Every file the reply carried, in the order the model emitted them. An + /// [`LlmError`] if the request failed, a frame could not be lexed, or a + /// fenced path was unsafe to write. + pub async fn infer_llm( + &mut self, + root_path: impl Into<String>, + message: impl Into<String>, + ) -> Result<Vec<AgentFileEvent>, LlmError> { + // Append, then snapshot: the provider consumes its copy, so the clone is + // what keeps this context's history intact across calls. + self.messages.push(LlmClaudeMessage::user(message)); + let history = self.messages.clone(); + + let (tx, mut rx) = mpsc::channel(10); + let req_fut = self.http_ai_provider.http_infer(tx, history); + + let handle = tokio::spawn(req_fut); + // Right now we're using the claude buffer + let mut buffer = Buffer::new(root_path); + let mut events_buffer = Vec::new(); + + while let Some(chunk) = rx.recv().await { + match buffer.ingest_chunk(chunk)? { + BufferResponse::Events(events) => events_buffer.extend(events), + BufferResponse::End { events, end_of_stream } => { + events_buffer.extend(events); + // The model's turn, verbatim, so the next pass can see what it + // already wrote rather than only what we asked for. + self.messages.push(LlmClaudeMessage::assistant(end_of_stream.reply)); + }, + BufferResponse::None => {}, + } + } + // Two layers: the outer says whether the task ran at all (it panicked or + // was cancelled), the inner whether the request itself succeeded. + handle.await.map_err(|e| LlmError::ClaudeResponseStreaming { + message: format!("the inference task did not finish: {e}"), + })??; + Ok(events_buffer) + } +}