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:
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)
+ }
+}